Differences between revisions 2 and 11 (spanning 9 versions)
Revision 2 as of 2012-02-06 05:29:04
Size: 2338
Editor: dethrophes
Comment: added example how foo=bar echo "$foo" can be useful
Revision 11 as of 2022-08-01 14:45:16
Size: 3284
Editor: emanuele6
Comment: unset => unset -v
Deletions are marked like this. Additions are marked like this.
Line 10: Line 10:
$ unset foo $ unset -v foo
Line 23: Line 23:
 * 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.  * 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.
Line 28: Line 28:
$ unset foo $ unset -v foo
Line 37: Line 37:
 * The child Bash process expands the `$foo` using the value from the environment and hands that value to `echo`.  * 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.
Line 41: Line 42:
-----
* '''In The Manual — [[https://www.gnu.org/software/bash/manual/html_node/Simple-Command-Expansion.html#Simple-Command-Expansion|Simple Command Expansion]]'''
-----
Line 42: Line 46:
There are some special cases in bash where understanding this can be useful. Take the following examples There are some special cases in Bash where understanding this can be useful. Take the following example:
Line 44: Line 48:
  # Allow us to set IFS for 1 assigment
  Array1=( "Var 1" "Var 2" "Var 3" "Var 4" )
arr=('Var 1' 'Var 2' 'Var 3' 'Var 4')
Line 47: Line 50:
  # joins each array element with a ";"
  IFS=";" eval 'echo "${Array1[*]}"'
  # 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
Line 51: Line 56:
  IFS=";" eval 'JoinedVariable="${Array1[*]}"'
  echo "${JoinedVariable}"
  # Var 1;Var 2;Var 3;Var 4
# 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[*]}"'
Line 55: Line 60:
# Alternative two: a function. Local variables are not POSIX (as of 2017).
# Usage: join joinchar [arg ...]
join() {
  local IFS="$1"
  shift
  printf '%s\n' "$*"
}
}}}
Line 56: Line 69:
  # splits the string at ";"
  IFS=";" eval 'Array2=(${JoinedVariable})'
  IFS="_" eval 'echo "${Array2[*]}"'
  # Var 1_Var 2_Var 3_Var 4

}}}
Here, the `eval` alternative is simpler and more elegant than unsetting [[IFS]] (''In your opinion! -- GreyCat''). Appropriate care must be taken [[BashFAQ/048|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.)

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 -v 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)