I suddenly realized I don't know how to execute things over SSH.
I tried to do
$ ssh user@server sh -c 'echo "hello"'
but it outputs nothing, or rather, it outputs an empty line. If the command given to ssh
is run through $SHELL -c
on the remote host, then I can see why that is (or I can try to rationalize it to myself anyway).
Ok, second try:
$ ssh user@server 'echo "hello"'
hello
All well and good.
Now for what I was really hoping would work:
$ ssh user@server 'echo "hello $1"' sh "world"
hello sh world
Hmm… where does the sh
come from? This is indicating that $1
is empty and that what really gets executed on the other side is something like
$SHELL -c 'echo "hello sh world"'
and not what I had hoped,
$SHELL -c 'echo "hello $1"' sh "world"
Is there a way to safely pass arguments to the script executed via ssh
, in a sane and sensible way that is analogous to running
sh -c 'script text' sh "my arg1" "my arg2" "my arg3" ...
but on the remote host?
My login shell, both locally and remotely is /bin/sh
.
Safely = Preserving whitespace etc. in arguments.
Sanely = No crazy escaping of quotes.
Best Answer
The first thing to understand in this process is how ssh handles its arguments. I don't mean the arguments to the thing you're trying to run, but arguments of ssh. When you invoke
ssh
, the arguments after the remote host specification (user@server
) are concatenated together, and passed through the shell on the remote end. This is important to note, as just because your arguments are properly split on the local side, does not mean they will be properly split on the remote side.To use your example:
These arguments get concatenated as the command:
This is why you get
The double space between
hello
andsh
is because that's where$1
was supposed to go, but there is no$1
.As another example, without the
$1
, is:Which results in the output:
This is because the arguments are being concatenated together, so you end up with the command:
Since there is no way to get around the command being passed through a shell, you just have to ensure that what you passed can survive the shell evaluation. The way I usually accomplish this is with
printf "%q "
For example:
Which results in the output:
While it's cleaner and easier to understand with
cmd
being a separate var, it's not required. The following works just the same:This also works fine with your shell argument example:
Which results in the output:
As an alternative solution, you can pass your command as a complete shell script. For example:
There are drawbacks to this solution though as it's harder to do programmatically (generating the doc to pass on STDIN). Also because you're using STDIN, if you want the script on the remote side to read STDIN, you can't (at least not without some trickery).