How is it that these commands result in different output?
⋊> ssh host bash -c 'cd /tmp; pwd'
/home/manu
⋊> ssh host "bash -c 'cd /tmp; pwd'"
/tmp
An answer to a similar question here states that:
The trailing arguments are combined into a single string which is passed to as an argument to the -c option of your login shell on the remote machine.
In case of an *
I can understand this, but what part would be evaluated locally in this example? Also, an --
before the bash command doesn't help.
The thing to understand here is that
ssh
simply concatenates its arguments (in the same way that$*
does), and passes that concatenated string tosh -c
.Thus, in the case of:
...ssh runs:
...thus, sh runs:
...and as you can test yourself,
bash -c cd /tmp
doesn't do much that's useful (the script text it runs consists only ofcd
;/tmp
is stored as an argument, but the script text never reads its arguments, making that moot). Moreover, as soon asbash
exits, we're back in the parentsh
process, in whichcd
was never run.The syntactic quotes passed to your outer shell are entirely lost, and the
pwd
is invoked not by thebash
that you manually triggered (which, in this usage, simply invokescd
without any arguments --/tmp
is in$1
, but the script passed as an argument tocd
never dereferences that variable) but by thesh
that ssh implicitly invokes.If you know that your remote
sh
is provided by bash or ksh -- a shell supporting the$''
extension -- you can do the following for any arbitrary argv array (in this casebash
,-c
,cd /tmp; pwd
):The caveat to the above is that if your argument list can contain newlines or hidden characters,
printf %q
in bash or ksh can escape it in a form that POSIX sh isn't guaranteed to be able to read. To avoid that: