When tasked to do things multiple time, you usually don’t type things multiple times or copy/paste them. Instead, you write a loop which does that for you. Yesterday, I horribly tumbled trying to use one of those loops.
I had to change the content of a file on numerous machines. So I wrote the machine names into a file and ran
cat hosts | while read h; do ssh $h "sed -i 's/a/b/g' file"; done
I considered that “more elegant” than a bulky line as in “for h in host1 host2 host3 … host251; do” …
Well, since the loop did not present any error messages, I considered the task done. Couple hours later though, I notice the change I made apparently did not work and so investigated what happened. Turns out, only the first host in my “hosts” file was changed. Huh?!
I quickly tested the behaviour by running a test loop like
cat hosts | while read h; do ssh $h "hostname"; done
Which only gave one hostname. Using a for-loop however, worked.
for h in $(cat hosts); do ssh $h "hostname"; done
A colleague suggested using the “-n” switch for the ssh command and this apparently fixed the “issue”. Running that “while read”-loop with “ssh -n” again fixed the file on all hosts. The reason for this behaviour is interesting though. ssh, by default, “grabs” the stdin file descriptor and prevents “read” to use it after having read the first argument. The -n switch tells ssh to use /dev/null as stdin instead which makes the loop work as expected. In the for-loop however, there is no problem with ssh grabbing stdin because the loop iterations are not read from there.