Discussion:
The first command of a nested compound command receives no arguments
Parke
2018-10-21 05:54:55 UTC
Permalink
Hello,

The following three commands work as expected:

***@cosmic:~# ssh localhost 'echo 1 && echo 2 && echo 3'
1
2
3
***@cosmic:~# ssh localhost "dash -c 'echo 1 && echo 2 && echo 3'"
1
2
3
***@cosmic:~# ssh localhost echo 1 2 3 4 5
1 2 3 4 5

The following three commands produce unexpected behavior.
Specifically, it appears that the first nested command is called with
no arguments.

***@cosmic:~# ssh localhost dash -c 'echo 1 && echo 2 && echo 3'

2
3
***@cosmic:~# ssh localhost dash -c 'touch a && touch b && touch c'
touch: missing file operand
Try 'touch --help' for more information.
***@cosmic:~# ssh localhost dash -c 'touch a b && touch c d && touch e f'
touch: missing file operand
Try 'touch --help' for more information.

Is the above the intended behavior? If so, why? What is going on?
It seems strange to me.

The above commands were run on Ubuntu 18.10 on a VPS at Digital Ocean.
The openssh package version appears to be: 1:7.7p1-4.

Another pair of examples:

***@cosmic:~# ssh localhost dash -c 'hostname && hostname && hostname'
cosmic
cosmic
cosmic
***@cosmic:~# ssh localhost dash -c 'echo `hostname` && echo
`hostname` && echo `hostname`'

cosmic
cosmic

Another strange example:

***@cosmic:~# ssh localhost dash -c 'exit 2 && exit 3 && exit 4'
(Will exit with status of 3.)

Thank you,

Parke
Timo Kilpilehto
2018-10-21 11:10:52 UTC
Permalink
Post by Parke
The following three commands produce unexpected behavior.
Specifically, it appears that the first nested command is called with
no arguments.
2
3
touch: missing file operand
Try 'touch --help' for more information.
touch: missing file operand
Try 'touch --help' for more information.
Is the above the intended behavior? If so, why? What is going on?
It seems strange to me.
Hi Parke,

This has nothing to do with openssh or even chaining commands really.
We can see similar behaviour when just using eval and sh -c:

eval sh -c "echo none of this makes any difference"
-> prints empty line

So, what happens here?

The eval utility shall construct a command by concatenating arguments
together, separating each with a <space> character. The constructed
command shall be read and executed by the shell.

So, in short what happens is this gets executed:
sh -c echo none of this makes any difference
-> again, prints empty line

And why does this happen you might ask?

Here's what man page says about option -c:

-c string If the -c option is present, then commands are read from
string. If there are arguments after the string, they are
assigned to the positional parameters, starting with $0.

In other words what this means is that shell will execute the sole
command (echo) we provided in a sub shell as an inline script. Then
the rest of the arguments if any will be provided to the script as
positional arguments starting with $0. However, our script does not
use them for anything, it just runs echo with no arguments. In order
to make it output these arguments we'd have to do something like this:

sh -c 'echo $@' _ now something gets printed
-> outputs "now something gets printed"

In the end it may just be easier to use quoting on the entire inline
script to execute:
sh -c "echo now something gets printed"

And in order to persist the quoting through evaluating using eval or
ssh or anything of the sort we need extra layer of quoting:
eval 'sh -c "echo now something gets printed"'

So to summarise ssh handles in this case the command precisely as if
using eval which I would say is quite likely the intended behaviour
and exactly what one would expect.

I hope this clarifies things for you.

Best regards,
Timo
Christian Weisgerber
2018-10-21 11:56:30 UTC
Permalink
2
3
This should give you an idea:

$ sh -c 'sh -c echo 1 && echo 2 && echo 3'

2
3
--
Christian "naddy" Weisgerber ***@mips.inka.de
Parke
2018-10-21 17:21:56 UTC
Permalink
Hi,

Thanks to Timo and Christian for their answers.
Post by Timo Kilpilehto
eval sh -c "echo none of this makes any difference"
Where is it documented that ssh is going to eval my command? The fact
remains that ssh does not transparently pass the command to the remote
host.

This pair of commands result in the same output (a blank line):

$ dash -c echo 1
$ ssh localhost dash -c echo 1

However, the following three pairs of commands result in different output:

$ dash -c 'echo 1'
$ ssh localhost dash -c 'echo 1'

$ dash -c echo\ 1
$ ssh localhost dash -c echo\ 1

$ dash -c 'echo\ 1'
$ ssh localhost dash -c 'echo\ 1'

It appears that what is happening is that ssh is flattening the
multiple command arguments to a single string without escaping them.
(I concede there may be no "standard" way to do such escaping.)

