3

I would like to take a specific line from a variable to another variable. I tried this but it doesn't work:

c="1.apple
2.banna
3.peach"

read "Please choose fruit [1-3]:" t

a=$c | awk "NR==$t"
echo "You choose: $a"

What is my mistake?

jimmij
  • 47,140

2 Answers2

5

Use Here String redirection <<< together with Command Substitution $() and don't forget to put double quotes around your variables:

a=$(awk "NR==$t" <<< "$c")
jimmij
  • 47,140
  • 1
    Don't forget to put double quotes around command substitution, too! – Darkhogg Jan 18 '15 at 12:27
  • 1
    @Darkhogg - quoting the command substitution makes no difference here as it is contextually an assignment - not a list. var=$(: std out of command sub) is no different than var="$(: std out of command sub)" and the former form is a little less cluttered. – mikeserv Jan 18 '15 at 17:41
0

In the first place:

read "Please choose fruit [1-3]:" t

...will not work. It looks like you're trying to provide a prompt string to the shell builtin read, but read interprets its first argument as the name of a variable to assign the value of the line it reads from stdin unless it is handed options.

read -p "Please choose fruit [1-3]:" t

...is an option supported in many shells and is probably nearer to what you intend to do.

That said, you probably shouldn't stack multiple values in a singly delimited assignment unless you have previously settled on a means splitting it out. When you do:

var=' some list of things '

The shell will eventually parse that out to something like:

\0 some list of things \0

...and assign the single name to the single value. Many shells offer more advance forms of delimiting - such as named arrays - but all POSIX shells provide at least one easily definable array per function context - the $@ shell array. Various implementations providing named arrays generally mimic the $@ shell array behavior for their named arrays as well.

So rather than assigning all of those individual values to a single string as you do, you might do instead:

set apple banana peach

You can witness the effect of this by addressing each individual value singly by number like:

printf %s\\n "$1"

...which prints:

apple

Note - if you get to using values larger than 9 it is best to enclose the reference in braces like "${10}"

You can address the number of different strings in "$#" like:

printf %d\\n "$#"

...which prints...

3

You can address the lot of them in a list of separate strings like:

printf %s\\n "$@"

...which prints:

apple
banana
peach

...or as a single, concatenated string like:

printf %s\\n "$*"

...where the separate array value strings are concatenated on the first character contained in the shell variable $IFS. So if you have a default $IFS value of <space><tab><newline> each string in the array will join to the next with a single <space> betwixt them. The above command, for example, prints:

apple banana peach

...but if I do:

IFS='fruit sucks'; printf %s\\n "$*"

...it prints:

applefbananafpeach

Most shells that implement named array extensions do so with similar syntax, excepting that the various means of addressing the array must be associated with a name. An array assignment typically looks like:

array=( apple banana peach )

...or...

array[0]=apple array[1]=banana array[2]=peach

Compared to "$1", "$#", "$@", "$*", named arrays will usually work like "${array[1]}", "${#array[@]}", "${array[@]}", and "${array[*]}" where the previously mentioned relationship between "$*" and "$IFS" still holds true for "${array[*]}".

Once you have properly delimited your values your problem becomes a much easier one to handle:

set apple banana peach; n=0
{   for a do printf "$((n+=1)).:\t%s\n" "$a"; done
    printf "Please choose fruit [1-$#]: "; read t
} <>/dev/tty >&0 &&
[ "0$((!${#t}))" -lt "0${t##*[!0-9]*}" ] &&
eval 'printf "You choose: %s\n" "${'"$t"}\"

Some may scoff at the use of eval above but its use here is no less safe than many would do like "${array[$t]}" (which is one way you might do this with named arrays) because "${array[$t]}" implies a second evaluation of $t as an index after it is first parsed as a string. Without testing it as I do above to ensure that it contains at least one and nothing other than a digit and that it is greater than 0 (or whatever the shell implementation's minimum named array index might be) either case could render unintentional results.

mikeserv
  • 58,310