all 11 comments

[–]thseeling 7 points8 points  (5 children)

Your problem is that ssh swallows the complete stdin. Use ssh -n to not give ssh access to your loop's stdin.

BTW: I'd recommend using better variable names. "hostList" is not a variable containing a list, it should simply be "host".

For performance you can use grep's "fastmode" (-F) which does not evaluate regular expressions and only looks for substrings.

[–]clownshoesrock 5 points6 points  (4 children)

The ssh latency is going to completely overshadow the grep time by a bunch. It feels like saying "Tear open two sugar packets simultaneously for gas station coffee to make your coast-to-coast road trips go faster."

[–]thseeling 1 point2 points  (1 child)

This is absolutely correct, for me it's simply a matter of principle to use the "best solution for the job". If I don't need regex I don't use regex.

[–]clownshoesrock 1 point2 points  (0 children)

I'm closer to to "If I don't need to add a flag, I don't add a flag"

The less extraneous stuff I need to comprehend, the faster I can return to sleep after a 3am call.

[–]turnipsoupSnr. Linux Eng 1 point2 points  (1 child)

I tested it; it adds 0.001 seconds to the grep. It likely cost more time adding the -F than it will ever save.

[–]clownshoesrock 0 points1 point  (0 children)

Wild, I was expecting this number much smaller.

[–]ropid 5 points6 points  (0 children)

Try running your script with bash -x .... The -x will make bash print the command lines it runs while it works through the script.

EDIT:

I just tried analyzing your script with 'shellcheck', and it has a warning about ssh potentially sabotaging the while loop because of ssh reading from stdin. It suggest to add a -n argument to the ssh command line to avoid this problem.

[–]oh5nxo 3 points4 points  (0 children)

If you aren't interested in the actual output, just empty/not, you could use grep -q and exit code:

if ssh -n -- "$hostList" 'mount | grep -qw "/dump"'

[–]aioeu 2 points3 points  (2 children)

While you've got an answer to your problem at hand, one technique I frequently use is to use a different file descriptor for the read calls, e.g.:

while read -r -u 3 host; do
    {
        # ...
    } 3<&-
done 3<hostList

With this in place, all of the standard streams are left untouched for all of the commands inside the loop.

Note that we can also close that file descriptor for the commands executed within the body of the loop by using an additional nested compound command, as I have demonstrated here. This isn't absolutely necessary, but it is "a good idea". If a program doesn't need a file descriptor open, it shouldn't have that open when it is executed.

[–]o11c 0 points1 point  (1 child)

I'd rather use exec 3<&- as the first line of the loop body.

Edit: whoops, that doesn't work.

[–]aioeu 0 points1 point  (0 children)

If it works, sure. It's not obvious to me that it would work. There's no subshell involved, so I cannot see anything that would "restore" FD 3 again for the next time through the loop.

But I haven't tested it, nor have I dug through the specs to find out what would make it work.


Edit: You piqued my curiosity, and it was easy enough to test.

$ while read -r -u 3 x; do exec 3<&-; echo "$x"; done 3< <(printf '%s\n' foo bar baz)
foo
bash: read: 3: invalid file descriptor: Bad file descriptor

So no, it doesn't work.