2

I have this code, it prints the correct result, but I can't figure out how to get the echo from the last line into a variable.

# hostname is 'tech-news-blog-324344' . Setting it into array host_name_array
IFS='-' read -r -a host_name_array <<< "$(hostname)" 
#removing the last part of string after last "-"
unset 'host_name_array[${#host_name_array[@]}-1]'
( IFS=$'-'; echo "${host_name_array[*]}" )  
#result is 'tech-news-blog'

How could get the value of the last line into a variable? I've tried the following:

( IFS=$'-'; URL="${host_name_array[*]}" )

But I get the result "tech news blog" with spaces between pieces of array instead of '-'.

2

When IFS='-' read -r -a host_name_array <<< "$(hostname)" is ran, the array is (tech news blog 324344).

After the final element is removed with unset 'host_name_array[${#host_name_array[@]}-1]', the array is (tech news blog).

So, to get this to echo tech-news-blog, some substitution will have to be done, since echo "${host_name_array[*]}" will yield tech news blog:

With tr: echo "${host_name_array[*]}" | tr ' ' '-'

sed: echo "${host_name_array[*]}" | sed 's/ /-/g'

  • Both the echo works, but if I try to set that result into a variable and reuse it I get from the variable the string with spaces – BenB Jan 8 '17 at 6:21
  • var=$(echo "${host_name_array[*]}" | tr ' ' '-'), but I think @heemayl's solution is better, since it is much less bulky and awkward. – Kenneth B. Jensen Jan 8 '17 at 6:25
  • When I echo the $var I get the string with spaces for some reason – BenB Jan 8 '17 at 6:28
  • 1
    Is IFS still set to '-'? If so, that may be the cause; reset it with unset IFS. – Kenneth B. Jensen Jan 8 '17 at 6:42
  • 1
    Using echo with tr/sed is silly here. "${array[*]}" does join the elements on the first character of IFS. Here you're joining with spaces and converting all spaces to dashes instead of just joining with dash in the first place. – Stéphane Chazelas Jan 8 '17 at 9:17
3

You could do this by outputting the elements separated by -, and then strip off the last - by parameter expansion:

$ var=$(printf '%s-' "${host_name_array[@]}")

$ echo "$var"
foo-bar-spam-egg-

$ var="${var%-}"

$ echo "$var"
foo-bar-spam-egg

Also you need ${host_name_array[@]} instead of ${host_name_array[*]} to prevent outputting the elements as a whole separated by first character of IFS.


What you are trying to do can be achieved by a simple parameter expansion:

${var%-*}

Example:

$ var=$(hostname)

$ echo "${var%-*}"
  • The echo outputs correctly, but if I try to put into a variable and use the variable, I get the string with spaces "tech news blog". How could I set the string "tech-blog-news" into a variable and re-use it? – BenB Jan 8 '17 at 6:19
  • Wow, this is strong! Why did you not use printf -v var "%s-" ... syntax? – F. Hauri Jan 8 '17 at 8:22
  • @F.Hauri You know what, thanks for reminding me; i always tend to forget that :) – heemayl Jan 8 '17 at 9:16
  • @heemayl Using var=$(printf... implie one fork to subshell, where printf -v ... let you use builtin printf and set var without forks! This is a lot quicker. – F. Hauri Jan 10 '17 at 9:15
  • @F.Hauri I was serious there :) You are absolutely right :) – heemayl Jan 10 '17 at 9:18
2

(...) introduces a subshell. So $URL wouldn't be set after that (still have the value it had before the subshell). You want:

IFS=-
read -r -a host_name_array <<< "$(hostname)" 
unset 'host_name_array[${#host_name_array[@]}-1]'
URL="${host_name_array[*]}"

"${host_name_array[*]}" joins the elements on the arrays on the first character of $IFS just like "$*" does in standard sh.

If the reason why you're using a subshell is because you don't want to modify $IFS globally, you could do that in a function where you give $IFS a local scope:

f() {
  local IFS=-
  ...
}
f

Or use command substitution that also creates a subshell but allows passing data to the parent shell:

URL=$(IFS=-; printf '%s\n' "${host_name_array[*]}")

Here though I'd use standard sh expansion to remove that trailing component:

URL=$(uname -n) # uname -n is the standard equivalent of hostname
URL=${URL%-*}

It has several advantages over the above:

  • it works even if the text contains newline characters (very unlikely for host names here though);
  • if the text doesn't contain -, it doesn't remove anything;
  • it is more efficient. read <<< $(hostname) means running hostname, read its output through a pipe, storing that in a temp file and have read read the first line of that;
  • it doesn't clobber the variable namespace with those temporary variables
  • it is shorter;
  • it is portable. No need to install bash to run that code. The system's sh will suffice.

In any case, remember to quote your variables when using them in list contexts:

printf '%s\n' "$URL"

When doing:

echo $URL

You're invoking the split+glob operator. So, if $IFS still contains -, that $URL would be split on -, and echo would output those words separated by spaces.

0

specific answer: whipe forks!

As this already use bashisms, sed, tr or any other external tool are useless:

IFS='-' read -r -a host_name_array < <(hostname)
#removing the last part of string after last "-"
unset host_name_array[${#host_name_array[@]}-1]

shorted=${host_name_array[*]}
echo ${shorted// /-}

Or simplier:

This is a lot shorter, quicker and simplier:

read myvar < <(hostname)
echo ${myvar%-*}

And if really you need host_name_array for other reasons:

read shorted < <(hostname)
host_name_array=(${shorted//-/ })
shorted=${shorted%-*}

So you have an array and a shorted hostname:

declare -p host_name_array shorted

will return something like:

declare -a host_name_array='([0]="tech" [1]="news" [2]="blog" [3]="324344")'
declare -- shorted="tech-news-blog"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.