ssh 命令引用有什么区别?

What difference does ssh command quoting make?

这些命令如何导致不同的输出?

⋊> ssh host bash -c 'cd /tmp; pwd'
/home/manu
⋊> ssh host "bash -c 'cd /tmp; pwd'"
/tmp

类似问题的回答 指出:

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.

* 的情况下,我可以理解这一点,但是在这个例子中,哪些部分将在本地进行评估?此外,bash 命令前的 -- 也无济于事。

这里要理解的是 ssh 只是连接它的参数(与 $* 的方式相同),并将连接后的字符串传递给 sh -c.


因此,在以下情况下:

ssh josh-play bash -c 'cd /tmp; pwd'

...ssh 运行s:

sh -c 'bash -c cd /tmp; pwd'

...因此,sh 运行s:

bash -c cd /tmp
pwd

...正如您可以自己测试的那样,bash -c cd /tmp 并没有做太多有用的事情(运行s 的脚本文本仅包含 cd/tmp 存储为参数,但脚本文本从不读取其参数,因此没有实际意义)。此外,一旦 bash 退出,我们就会回到父 sh 进程,其中 cd 从未 运行.

传递给外部 shell 的句法引号完全丢失,并且 pwd 不是由您手动触发的 bash 调用的(在这种用法中,只是在没有任何参数的情况下调用 cd——/tmp</code> 中,但是作为参数传递给 <code>cd 的脚本从不取消引用该变量)但是 sh ssh 隐式调用。


如果您知道您的远程 sh 是由 bash 或 ksh 提供的——支持 $'' 扩展的 shell——您可以执行以下操作任意 argv 数组(在本例中为 bash-ccd /tmp; pwd):

# ask your shell to generate an eval-safe quoted form of your argument list
printf -v rmt_cmd '%q ' bash -c 'cd /tmp; pwd'

# pass that through to be run by the remote sh -c
ssh josh-play "$rmt_cmd"

上面的警告是,如果您的参数列表可以包含换行符或隐藏字符,bash 中的 printf %q 或 ksh 可以以 POSIX sh 不是的形式转义它保证能读懂。为避免这种情况:

# ask your shell to generate an eval-safe quoted form of your argument list
printf -v rmt_cmd '%q ' bash -c 'cd /tmp; pwd'

# ...and use bash to evaluate that quoted form.
ssh josh-play 'exec bash -s' <<<"$rmt_cmd"