mlf has asked for the wisdom of the Perl Monks concerning the following question:

Given the following script:
#!/usr/bin/perl while (<STDIN>) { chomp; open(H, "ssh $_ 'uptime' 2>&1 |"); $b = <H>; close(H); printf("%s: %s\n", $_, $b); }
and a file hosts.dat containing the the host names
larry curley moe
and assuming appropriate ssh keys in place and passphases entered, I'd expect the output of
./test.pl <hosts
to be something along the line of
larry: 22:20:46 up 53 days, 1:27, 1 user, load average: 0.06, 0.04 +, 0.05 curley: 22:20:46 up 67 days, 7:56, 1 user, load average: 0.07, 0.0 +5, 0.05 moe: 22:20:47 up 12 days, 11:22, 1 user, load average: 0.04, 0.04, + 0.05
Instead, I get only the first line before the program exits. Commenting out these two lines:
$b = <H>; close(H);
allows the script to hit all three hosts, but without the requested uptime info. Uncommenting either line reverts to single line output. Also prefixing the ssh command with 'echo' correctly echos the command for all three hosts.

Based on my tinkering it would appear that reading any input from the ssh command or closing it's filehandle is somehow clobbering perl's STDIN stream. I can think of a number of possible ways around this situation, but I'm curious about why it's happening in the first place.

Replies are listed 'Best First'.
Re: Odd perl/ssh interaction
by huck (Prior) on Jul 08, 2017 at 03:49 UTC

    I THINK what is happening is that that form of open means your perl program and ssh share the same STDIN. And that ssh is the one flushing STDIN.

    But i cant find a reference to this right now. I do know that if you didnt redirect STDOUT (and/or STDERR) your perl program and ssh share those two files as well

    What might test that is  open(H, "echo '' | ssh $_ 'uptime' 2>&1 |");. In that case the shell is redirecting the STDIN of ssh

    edit:another way to test this theory (redirecting the STDIN of ssh) would be  open(H, "ssh $_ 'uptime' </dev/null 2>&1 |");.

      Yes, you hit the nail on the head. Ultimately adding -n to the ssh command, as suggested by haukex below, is the simplest solution.

      Thanks for the help!

Re: Odd perl/ssh interaction
by haukex (Archbishop) on Jul 08, 2017 at 10:49 UTC

    I think that the script could do with some improvements - Use strict and warnings, three arg open with proper error checking, error checking on the close due to the piped open, and using <> instead of <STDIN> would be some best practices that would also make the script more robust and easily debuggable in case of errors.

    Anyway, I think huck is on to something. I can reproduce the problem when I run the program as ./1194547.pl <hosts.txt, but when I run the program as cat hosts.txt | perl 1194547.pl, it works. Also, adding the </dev/null to the command as huck suggested makes it work as well, as does using the ssh -n switch, which has the same effect. So while I haven't dug into the details, changing the uptime to cat does seem to show that the problem is ssh is reading STDIN and sending it to the remote command.

    However, as I wrote about at length here, shelling out to an external process is not always a good idea, especially when you can't trust the user input. The great Net::OpenSSH module, while it does also use external ssh processes, does this in a more robust fashion, plus it provides a whole lot more functionality (Net::OpenSSH::Parallel might be of interest to you). I would strongly recommend using that module instead of shelling out to ssh yourself.

      Agreed regarding strict/warn, error checking, <> and such. The original script had most of that, but I stripped it out as I trimmed down to a minimal test case. Also agree in principle the the Net::OpenSSH suggestion, though I work in an environment where 'extra' modules are not always easily/readily available. They were on my list of possible work-arounds.

      Good call on the 'cat' substitution. That definitely highlights the problem. Ultimately '-n' will be the best solution in this case.

      Thanks for the help!

        I work in an environment where 'extra' modules are not always easily/readily available.

        Note that Net::OpenSSH is a pure-Perl module, and so in the worst case can be installed by copying it over. Only if you need password authentication the XS based module IO::Pty needs to be installed.

        Update:

        Agreed regarding strict/warn, error checking, ... I stripped it out as I trimmed down to a minimal test case.

        The effort to create an SSCCE is appreciated, but it's best to leave those things in when posting questions here, otherwise most monks' first thought will be "how do we know the problem isn't being hidden because those things are missing"? :-)

Re: Odd perl/ssh interaction
by Laurent_R (Canon) on Jul 08, 2017 at 08:54 UTC
    Maybe you could store the output of the hosts command into an array and then loop over the array to issue your ssh instruction.

    Also, rather than storing the output of the ssh command into a file, it might be simpler to issue the command with backticks and store the result into a variable. Something like this (untested):

    my $uptime_output = `ssh $_ 'uptime'`;
    (I don't know whether you need redirection in the ssh command. I would assume the output should come on STDOUT.)

      Backticks, like two-argument pipe opens, share the same problem: Undefined default shell behaviour. See Re: Ssh and qx. Using a perl SSH agent would entirely avoid the subprocess and the default shell. For a cross-platform, core-only solution, "Safe pipe opens" in perlipc explains how to avoid the default shell. The main trick in this case is to open STDIN from /dev/null in the child, then use multi-argument exec to prevent any interaction with the default shell.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
      Yes, reading the full host list is a valid work-around. Unfortunately though, backticks exhibit the same issue with STDIN being consumed by the ssh command. Ultimately adding -n to keep it from sucking up STDIN is the solution.

      Regarding the redirection in the shh command, normally your assumption would hold true as in the 'uptime' test case, but alas, the third party program I'm dealing with on the remote systems in the real use case was not written with proper *nix philosophy in mind and writes most of it's "output" on STDERR.