💾 Archived View for thrig.me › tech › ssh-commands.gmi captured on 2023-04-19 at 23:16:35. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

🚧 View Differences

-=-=-=-=-=-=-

SSH Commands

SSH commands differ from ordinary shell commands; that is, the shell command

    sh -c 'echo foo; echo bar'

is very different from

    ssh host sh -c 'echo foo; echo bar'

despite the misleading visual similarity of the two.

    $ sh -c 'echo foo; echo bar'
    foo
    bar
    $ ssh localhost sh -c 'echo foo; echo bar'

    bar

The verbose ssh -v -v -v, usually a good debug step, will only indicate that "sh -c echo foo; echo bar" was sent.

    $ ssh -v -v -v localhost sh -c 'echo foo; echo bar' 2> log >/dev/null
    $ grep -i sending log
    debug1: Sending command: sh -c echo foo; echo bar
    $ rm log

The shell quotes have been removed; that is done by the shell that runs the original command. In either case the shell '' will not be present, as can be confirmed with a process tracing tool.

    $ ktrace sh -c 'echo foo; echo bar'
    foo
    bar
    $ kdump | grep -3 bar | sed '/--/q'
     15225 ktrace   ARGS
            [0] = "sh"
            [1] = "-c"
            [2] = "echo foo; echo bar"
     15225 sh       RET   execve 0
     15225 sh       CALL  kbind(0,0,0)
     15225 sh       RET   kbind 0
    --

That is, the shell code results in something like the following C being run; the single quotes were for the shell itself to group the echos into a single argument.

    execl("/bin/sh", "sh", "-c", "echo foo; echo bar", (char *)0);

If we think about

    debug1: Sending command: sh -c echo foo; echo bar

for a bit, we may figure out what has happened. This is a better state to be in than denial over the fact that our mental model is perhaps at odds with reality.

    sh -c echo

What does this do? It runs the echo command without any arguments. The foo cannot be an argument to echo; if it were, we would see it printed. How about a minimal test case with no ssh involved?

    $ sh -c echo foo

    $

There is no foo. If you quote things up as "echo foo" or 'echo foo' there will be foo. But that's not what we told ssh to run. Anyways, foo here is an argument to whatever it is that the -c does run.

    $ sh -c 'echo ">>>$0<<<"' foo bar
    >>>foo<<<
    $ sh -c 'echo ">>>$1<<<"' foo bar
    >>>bar<<<

However in "sh -c echo foo" the echo does nothing with the $0 and thus prints a blank line. This is like having the not very useful function

    $ function ignoreargs { echo; }; ignoreargs foo

    $

and then to wonder why foo is not printed.

At this point we might guess that ssh has sent the commands as a single string and not as individual arguments,

    sh -c echo foo; echo bar

which means that if -c needs to see a single argument that will need to be quoted on the remote system:

    sh -c 'echo foo; echo bar'

and, this quoting must also be quoted to protect it from the shell that is running the ssh command:

    "sh -c 'echo foo; echo bar'"

And to test:

    $ ssh localhost "sh -c 'echo foo; echo bar'"
    foo
    bar

Additional levels of quoting might get out of hand if the sh script is to do anything at all complicated; in that case I would most likely first copy the code to the system, and then run it with suitable arguments. Another option is to use a different language. For example TCL uses a different quoting syntax than the shell does; {...} is a literal quote:

    #!/usr/bin/env tclsh8.6
    puts [ exec ssh localhost { sh -c 'echo foo; echo bar' } ]

Or in C:

    ...
    execlp("ssh", "ssh", "localhost", "sh -c 'echo foo;echo bar'", 0);
    ...

If anything, alternate language implementations may help distinguish the syntax of the outer shell layer from that of the sh run by way of the ssh command.

Back to tech index

tags #ssh #sh