ssh host cmd
运行ssh host
和在 shell 中运行命令之间有什么区别?
我尝试启动运行以下命令的服务器:
ssh user@host "cd some/directory; ./run-server.sh"
服务器启动后突然死机。但是,当我通过运行以下命令启动命令时:
ssh user@host
cd some/directory
./run-server.sh
从 shell 内部,服务器启动没有问题。怎么可能呢?我对 ssh 的理解是,这两个调用是无法区分的。
答案1
初步说明
两者之间几乎没有什么区别。至于哪个是相关的,这取决于
./run-server.sh
您的登录 shell、启动脚本、操作系统配置。后面的段落将对此进行详细说明。您没有透露任何有关 的信息
./run-server.sh
。我不知道它是否与用户交互,如果它分叉,它实际上使用什么解释器 (shell)。这个答案是一般性的。在这个答案中,“服务器”一词的意思是“实际提供某些服务的软件(运行
run-server.sh
)”而不是“机器”。
伪终端分配
ssh user@host
如果客户端有伪终端,则会请求伪终端。我假设您的客户端有伪终端。除非您在命令行中使用(或或类似中的)ssh user@host ./run-server.sh
,否则不会。-t
RequestTTY yes
ssh_config
我期望run-server.sh
服务器本身和它们都不需要 tty。或者,如果它们中的任何一个需要并且没有获得 tty,它应该打印一条错误消息。但一般来说,它们可能默默地失败。
如果run-server.sh
服务器在工作时(即后一种情况)完全无需与用户交互即可完成工作,则它们可能不需要 tty。另一方面,如果其中任何一个打印交互式(文本)菜单,则可能需要一个 tty。如果其中任何一个要求输入密码,则可能需要 tty。
启动脚本
ssh user@host
运行交互式登录 shell。它是/etc/passwd
为用户指定的 shell。ssh user@host ./run-server.sh
以非交互式非登录方式使用相同的 shell。
交互与否、登录与否,这取决于 shell 的行为方式。登录 shell 应该 source/etc/profile
和~/.profile
。交互 shell 可能会 source 其他文件(或另外 source 其他文件)。非交互非登录 shell 可能不会 source 任何内容。某些 shell 可能遵循比其他 shell 更复杂的规则。
合理地假设ssh user@host
远程 shell 会获取某些资源,而ssh user@host ./run-server.sh
远程 shell 不会获取任何资源。因此,某些启动脚本会被获取,或者不会被获取。
根据这些脚本的功能,您可能会遇到以下差异:
不同的环境。
./run-server.sh
或者服务器本身可能依赖于 中定义的某些环境变量profile
。特别是,以PATH
这种方式定义很常见。更多:这是定义 的正确方法PATH
。如果不获取启动文件,您将使用默认的PATH
。在您的情况下,服务器可能已尝试运行默认 中不可用的某些组件PATH
。不同的工作目录。
在启动脚本中,可以
cd
转到另一个目录。实际上,两次调用ssh
将从不同的目录开始运行相同的代码。您的代码调用的cd some/directory
路径不是绝对路径,而是./run-server.sh
相对路径。如果这是问题所在,您可能会收到一条错误消息,提示some/directory
和/或./run-server.sh
不存在。但一般来说,some/directory/run-server.sh
可能存在于两个不同的目录中。您可以运行两个不同的run-server.sh
脚本,其中一个可能成功,另一个可能很快失败。资源是否可用。
在启动脚本中,可以挂载/解密/准备一些资源。一般来说,这不是正确的做法(使用 systemd 的正确方法这里),但我见过各种各样的装置。如果
run-server.sh
服务器本身依赖于此类资源,则在启动脚本未被解析的情况下,它可能会失败。不同的外壳。
在启动脚本中,可以运行 (
exec
到) 不同的 shell。更改登录 shell 的正确方法是使用chsh
;但如果所需的 shell 不在 中,它将失败/etc/shells
。您可以尝试通过启动脚本从不需要的登录 shell 运行所需的 shell 来规避它(如果新 shell 要解析相同的脚本,则使用一些逻辑来避免递归)。实际上, 的两个调用ssh
可能从不同的 shell 运行./run-server.sh
。如果 中有正确的 shebang,run-server.sh
那么解释脚本应该没有区别。没有 shebang解释器依赖于调用 shell. 当脚本被错误的 shell 解释时,可能会出现错误行为。还有别的吗?(意思是:这个列表可能不详尽)。
操作顺序
我想象run-server.sh
启动实际的服务器。服务器将自己与原始的 stdin、stdout 和 stderr 分离;也许它会再次分叉,也许会以不同的用户身份(如果允许)。
可能的情况:
ssh user@host "cd some/directory; ./run-server.sh"
run-server.sh
如果退出,则退出管道写入端没有任何进程打开文件描述符。如果发生这种情况,并且用户不再登录,并且剩余的过程仍然属于该用户(即服务器只是即将更改其用户 ID,或者根本不会更改它),并且logind.conf
是KillUserProcesses=yes
,那么服务器就会被杀死。当您手动运行时
./run-server.sh
,您将保持登录状态。服务器直到您注销时才会被终止,或者它在您注销之前设法更改其用户 ID,因此它完全不受影响KillUserProcesses=yes
。另一种可能性是允许服务器
ssh
退出、存活,然后才根据登录的用户请求由 systemd 单元管理的一些基本资源。当您./run-server.sh
手动运行时,您将保持登录状态并且服务器设法及时获取资源。其他(含义:该列表可能不详尽)。
本地或远程扩展、引用和转义
有问题的命令是ssh user@host "cd some/directory; ./run-server.sh"
。关键字符串是双引号,没有任何内容可以扩展。一般来说,可能存在一些问题;那么它可能是罪魁祸首。假设./run-server.sh
接受一个参数,并且您想要传递 (expanded) $HOME
。然后这个
# local shell
ssh user@host "cd some/directory; ./run-server.sh '$HOME'"
将$HOME
在当地扩张(见变量扩展和引号内的引号)但是这个代码片段在远程 shell 中
# remote interactive shell
cd some/directory; ./run-server.sh '$HOME'
根本不会扩张$HOME
。另一方面,这
# local shell
ssh user@host 'cd some/directory; ./run-server.sh "$HOME"'
$HOME
将在远程 shell 中扩展;并且这
# remote interactive shell
cd some/directory; ./run-server.sh "$HOME"
效果是一样的。或者你可以尝试这样做:
# local shell
ssh user@host "cd some/directory; ./run-server.sh $HOME"
(这是有缺陷的,因为在远程 shell 的上下文中没有引用),它将$HOME
在本地扩展;或者是这样的:
#local shell
ssh user@host "cd some/directory; ./run-server.sh "$HOME""
(这更加有缺陷,因为变量在本地 shell 上下文和远程 shell 上下文中没有被引用),它将$HOME
在本地扩展;并与此进行比较:
# remote interactive shell
cd some/directory; ./run-server.sh $HOME # also flawed
# or
cd some/directory; ./run-server.sh "$HOME" # not flawed
$HOME
在远程 shell 中展开的位置。
您可以在本地退出:
# local shell
ssh user@host "cd some/directory; ./run-server.sh \"\$HOME\""
并且它将$HOME
在远程 shell 中正确引用并在那里展开。但是这个
# remote interactive shell
cd some/directory; ./run-server.sh \"\$HOME\"
不会扩展$HOME
也不会删除引号(它们将保留为 参数的一部分./run-server.sh
)。
重点是$HOME
两个系统可能有所不同;各种引用或转义方式可能会让您$HOME
以正确或错误的方式获得其中一个或另一个(或文字)。请记住,$HOME
这只是一个例子。任何变量/参数甚至其他类型的扩展(例如通配符)都会带来问题。可能只有两个 shell(本地、远程)中的一个可以扩展您选择的语法。因此,了解扩展将在什么时候发生(如果有的话)很重要。
如果经验不足或不够彻底,您可能会认为您使用了等效命令,但实际上可能并非如此。不同的输入可能会产生不同的结果。
最后说明
- 无论出于何种原因,服务器都会打印正确的错误消息。您可能没有看到任何错误消息。您可能认为服务器是一个足够好的软件,因此它会响亮地而不是默默地死去;而沉默会打扰您。但是在服务器正确地守护自身之后(如果确实如此),它不再附加到脚本的 stdin/stdout/stderr。
run-server.sh
因此,您不应该期望最重要的错误消息出现在您的屏幕上,请检查服务器的日志。
答案2
根据我自己的经验,是这两种方法之间的区别:
- 当使用单行命令时,例如
ssh user@host "cd some/directory; ./run-server.sh"
您实际上并没有触发强制(或“交互式”)登录 shell:您的命令基本上被执行,然后在最后一条指令执行后立即关闭 SSH 隧道,并将控制权交还给用户 - 使用该命令时,您将创建一个强制登录 shell,因此从那里抛出的每个命令都可以执行,直到您发送或命令
ssh user@host
为止Ctrl-C
exit
如果你希望run-server.sh
使用单行命令保持脚本正常运行,则需要研究如何从脚本中创建守护进程,例如关于基于 Debian 的发行版的答案。