eval
is part of POSIX. Its an interface which can be a shell built-in.
Its described in the "POSIX Programmer's Manual": http://www.unix.com/man-page/posix/1posix/eval/
eval - construct command by concatenating arguments
It will take an argument and construct a command of it, which will be executed by the shell. This is the example of the manpage:
1) foo=10 x=foo
2) y='$'$x
3) echo $y
4) $foo
5) eval y='$'$x
6) echo $y
7) 10
- In the first line you define
$foo
with the value '10'
and $x
with the value 'foo'
.
- Now define
$y
, which consists of the string '$foo'
. The dollar sign must be escaped
with '$'
.
- To check the result,
echo $y
.
- The result will be the string
'$foo'
- Now we repeat the assignment with
eval
. It will first evaluate $x
to the string 'foo'
. Now we have the statement y=$foo
which will get evaluated to y=10
.
- The result of
echo $y
is now the value '10'
.
This is a common function in many languages, e.g. Perl and JavaScript.
Have a look at perldoc eval for more examples: http://perldoc.perl.org/functions/eval.html
ssh
is reading the rest of your standard input.
while read HOST ; do … ; done < servers.txt
read
reads from stdin. The <
redirects stdin from a file.
Unfortunately, the command you're trying to run also reads stdin, so it winds up eating the rest of your file. You can see it clearly with:
$ while read HOST ; do echo start $HOST end; cat; done < servers.txt
start server1.mydomain.com end
server2.mydomain.com
server3.mydomain.com
Notice how cat
ate (and echoed) the remaining two lines. (Had read done it as expected, each line would have the "start" and "end" around the host.)
Why does for
work?
Your for
line doesn't redirect to stdin. (In fact, it reads the entire contents of the servers.txt
file into memory before the first iteration). So ssh
continues to read its stdin from the terminal (or possibly nothing, depending on how your script is called).
Solution
At least in bash, you can have read
use a different file descriptor.
while read -u10 HOST ; do ssh $HOST "uname -a" ; done 10< servers.txt
# ^^^^ ^^
ought to work. 10
is just an arbitrary file number I picked. 0, 1, and 2 have defined meanings, and typically opening files will start from the first available number (so 3 is next to be used). 10 is thus high enough to stay out of the way, but low enough to be under the limit in some shells. Plus its a nice round number...
Alternative Solution 1: -n
As McNisse points out in his/her answer, the OpenSSH client has an -n
option that'll prevent it from reading stdin. This works well in the particular case of ssh
, but of course other commands may lack this—the other solutions work regardless of which command is eating your stdin.
Alternative Solution 2: second redirect
You can apparently (as in, I tried it, it works in my version of Bash at least...) do a second redirect, which looks something like this:
while read HOST ; do ssh $HOST "uname -a" < /dev/null; done < servers.txt
You can use this with any command, but it'll be difficult if you actually want terminal input going to the command.
Best Answer
eval
performs an extra level of substitution and processing on the remainder of the line.In the first iteration of the loop, i is set to "VAR1", and one level of backslash-escaping is reduced, so:
becomes:
which evaluates to:
(repeat for the next loop, only $i is then VAR2, and $VAR2=dad)