4

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 '-'.

    4 Answers 4

    3

    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'

    6
    • 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
      CommentedJan 8, 2017 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.CommentedJan 8, 2017 at 6:25
    • When I echo the $var I get the string with spaces for some reason
      – BenB
      CommentedJan 8, 2017 at 6:28
    • 1
      Is IFS still set to '-'? If so, that may be the cause; reset it with unset IFS.CommentedJan 8, 2017 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.CommentedJan 8, 2017 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%-*}" 
    5
    • 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
      CommentedJan 8, 2017 at 6:19
    • Wow, this is strong! Why did you not use printf -v var "%s-" ... syntax?CommentedJan 8, 2017 at 8:22
    • @F.Hauri You know what, thanks for reminding me; i always tend to forget that :)
      – heemayl
      CommentedJan 8, 2017 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.CommentedJan 10, 2017 at 9:15
    • @F.Hauri I was serious there :) You are absolutely right :)
      – heemayl
      CommentedJan 10, 2017 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.

      1

      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" 

        You must log in to answer this question.

        Start asking to get answers

        Find the answer to your question by asking.

        Ask question

        Explore related questions

        See similar questions with these tags.