不同 ssh 调用之间的区别

不同 ssh 调用之间的区别

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,否则不会。-tRequestTTY yesssh_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 不会获取任何资源。因此,某些启动脚本会被获取,或者不会被获取。

根据这些脚本的功能,您可能会遇到以下差异:

  1. 不同的环境。

    ./run-server.sh或者服务器本身可能依赖于 中定义的某些环境变量profile。特别是,以PATH这种方式定义很常见。更多:这是定义 的正确方法PATH。如果不获取启动文件,您将使用默认的PATH。在您的情况下,服务器可能已尝试运行默认 中不可用的某些组件PATH

  2. 不同的工作目录。

    在启动脚本中,可以cd转到另一个目录。实际上,两次调用ssh将从不同的目录开始运行相同的代码。您的代码调用的cd some/directory路径不是绝对路径,而是./run-server.sh相对路径。如果这是问题所在,您可能会收到一条错误消息,提示some/directory和/或./run-server.sh不存在。但一般来说,some/directory/run-server.sh可能存在于两个不同的目录中。您可以运行两个不同的run-server.sh脚本,其中一个可能成功,另一个可能很快失败。

  3. 资源是否可用。

    在启动脚本中,可以挂载/解密/准备一些资源。一般来说,这不是正确的做法(使用 systemd 的正确方法这里),但我见过各种各样的装置。如果run-server.sh服务器本身依赖于此类资源,则在启动脚本未被解析的情况下,它可能会失败。

  4. 不同的外壳。

    在启动脚本中,可以运行 (exec到) 不同的 shell。更改登录 shell 的正确方法是使用chsh;但如果所需的 shell 不在 中,它将失败/etc/shells。您可以尝试通过启动脚本从不需要的登录 shell 运行所需的 shell 来规避它(如果新 shell 要解析相同的脚本,则使用一些逻辑来避免递归)。实际上, 的两个调用ssh可能从不同的 shell 运行./run-server.sh。如果 中有正确的 shebang,run-server.sh那么解释脚本应该没有区别。没有 shebang解释器依赖于调用 shell. 当脚本被错误的 shell 解释时,可能会出现错误行为。

  5. 还有别的吗?(意思是:这个列表可能不详尽)。


操作顺序

我想象run-server.sh启动实际的服务器。服务器将自己与原始的 stdin、stdout 和 stderr 分离;也许它会再次分叉,也许会以不同的用户身份(如果允许)。

可能的情况:

  1. ssh user@host "cd some/directory; ./run-server.sh"run-server.sh如果退出,则退出管道写入端没有任何进程打开文件描述符。如果发生这种情况,并且用户不再登录,并且剩余的过程仍然属于该用户(即服务器只是即将更改其用户 ID,或者根本不会更改它),并且logind.confKillUserProcesses=yes,那么服务器就会被杀死。

    当您手动运行时./run-server.sh,您将保持登录状态。服务器直到您注销时才会被终止,或者它在您注销之前设法更改其用户 ID,因此它完全不受影响KillUserProcesses=yes

  2. 另一种可能性是允许服务器ssh退出、存活,然后才根据登录的用户请求由 systemd 单元管理的一些基本资源。当您./run-server.sh手动运行时,您将保持登录状态并且服务器设法及时获取资源。

  3. 其他(含义:该列表可能不详尽)。


本地或远程扩展、引用和转义

有问题的命令是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-Cexit

如果你希望run-server.sh使用单行命令保持脚本正常运行,则需要研究如何从脚本中创建守护进程,例如关于基于 Debian 的发行版的答案

相关内容