After reading the bash man pages and with respect to this post.
I am still having trouble understanding what exactly the eval
command does and which would be its typical uses. For example if we do:
bash$ set -- one two three # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n} ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n) ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one
What exactly is happening here and how do the dollar sign and the backslash tie into the problem?
I've recently had to use
eval
to force multiple brace expansions to be evaluated in the order I needed. Bash does multiple brace expansions from left to right, soexpands to
but I needed the second brace expansion done first, yielding
The best I could come up with to do that was
This works because the single quotes protect the first set of braces from expansion during the parsing of the
eval
command line, leaving them to be expanded by the subshell invoked byeval
.There may be some cunning scheme involving nested brace expansions that allows this to happen in one step, but if there is I'm too old and stupid to see it.
eval
takes a string as its argument, and evaluates it as if you'd typed that string on a command line. (If you pass several arguments, they are first joined with spaces between them.)${$n}
is a syntax error in bash. Inside the braces, you can only have a variable name, with some possible prefix and suffixes, but you can't have arbitrary bash syntax and in particular you can't use variable expansion. There is a way of saying “the value of the variable whose name is in this variable”, though:$(…)
runs the command specified inside the parentheses in a subshell (i.e. in a separate process that inherits all settings such as variable values from the current shell), and gathers its output. Soecho $($n)
runs$n
as a shell command, and displays its output. Since$n
evaluates to1
,$($n)
attempts to run the command1
, which does not exist.eval echo \${$n}
runs the parameters passed toeval
. After expansion, the parameters areecho
and${1}
. Soeval echo \${$n}
runs the commandecho ${1}
.Note that most of the time, you must use double quotes around variable substitutions and command susbtitutions (i.e. anytime there's a
$
):"$foo", "$(foo)"
. Always put double quotes around variable and command substitutions, unless you know you need to leave them off. Without the double quotes, the shell performs field splitting (i.e. it splits value of the variable or the output from the command into separate words) and then treats each word as a wildcard pattern. For example:eval
is not used very often. In some shells, the most common use is to obtain the value of a variable whose name is not known until runtime. In bash, this is not necessary thanks to the${!VAR}
syntax.eval
is still useful when you need to construct a longer command containing operators, reserved words, etc.In my experience, a "typical" use of eval is for running commands that generate shell commands to set environment variables.
Perhaps you have a system that uses a collection of environment variables, and you have a script or program that determines which ones should be set and their values. Whenever you run a script or program, it runs in a forked process, so anything it does directly to environment variables is lost when it exits. But that script or program can send the export commands to stdout.
Without eval, you would need to redirect stdout to a temp file, source the temp file, and then delete it. With eval, you can just:
Note the quotes are important. Take this (contrived) example:
In the question:
outputs errors claiming that files a and tty do not exist. I understood this to mean that tty is not being interpreted before execution of grep, but instead that bash passed tty as a parameter to grep, which interpreted it as a file name.
There is also a situation of nested redirection, which should be handled by matched parentheses which should specify a child process, but bash is primitively a word separator, creating parameters to be sent to a program, therefore parentheses are not matched first, but interpreted as seen.
I got specific with grep, and specified the file as a parameter instead of using a pipe. I also simplified the base command, passing output from a command as a file, so that i/o piping would not be nested:
works well.
is not really desired, but eliminates the nested pipe and also works well.
In conclusion, bash does not seem to like nested pipping. It is important to understand that bash is not a new-wave program written in a recursive manner. Instead, bash is an old 1,2,3 program, which has been appended with features. For purposes of assuring backward compatibility, the initial manner of interpretation has never been modified. If bash was rewritten to first match parentheses, how many bugs would be introduced into how many bash programs? Many programmers love to be cryptic.