为什么通过 SSH 运行时 bash -c 'cd /; pwd' 会失败

为什么通过 SSH 运行时 bash -c 'cd /; pwd' 会失败

我试图弄清楚为什么以下命令无法正确设置工作目录。

ssh localhost bash -c "cd / ;pwd"

以下版本有效:

bash -c "cd / ;pwd"

ssh localhost "cd / ;pwd" 

ssh localhost 'bash -c "cd / ;pwd"'

ssh localhost bash -c "pwd; cd / ;pwd"

我注意到 macos 和 ubuntu 18.04 上存在这种行为。

答案1

初步说明

在这里我将使用当地的偏僻的区分 SSH 客户端和 SSH 服务器端分别发生的情况。忘记您的 SSH 服务器是localhost,即“本地”;将其视为巧合。通常,SSH 服务器是远程的。


分析

ssh能够根据您提供的多个参数构建远程执行代码,但此代码不会作为数组传递。它传递的是作为单个字符串. 参数是否

  • bash,,-ccd / ;pwd
  • bash,,,,,-ccd/;pwd
  • bash -c,,cd/ ;pwd
  • bash -c cd / ;pwd

在每个情况下,结果字符串都是bash -c cd / ;pwd。字符串将由远程 shell(由 SSH 服务器生成的 shell)解释。因为;远程 shell 将识别两个命令。它会将它们拆分为单词bash-ccd/;然后(作为单独的命令)pwd。这将运行bash执行cd(注意:甚至不cd /) 不能改变父 shell 的状态,所以它是一个无操作(在某些奇怪的设置中它可能会引发错误,但仍然是无操作);然后pwd完全独立于前一个命令。


解决方案

您要使用的字符串ssh与上面的不同,它是bash -c "cd / ;pwd"。要获得它,您需要使双引号在本地 shell(您键入命令的地方ssh …)的引号删除阶段中保留下来。您需要引用引号或对其进行转义。引用或转义的引号无法避免;在本地被解释为命令终止符/分隔符,因此也必须引用或转义它:

ssh localhost 'bash -c "cd / ;pwd"'   # this one you have already discovered
#or
ssh localhost bash -c \"cd / \;pwd\"

(这些并不是唯一可能生成所需远程字符串的本地命令。)

请注意,在前一种情况下,只有一个参数可以构建结果字符串;在后一种情况下,字符串由ssh多个本地参数创建。当我需要引用或转义任何内容时,我更喜欢制作一个参数。如果所需的远程命令是静态的(我的意思是如果它不依赖于本地扩展,即本地变量或类似的东西),那么制作这样的本地参数是算法并不难, 它可以在 Bash 中自动化


边注

运行bash -cviassh可能没有必要。无论你ssh构建什么字符串,它都会被远程端的某个 shell(用户的登录 shell)解释。这个 shell 可能是也可能不是 Bash。如果你故意想让 Bash 解释某些代码(例如因为代码包含巴什主义) 则bash -c很有用。能够运行的 shellbash -c …应该能够cd / ;pwd按照您的期望进行解释,因此在这种情况下bash -c不需要。

您尝试过ssh localhost "cd / ;pwd",它成功了。它传递cd / ;pwd到您的远程 shell,无论是否是 Bash。它比带有 的版本麻烦少bash -c

我理解cd / ;pwd这只是一个例子。通常,代码可能需要 Bash。另一方面,通常它可能包含引号。我的观点是,有两个或三个 shell,每个 shell 都会解释引号等;它们是:

  1. 产卵前的本地壳ssh
  2. SSH 守护进程生成的远程 shell
  3. bash -c这种情况下bash

堆叠的 shell 数量越多,需要的引用和/或转义就越复杂。这bash -c需要付出代价:它增加了本地命令的复杂性。如果您知道远程端的登录 shell 可以正确解释您想要运行的代码,那么bash -c这很可能只是一种负担。

相关内容