我有一个本地主机和一个远程主机,都运行 Ubuntu,shell 设置为 bash。在我的主目录中,我有两个文件file-1
和file-2
,分别位于本地主机和名为 的远程主机上remote
。每个主目录中还有一些其他文件,我只想列出匹配的文件file-*
。
在本地,这些会产生预期的结果file-1 file-2
:
$ ls file-*
$ bash -c 'ls file-*'
但这些命令返回远程主目录中的所有文件。那里发生了什么事?
$ ssh remote bash -c 'ls file-*'
$ ssh remote bash -c 'ls "file-*"'
$ ssh remote bash -c 'ls file-\*'
$ ssh remote bash -c 'ls "file-\*"'
我知道这只会ssh remote 'ls file-*'
产生预期的结果,但为什么似乎ssh remote bash -c 'ls ...'
放弃了传递给 的参数ls ...
? (我还通过管道传输了远程执行的 ls 的输出,并且它被传递,因此ls
似乎只有 受到影响:ssh remote bash -c 'ls file-* | xargs -I {} echo "Got this: {}"'
。)
答案1
使用时在远程主机上执行的命令ssh remote bash -c 'ls file-*'
是
bash -c ls file-*
这意味着bash -c
执行脚本ls
。作为位置参数,bash -c
脚本获取远程主机上匹配的名称file-*
(这些名称中的第一个将被放入 中$0
,因此它实际上并不是位置参数的一部分)。参数不会传递给ls
命令,因此会列出目录中的所有名称。
ssh
在远程主机上传递要执行的命令,并删除一级引号(在命令行上使用的外部引号集)。您调用的 shellssh
会删除这些引号,并且ssh
不会插入新的引号来分隔远程命令的参数(因为这可能会干扰命令使用的引号)。
如果您使用以下命令,您可以看到这一点ssh -v
:
[...]
debug1: Sending command: bash -c ls file-*
[...]
您显示的其他三个命令的工作方式相同,但只会设置$0
为字符串file-*
,而不为 shell 设置$1
、$2
等bash -c
。
您可能想要做的是引用整个命令:
ssh remote 'bash -c "ls file-*"'
在ssh -v
调试输出中,报告为
[...]
debug1: Sending command: bash -c "ls file-*"
[...]
简而言之,您必须确保作为远程命令传递的字符串是您要在本地 shell 删除引号后运行的命令。
你也可以使用
ssh remote bash -c \"ls file-\*\"
或者
ssh remote bash -c '"ls file-*"'
答案2
我认为误解是您希望 ssh 参数直接由服务器执行,但事实并非如此。当你写:
ssh remote bash -c 'ls file-*'
ssh 客户端会将bash
、-c
和ls file-*
、用空格连接起来bash -c ls file-*
并将其发送到服务器。服务器将获取该字符串,并将其作为一个参数传递给bash -c
(如果这是您的远程 shell)。这意味着您最终会在服务器中执行与以下内容等效的操作:
bash -c 'bash -c ls file-*'
strace
您可以通过在ssh 服务器上执行命令来验证这一点:
$ sudo strace -fe trace=execve -p "$(pgrep -o sshd)" |& grep bash
[pid 794417] execve("/bin/bash", ["bash", "-c", "bash -c ls files-*"], 0x560ab66915d0 /* 13 vars */) = 0
我想你想要的是:
ssh remote 'ls file-*'
以便服务器执行:
bash -c 'ls file-*'