SSH might be reading from standard input, eating up your actionlist. Try to redirect ssh's standard input to /dev/null:
ssh $REMOTE_HOST zfs snapshot -r ${RMT_FILESYSTEM}@${MARKER} </dev/null
As a general rule, when running commands that may interfere with standard input under a while read
-style loop, I like to wrap the whole loop body into braces:
cat /tmp/uuoc | while read RMT_FILESYSTEM ISMOUNTED
do {
echo ${RMT_FILESYSTEM}@${MARKER}
[ "$ISMOUNTED" = "yes" ] && ssh $REMOTE_HOST zfs snapshot -r ${RMT_FILESYSTEM}@${MARKER}
echo Remote Command Return Code: $?
} < /dev/null; done
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
The
for
loop is fine here. But note that this is because the file contains machine names, which do not contain any whitespace characters or globbing characters.for x in $(cat file); do …
does not work to iterate over the lines offile
in general, because the shell first splits the output from the commandcat file
anywhere there is whitespace, and then treats each word as a glob pattern so\[?*
are further expanded. You can makefor x in $(cat file)
safe if you work on it:Related reading: Looping through files with spaces in the names?; How can I read line by line from a variable in bash?; Why is
while IFS= read
used so often, instead ofIFS=; while read..
? Note that when usingwhile read
, the safe syntax to read lines iswhile IFS= read -r line; do …
.Now let's turn to what goes wrong with your
while read
attempt. The redirection from the server list file applies to the whole loop. So whenssh
runs, its standard input comes from that file. The ssh client can't know when the remote application might want to read from its standard input. So as soon as the ssh client notices some input, it sends that input to the remote side. The ssh server there is then ready to feed that input to the remote command, should it want it. In your case, the remote command never reads any input, so the data ends up discarded, but the client side doesn't know anything about that. Your attempt withecho
worked becauseecho
never reads any input, it leaves its standard input alone.There are a few ways you can avoid this. You can tell ssh not to read from standard input, with the
-n
option.The
-n
option in fact tellsssh
to redirect its input from/dev/null
. You can do that at the shell level, and it'll work for any command.A tempting method to avoid ssh's input coming from the file is to put the redirection on the
read
command:while read server </home/kenny/list_of_servers.txt; do …
. This will not work, because it causes the file to be opened again each time theread
command is executed (so it would read the first line of the file over and over). The redirection needs to be on the whole while loop so that the file is opened once for the duration of the loop.The general solution is to provide the input to the loop on a file descriptor other than standard input. The shell has constructs to ferry input and output from one descriptor number to another. Here, we open the file on file descriptor 3, and redirect the
read
command's standard input from file descriptor 3. The ssh client ignores open non-standard descriptors, so all is well.In bash, the
read
command has a specific option to read from a different file descriptor, so you can writeread -u3 server
.Related reading: File descriptors & shell scripting; When would you use an additional file descriptor?