3

I was working with a command like this:

ssh another_host <<EOF
set -x
command_a
command_b
run_framework
command_c
...
EOF

The odd thing was that only commands up to and including run_framework were echoed by -x. And appeared that command_c and onwards were not executed at all. Initially, I suspected set -e was in effect, and the run_framework command failed; but that wasn't the case.

From searching around, my conjecture was this: the run_framework command was somehow munching the stdin, and thus, when the shell gets around to execute the following commands, the stdin was exhausted, so doesn't run anything beyond that. To test this, I rewrote the framework command above as:

(: | run_framework)

And then it started working as I expected. I.e., I now see command_c and beyond are executed.

The said framework is a ton of Python scripts, so I couldn't pin down what exactly was munching the stdin.

Is this an anti-pattern with ssh, to execute commands like this? To be safe, shouldn't one run every such command in isolation in order to prevent the ill-effects of accidental stdin munching?

For some non-ssh scenario:

$ cat script
echo only this line is executed
./runs_head
echo not executed
echo not executed either
$ cat runs_head
head -n1 > /dev/null

Interestingly, there's a difference between:

$ cat script | bash
only this line is executed

And

$ bash < script
only this line is executed
not executed either

But I wonder why there's a difference. Can anyone explain?

0

1 Answer 1

5

Using ssh with Here Documents

So a command being read and executed before next command is read, which is causing your problems. To avoid this, use a group command, so the entire group command is read before executing the group command. For example, try the following.

ssh another_host <<EOF
{
set -x
command_a
command_b
run_framework
command_c
...
}
EOF

or maybe this would be safer.

ssh another_host <<EOF
: | {
set -x
command_a
command_b
run_framework
command_c
...
}

or

ssh another_host <<EOF
{
set -x
command_a
command_b
run_framework
command_c
...
} < /dev/null

Note: When I use ssh another_host <<EOF, I get the following error message.

Pseudo-terminal will not be allocated because stdin is not a terminal.

Either one of the following 2 changes can be use to avoid this message.

  • Add the -T option to the ssh command, as in the following.
    ssh -T another_host <<EOF
    
    This option disables pseudo-terminal allocation.
  • Add bash -s to the ssh command, as in the following.
    ssh another_host 'bash -s' <<EOF
    
    This executes the bash -s command on the remote host instead of a login shell.

I suppose if some of the commands actually needed input, then another "here document" could be used. For example, see the following.

ssh another_host 'bash -s' <<'EOF'
{
echo a
echo b
read x
echo "$x"
echo c
head -n1
echo d          
} <<'EOF2'
hi there
nice day
bye now
EOF2
EOF

For me, this produced the following output.

a
b
hi there
c
nice day
d

The OP has the following script file.

echo only this line is executed
./runs_head
echo not executed
echo not executed either

I changed the script file to be the following.

{
echo only this line is executed
./runs_head
echo not executed
echo not executed either
}

Now both cat script | bash and bash < script produce the following output.

only this line is executed
not executed
not executed either

Using head with Pipes

One my mac, the default pipe buffer size is 64 kiB. If you are using Linux you may be able to determine your buffer size by entering the command below.

: | pipesz --get

For example, using Arch Linux produced the following output. Note that 65536 bytes = 64 kiB.

fd 0    65536   0

The commands below were used to create the myfile file, which will be used to illustrate how the head command behaves.

printf "%s\n" a b c d > myfile
printf "%s\n" w x y z | dd bs=1024 seek=64 of=myfile

The contents of this myfile are listed below.

  • The following lines occur at the beginning of the file.
    a
    b
    c
    d
    
  • 65528 bytes of zeros are in the middle of the file.
  • The following lines occur at the end of the file.
    w
    x
    y
    z
    

To confirm the contents, the following commands were entered.

head -n4 myfile; hexdump -C myfile; tail -n4 myfile

The output is shown below.

a
b
c
d
00000000  61 0a 62 0a 63 0a 64 0a  00 00 00 00 00 00 00 00  |a.b.c.d.........|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00010000  77 0a 78 0a 79 0a 7a 0a                           |w.x.y.z.|
00010008
w
x
y
z

For my first test, I entered the following. Here each head -n1 command is reading from the same regular file.

for i in 1 2 3; do head -n1; done < myfile

Below is the output, which to me looks OK.

a
b
c

For my second test, I entered the following. Here each head -n1 command is reading from the same pipe.

cat myfile | for i in 1 2 3; do head -n1; done

The output is shown below.

a
w

The following happened.

  • The first head -n1 read the first 64 kiB from the pipe and output the line containing a.
  • The second head -n1 read the remaining 8 bytes from the pipe and output the line containing w.
  • The third head -n1 had no bytes left to read from the pipe and therefore output nothing.

For my third test, I entered the following. Here each read command is reading from the same pipe.

cat myfile | for i in 1 2 3; do read; echo $REPLY; done

Below is the output, which to me looks OK.

a
b
c

My conclusion would be that the command head -n1 has undocumented behavior with respect to pipes and therefore the use of the command should be avoided when reading from a pipe.

References

3
  • Are you able to explain the difference in original behaviour when using cat | and < script for reading the input? Commented 19 hours ago
  • 1
    I added an explanation. Commented 13 hours ago
  • 1
    Wow, this is an amazingly thorough examination of some stuff I had really thought I already had a good grasp on, covering a lot that I was missing. Kudos!! Commented 7 hours ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.