Differences between revisions 2 and 3
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 3 as of 2012-02-06 19:05:04
Size: 2871
Editor: GreyCat
Comment: clean up eval section (provide traditional solutions as well, link to FAQ 48)
Deletions are marked like this. Additions are marked like this.
Line 41: Line 41:

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 examples:
Line 44: Line 43:
  # Allow us to set IFS for 1 assigment
Line 47: Line 45:
  # 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="${Array1[*]}"
  unset IFS
Line 51: Line 51:
  IFS=";" eval 'JoinedVariable="${Array1[*]}"'
  echo "${JoinedVariable}"
  # Var 1;Var 2;Var 3;Var 4
  # Alternative solution: temporarily set IFS for the duration of eval
  IFS=";" eval 'JoinedVariable="${Array1[*]}"'
}}}
Line 55: Line 55:
Here, the `eval` alternative is simpler and more elegant. Appropriate care must be taken [[BashFAQ/048|to ensure safety when using eval]].
Line 56: Line 57:
  # splits the string at ";"
  IFS=";" eval 'Array2=(${JoinedVariable})'
  IFS="_" eval 'echo "${Array2[*]}"'
  # Var 1_Var 2_Var 3_Var 4
{{{
  # split the string at ";"
  # Traditional solution, using read with temporary IFS value
  IFS=";" read -ra Array2 <<< "$JoinedVariable"
Line 61: Line 62:
  # Alternative, using eval with temporary IFS value and set -f/+f
  set -f
  IFS=";" eval 'Array2=(${JoinedVariable})'
  set +f
Line 62: Line 67:

We need `set -f` to prevent [[glob]] expansion of the fields of the `JoinedVariable`. `set +f` restores globbing after we're done. In this case, the `eval` alternative didn't really help us any; in fact, it's quite a bit worse, as we must assume that the script wants globbing enabled.

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.

This version works as we expect:

$ unset 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 the $foo using the value from the environment and hands that value to echo.

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.

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

  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="${Array1[*]}"
  unset IFS

  # Alternative solution: temporarily set IFS for the duration of eval
  IFS=";" eval 'JoinedVariable="${Array1[*]}"'

Here, the eval alternative is simpler and more elegant. Appropriate care must be taken to ensure safety when using eval.

  # split the string at ";"
  # Traditional solution, using read with temporary IFS value
  IFS=";" read -ra Array2 <<< "$JoinedVariable"

  # Alternative, using eval with temporary IFS value and set -f/+f
  set -f
  IFS=";" eval 'Array2=(${JoinedVariable})'
  set +f

We need set -f to prevent glob expansion of the fields of the JoinedVariable. set +f restores globbing after we're done. In this case, the eval alternative didn't really help us any; in fact, it's quite a bit worse, as we must assume that the script wants globbing enabled.

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