Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one properly assign temporary Bash variables on a per-command basis?

Tags:

scope

bash

ifs

Bash seems to behave unpredictably in regards to temporary, per-command variable assignment, specifically with IFS.

I often assign IFS to a temporary value in conjunction with the read command. I would like to use the same mechanic to tailor output, but currently resort to a function or subshell to contain the variable assignment.

$ while IFS=, read -a A; do
>   echo "${A[@]:1:2}"                # control (undesirable)
> done <<< alpha,bravo,charlie
bravo charlie

$ while IFS=, read -a A; do
>   IFS=, echo "${A[*]:1:2}"          # desired solution (failure)
> done <<< alpha,bravo,charlie
bravo charlie

$ perlJoin(){ local IFS="$1"; shift; echo "$*"; }
$ while IFS=, read -a A; do
>   perlJoin , "${A[@]:1:2}"          # function with local variable (success)
> done <<< alpha,bravo,charlie
bravo,charlie

$ while IFS=, read -a A; do
>   (IFS=,; echo "${A[*]:1:2}")       # assignment within subshell (success)
> done <<< alpha,bravo,charlie
bravo,charlie

If the second assignment in the following block does not affect the environment of the command, and it does not generate an error, then what is it for?

$ foo=bar
$ foo=qux echo $foo
bar
like image 512
vintnes Avatar asked Dec 22 '22 23:12

vintnes


1 Answers

$ foo=bar
$ foo=qux echo $foo
bar

This is a common bash gotcha -- and https://www.shellcheck.net/ catches it:


foo=qux echo $foo
^-- SC2097: This assignment is only seen by the forked process.
             ^-- SC2098: This expansion will not see the mentioned assignment.

The issue is that the first foo=bar is setting a bash variable, not an environment variable. Then, the inline foo=qux syntax is used to set an environment variable for echo -- however echo never actually looks at that variable. Instead $foo gets recognized as a bash variable and replaced with bar.

So back to your main question, you were basically there with your final attempt using the subshell -- except that you don't actually need the subshell:

while IFS=, read -a A; do
  IFS=,; echo "${A[*]:1:2}"
done <<< alpha,bravo,charlie

outputs:

bravo,charlie

For completeness, here's a final example that reads in multiple lines and uses a different output separator to demonstrate that the different IFS assignments aren't stomping on each other:

while IFS=, read -a A; do
  IFS=:; echo "${A[*]:1:2}"
done < <(echo -e 'alpha,bravo,charlie\nfoo,bar,baz')

outputs:

bravo:charlie
bar:baz
like image 130
George Avatar answered Dec 25 '22 13:12

George