我试图弄清楚为什么以下命令无法正确设置工作目录。
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
,,-c
cd / ;pwd
bash
,,,,,-c
cd
/
;pwd
bash -c
,,cd
/ ;pwd
bash -c cd / ;pwd
在每个情况下,结果字符串都是bash -c cd / ;pwd
。字符串将由远程 shell(由 SSH 服务器生成的 shell)解释。因为;
远程 shell 将识别两个命令。它会将它们拆分为单词bash
、-c
、cd
、/
;然后(作为单独的命令)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 -c
viassh
可能没有必要。无论你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 都会解释引号等;它们是:
- 产卵前的本地壳
ssh
, - SSH 守护进程生成的远程 shell
- 在
bash -c
这种情况下bash
。
堆叠的 shell 数量越多,需要的引用和/或转义就越复杂。这bash -c
需要付出代价:它增加了本地命令的复杂性。如果您知道远程端的登录 shell 可以正确解释您想要运行的代码,那么bash -c
这很可能只是一种负担。