Differences between revisions 9 and 10
Revision 9 as of 2017-10-05 17:46:42
Size: 3103
Editor: GreyCat
Comment: join function, alternative to unsetting IFS or eval
Revision 10 as of 2019-10-25 21:44:51
Size: 3281
Editor: geirha
Comment: Link to the relevant part of the manual
Deletions are marked like this. Additions are marked like this.
Line 42: Line 42:
-----
* '''In The Manual — [[https://www.gnu.org/software/bash/manual/html_node/Simple-Command-Expansion.html#Simple-Command-Expansion|Simple Command Expansion]]'''
-----

Why doesn't foo=bar echo "$foo" print bar?

This is subtle, and has to do with the exact order in which the BashParser performs each step.

Many people, when they first learn about var=value command and how it temporarily sets a variable for the duration of a command, eventually work up an example like this one and become confused why it doesn't do what they expect.

As an illustration:

$ unset foo
$ foo=bar echo "$foo"

$ echo "$foo"

$ foo=bar; echo "$foo"
bar

The reason the first one prints a blank line is because of the order of these steps:

  • The parameter expansion of $foo is done first. An empty string is substituted for the quoted expression.

  • After that, Bash sets up a temporary environment and puts foo=bar in it.

  • The echo command is run, with an empty string as an argument, and foo=bar in its environment. But since echo doesn't care about environment variables, it ignores that, and prints only a newline.

This version works as we expect:

$ unset -v foo
$ foo=bar bash -c 'echo "$foo"'
bar

In this case, the following steps are performed:

  • A temporary environment is set up with foo=bar in it.

  • bash is invoked within that environment, and given -c and echo "$foo" as its two arguments.

  • The child Bash process expands $foo using the value from the environment and hands that value to echo.

  • echo receives bar as its only argument, and prints it, plus a newline.

It's not entirely clear, in all cases, why people ask us this question. Mostly they seem to be curious about the behavior, rather than trying to solve a specific problem; so I won't try to give any examples of "the right way to do things like this", since there's no real problem to solve.


* In The Manual — Simple Command Expansion


There are some special cases in Bash where understanding this can be useful. Take the following example:

arr=('Var 1' 'Var 2' 'Var 3' 'Var 4')

# join each array element with a ";"
# Traditional solution: set IFS, then unset it afterward
IFS=\;
joinedVariable="${arr[*]}"
unset -v IFS

# Alternative one: temporarily set IFS for the duration of eval.
# Double-quotes work around a Bash bug in versions < 4.3.0
IFS=\; command eval 'JoinedVariable="${arr[*]}"'

# Alternative two: a function.  Local variables are not POSIX (as of 2017).
# Usage: join joinchar [arg ...]
join() {
  local IFS="$1"
  shift
  printf '%s\n' "$*"
}

Here, the eval alternative is simpler and more elegant than unsetting IFS (In your opinion! -- GreyCat). Appropriate care must be taken to ensure safety when using eval. The command prefix is required for all shells other than Bash plus Bash POSIX mode (see http://wiki.bash-hackers.org/commands/builtin/eval#using_the_environment). This won't work in recent versions of zsh due to an apparent regression (documented behavior broken for setopt POSIX_BUILTINS), or busybox due to a bug in that environment assignments fail to propagate in this case. (feel free to file bugs if anybody cares enough.)

BashFAQ/104 (last edited 2022-08-01 14:45:16 by emanuele6)