💾 Archived View for thrig.me › tech › ssh-commands.gmi captured on 2023-07-22 at 17:39:31. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-04-19)
-=-=-=-=-=-=-
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.
tags #ssh #sh