我正在考虑通过 ssh 将环境变量传递给命令:
$ ssh HOST A=1 set | grep ^A
$ ssh HOST A=1 sh -c set | grep ^A
A='1'
$ ssh HOST A=1 echo '$A'
$ ssh HOST A=1 sh -c 'echo $A'
因此,set
看不到环境变量,但它在单独的进程中看到。似乎没有发生同样的情况echo
。
有没有办法找出到底发送了什么,以及它是如何执行的(sshd
启动哪个命令)?
答案1
当您运行 时ssh user@host command
,由 扩展产生的标记command
将通过中间的空格连接起来ssh
并作为字符串发送到远程主机。然后,远程主机执行远程用户的默认 shell 的一个实例(在 中指定的实例/etc/passwd
),将从客户端接收到的字符串作为参数传递给 shell 的-c
选项(下面将详细介绍)。
因此,例如,在
$ foo="bar baz"
$ ssh user@host echo "$foo"
bar baz
本地ssh
接收两个参数echo
和bar baz
。它将它们连接起来形成字符串echo bar baz
并将其发送到服务器。服务器按照以下方式执行某些操作/bin/sh -c 'echo bar baz'
,并且该命令的输出打印在本地ssh
的标准输出上。
如何查看客户端实际发送到服务器的内容?
作为卡米尔·马乔罗夫斯基指出在问题的评论中,ssh -v
将发送到服务器的命令字符串作为调试消息输出:
$ ssh -v host A=1 sh -c set | grep ^A
...
debug1: Sending command: A=1 sh -c set
...
您如何才能看到sshd
实际执行的内容?
我不知道有什么方法可以请求服务器向客户端显示其自身操作的某种跟踪。但是,出于测试目的,您可以使用strace
守护程序的实例:
$ sudo strace -e execve -f /usr/bin/sshd -p 2222
这特别有用,因为在编写用于远程执行的命令行时很容易无法正确引用。举个例子(您问题中提供的示例的变体):
$ ssh -p 2222 host bash -c 'A=1 set'
# Prints nothing
strace
揭示了我们所看到的命令字符串实际上是在远程主机上分割的,它首先在用户的默认 shell 中运行完整的命令行(当本地 shell 构建参数列表时,单引号已被删除ssh
):
... execve("/bin/bash", ["bash", "-c", "bash -c A=1 set"] ...
进而:
... execve("/usr/bin/bash", ["bash", "-c", "A=1", "set"] ...
请注意命令字符串( 的参数-c
)恰好是A=1
。作为第零个参数set
传递bash
(可用作$0
并用作命令名称)。
如何查看远程shell实际执行了什么?
再说一遍,我不知道有什么简单的方法。在简单的情况下,shell 选项的使用xtrace
很简单。例如,ssh host A=1 set
可能会变成:
$ command=$(printf '%s ' A=1 set); printf '%s\n' "${command% }" |
ssh host sh -x
sh
应替换为远程用户的默认 shell。
该命令通过管道传输到ssh
的标准输入,因为这种形式允许不加更改地键入它(ssh host sh -xc A=1 set
由于上面所示的原因而不起作用,并且添加引号在某种程度上会破坏本练习的真正目的)。
在不太简单的情况下,特别是当ssh
您正在研究的调用已经使用标准输入时,您可以强制远程主机在xtrace
打开的情况下运行 shell。出于实验目的,并明确错误可能会使您无法登录到远程系统,一种(相当粗略的)方法可能是替换 中远程用户的默认 shell /etc/passwd
。假设它是/bin/sh
,将其更改为/home/your_user/bin/sh
并将后者创建为可执行脚本:
#!/bin/sh
exec /bin/sh -x "$@"
这将使 shell 向您显示正在执行的内容的踪迹:
$ ssh host A=1 echo foo
foo
+ A=1
+ echo foo
最后,关于您的示例命令:
ssh HOST A=1 set
不打印的原因A=1
可能是您的用户的默认 shell 是 Bash(可能作为 调用sh
)。与dash
、ksh
、yash
、中发生的情况相反zsh
,set
在 Bash (至少是我拥有的版本5.0.17
)中,不输出变量集对于set
命令本身。另一方面,您可能可以确认:$ ssh host 'A=1 > set' | grep ^A A=1
如果在调用之前声明了 Ie,则它
A
会显示在set
的输出中set
。在 中
A=1 sh -c set
,A
被添加到 的环境中sh
,因此毫不奇怪地由 打印set
。A=1 echo '$A'
不打印 的值,A
因为仅在 扩展后A
添加到执行环境(假设它是内置的) 。将其与打印的进行比较。echo
$A
ssh host 'A=1; echo $A'
1
A=1 sh -c 'echo $A'
不打印任何内容,因为如上所示,远程主机使用选项-c
和A=1 sh -c echo $A
作为参数执行远程用户的默认 shell,然后该 shell 运行sh -c echo $A
:命令字符串最终只是echo
,没有参数。为了使其工作,您需要确保周围的引号
echo $A
发送到远程主机和$A
防止过早扩展(通过本地 shell 或“外部”远程 shell):例如ssh host A=1 sh -c '"echo \$A"'
或ssh host A=1 sh -c "'echo \$A'"
。当显式调用远程 shell 时,让它从标准输入读取命令可能更容易:
$ echo 'echo $A' | ssh host A=1 sh -s 1