Why are the arguments supplied for the command run through ssh interpreted by shell before they are passed to the command on the server side?

Jeremy Lin jeremy.lin at gmail.com
Sun Jan 12 09:52:30 AEDT 2020


On Sat, Jan 11, 2020 at 2:02 AM Yuri <yuri at rawbw.com> wrote:
>
> On 2020-01-11 01:38, Darren Tucker wrote:
> > The command you give is always handled on the server by your shell in some
> > fashion.  It has to be, because SSH only specifies an opaque string for the
> > remote command, so without doing so you would not be able to specify
> > arguments at all.
>
> It's not obvious why does it have to be this way. ssh sends the command
> as an array of strings. The first string is the command, and the
> subsequent strings are arguments. It can easily call the same command
> with the same arguments on the remote host.

While it's important to understand how local shell processing works
(and this is obviously out-of-scope of the ssh(1) man page), I think
ssh(1) doesn't adequately explain how the "command" is executed on the
remote side, and how a command is formed from multiple command tokens.

>From https://github.com/openssh/openssh-portable/blob/ed3ad71b17adcd1fb4431d145f53cee1c6a1135e/ssh.c#L1069-L1072,
you can see that multiple command tokens are simply joined with spaces
into a single actual "command". That implies that the remote side sees
all of the following variations as exactly equivalent:

$ ssh host 'printargs "foo bar" baz | cat' # ssh sees a single command
token locally
$ ssh host printargs '"foo' 'bar"' baz \| cat # ssh sees 6 command
tokens locally
$ ssh host printargs '"foo bar" baz |' cat # ssh sees 3 command tokens locally

>From https://github.com/openssh/openssh-portable/blob/ed3ad71b17adcd1fb4431d145f53cee1c6a1135e/session.c#L1703-L1711,
you can see that these would all result approximately in running this
command remotely:

$SHELL -c 'printargs "foo bar" baz | cat'

I say "approximately" because for simpler presentation, I used shell
syntax above (as if you were to manually run the command in a remote
shell), but you can see from the source that ssh actually does this
via a fork-and-exec, so there would be no single quotes anywhere, for
example.

The output would look something like this:

argv[0] = {/usr/local/bin/printargs}
argv[1] = {foo bar}
argv[2] = {baz}

Here's a bash implementation of printargs:

#!/bin/bash

for (( i = 0; i <= $#; ++i )); do
    printf 'argv[%d] = {%s}\n' $i "${!i}"
done


More information about the openssh-unix-dev mailing list