It also appears that the ssh protocol defines the command as a single
string, not an argv-style list of multiple strings. (See section 6.5
of https://www.ssh.com/a/rfc4254.txt .)

It might be worth documenting the escape-less flattening of the
command (and the corresponding loss of information) on the ssh
manpage. I could write something and submit a patch, if the openssh
developers are interested.

Cheers,

Parke
Gert Doering
2018-10-21 17:30:44 UTC
Permalink
Hi,
Post by Parke
Thanks to Timo and Christian for their answers.
Post by Timo Kilpilehto
eval sh -c "echo none of this makes any difference"
Where is it documented that ssh is going to eval my command? The fact
remains that ssh does not transparently pass the command to the remote
host.
This is not a *ssh* thing, but a generic "how does quoting on unix work
if two levels of shell are involved".

gert
--
"If was one thing all people took for granted, was conviction that if you
feed honest figures into a computer, honest figures come out. Never doubted
it myself till I met a computer with a sense of humor."
Robert A. Heinlein, The Moon is a Harsh Mistress

Gert Doering - Munich, Germany ***@greenie.muc.de
Parke
2018-10-21 18:27:17 UTC
Permalink
Post by Gert Doering
Post by Parke
Where is it documented that ssh is going to eval my command? The fact
remains that ssh does not transparently pass the command to the remote
host.
This is not a *ssh* thing, but a generic "how does quoting on unix work
if two levels of shell are involved".
It is not obvious (at least to me), nor is it documented (that I can
find), that ssh should be thought of as a shell. Neither the
OpenSSH.com homepage nor the ssh manpage describe ssh as a shell.

If the ssh protocol could pass argv-style string lists (and this is
certainly technically possible), the protocol could be transparent and
command flattening could be avoided.

I claim the current behavior can be unexpected and is undocumented.

The current manpage describes:

ssh [snip] [user@]hostname [command]

I believe the manpage could improved to describe:

ssh [snip] [user@]hostname [command [arg ...]]

After all, ssh *does* accept commands with extra arguments. (ssh
could exit with an error instead of accepting extra arguments.)
However, the current manpage fails to describe how those extra
arguments will be relayed to the remote host.

Cheers,

Parke
Colin Watson
2018-10-21 18:52:31 UTC
Permalink
Post by Gert Doering
Post by Parke
Post by Timo Kilpilehto
eval sh -c "echo none of this makes any difference"
Where is it documented that ssh is going to eval my command? The fact
remains that ssh does not transparently pass the command to the remote
host.
This is not a *ssh* thing, but a generic "how does quoting on unix work
if two levels of shell are involved".
This is an oversimplification. It's an SSH thing to the extent that the
SSH protocol defines that command execution is done by passing a single
command string, the ssh client specifically joins its arguments using
spaces in order to send them to the server, and the ssh(1) manual page
doesn't explain what's going on at all.

There are of course limits to how much it can nail down, because the
exact behaviour depends on the remote user's shell; but the
documentation could at least explain that the client joins any
command-line arguments following the destination using spaces and sends
them to the server as a single string, which can then generally be
expected to pass them as an argument to "sh -c". People who aren't
especially familiar with the details of the SSH protocol but who are
using ssh as a building block would then have a better chance of working
out what quoting they need to use.

In general, it's a very good idea for commands that can invoke
subsidiary commands to document the quoting requirements even if they're
straightforward, as they're often non-trivial and the consequences of
getting quoting wrong can be unfortunate.

The protocol-level part of this is
https://bugzilla.mindrot.org/show_bug.cgi?id=2283.
--
Colin Watson [***@debian.org]
Christian Weisgerber
2018-10-21 20:38:08 UTC
Permalink
Post by Colin Watson
This is an oversimplification. It's an SSH thing to the extent that the
SSH protocol defines that command execution is done by passing a single
command string, the ssh client specifically joins its arguments using
spaces in order to send them to the server,
That behavior was inherited straight from rsh(1), which was really
just a wrapper around rcmd(3). This whole thing goes back to 1982.
https://svnweb.freebsd.org/csrg/usr.bin/rsh/rsh.c?view=log

Nowadays people may not be aware that one of the selling points of
ssh was that it was a drop-in replacement for rsh/rlogin/rcp.
--
Christian "naddy" Weisgerber ***@mips.inka.de
Parke
2018-10-21 21:33:18 UTC
Permalink
Post by Christian Weisgerber
Nowadays people may not be aware that one of the selling points of
ssh was that it was a drop-in replacement for rsh/rlogin/rcp.
Even if people are so aware, the behavior itself is not documented in
the FreeBSD, Linux, or OpenSolaris rsh manpages.

Cheers,

Parke

Loading...