ssh 命令只是退出会话

ssh 命令只是退出会话

我有这个:

ssh -i "alex-kp.pem" '[email protected]' '

  cd codes/vbe
  eval $(ssh-agent)
  ssh-add -D
  ssh-add ~/.ssh/id_vbe
'

它执行命令,但随后也退出会话,因此我返回到本地计算机。有人知道为什么吗?

答案1

您已经提供了要在远程系统上执行的命令(好吧,一系列命令)。一旦该命令(这些命令)完成,会话就会退出。这是已定义的并且应该是预期的行为 - 请参阅man ssh

如果指定了命令,则会在远程主机上执行该命令,而不是在登录 shell 上执行。

hostname一个更简单的示例,在远程系统上执行

ssh -i alex-kp.pem [email protected] hostname

答案2

远程端发生了什么

Localssh user@server 'shell_code'使 SSH 服务器运行如下:

"$SHELL" -c 'shell_code'

我并不是说它是确切地像这样,但它肯定足够接近以了解发生了什么。我把它写得好像在 shell 中调用它一样,所以看起来很熟悉;但实际上还没有shell。

以这种方式调用的 shell 在到达shell_code.在您的情况下,代码是多行的,这不会改变任何内容。

如果没有 shell 代码,例如在 plain 上ssh user@server,SSH 服务器会启动不同的东西:

"$SHELL" -i

再说一遍,我并不是说它是确切地像这样。

以这种方式调用的 shell 是交互的shell 打印提示并等待输入。看来这就是您想要实现的行为。

SSH 服务器故意使用一种形式或另一种形式,具体取决于本地调用的形式ssh。后一种形式非常适合交互式使用,前一种形式同样可以远程运行一些非交互式 shell 代码,就像在本地一样。例如在本地你可以这样做:

<data md5sum

但如果本地缺少md5sum,则可以使用远程:

<data ssh user@server md5sum

就像本地md5sum在完成其工作后退出一样,ssh … md5sum在远程md5sum完成其工作后也退出。很有用。

或者您可以通过管道传输到远程文件:

<data ssh user@server 'cat >copy'

并且cat,远程 shell 和本地 shellssh将在适当的时候自动退出。

或者您可以询问服务器时间:

ssh user@server date

它的行为就像 local 一样date,它不会将您置于交互式远程 shell 中。


当交互式 shell 自动退出时

我预计它 [ ssh … date] 默认情况下不会退出。当您在终端中键入命令时,它会默认退出吗?

确实如此。交互式 shell 在读取命令时到达 EOF 时会退出。问题是,在您按下Ctrl+ d(之前,它不会从终端“体验”EOF一次或两次)。这就是它在终端上的工作方式;但是如果你让一个交互式 shell 从常规文件或管道中读取,那么它会在你期望的时候准确地看到 EOF:

{ echo date; echo sleep 5; echo date; } | bash -i

然后它就会退出。

因此,当确实无事可做时,每个 shell 都会默认退出。据称,交互式 shell“不会退出”,因为从终端读取数据是故意地设计为在当前没有输入时不触发 EOF,假设输入可能随时出现。它与终端有关,与 shell 或任何一般程序无关;我的意思是,例如退出,但从tty 进行</etc/fstab cat普通读取可以无限期地工作,这是同一个故事。cat


远程tty的重要性

SSH 服务器运行 shell 的两种方式不仅在形式上有所不同。当远程交互式命令使用本地的 tty 时效果很好(所以它是服务器端的 tty,位于本地ssh和命令之间);但是md5sum当没有分配 tty 时,远程非交互式命令(如我们的示例)可以很好地工作。 SSH 服务器分配或不分配 tty,并且它会自动分配(但ssh如果需要,可以通过选项显式选择:-t, -tt, -T)。我认为您可以从我的以下问题(和答案)中获得一些见解,我试图同时从这两个问题中获得最好的结果:ssh 具有单独的 stdin、stdout、stderr 和 tty


你可以做什么

看起来您想要获得一个远程交互式 shell,它在给您第一个提示之前执行一些 shell 代码。最简单的方法是将代码放入正确的启动脚本中(~/.bashrc如果 shell 是 Bash)并且仅ssh(不传递任何 shell 代码)到交互式远程 shell。这是一种永久性的解决方案,如果您希望能够按需运行一次性代码并仍然获得提示,那么它就没用。

要按需执行 shell 代码,您需要这样的装置:

ssh -t … '
  exec bash --rcfile <(cat <<"EOF"
    # your custom shell code here
    . ~/.bashrc
    # and/or here
EOF
) -i'

这假设 SSH 服务器启动的 shell 能够理解<(…)(Bash 能够理解),并且您想要作为最终交互式 shell 的 shell 是 Bash。

SSH 服务器将运行非交互式 shell,仍然带有 tty(因为-t)。该 shell 将exec使用交互式 Bash,该 Bash 将获取自定义文件而不是~/.bashrc.该文件将是来自进程替换的管道,其内容将是您的自定义 shell 代码加上源指令~/.bashrc。这样,您将使最终 shell 执行您的自定义 shell 代码,然后给出提示。

请注意,由 SSH 服务器直接生成的交互式 shell 将是一个登录 shell,它将来源/etc/profile.这里两个 shell 都不是登录 shell。我们可以使用 运行最终的 shell -l,但它会忽略--rcfile

然而,你的具体的自定义 shell 代码设置了可以生存的东西exec(它们通常可以被继承)。这意味着我们可以将代码片段简化为:

ssh -t … '
  cd codes/vbe
  eval $(ssh-agent)
  ssh-add -D
  ssh-add ~/.ssh/id_vbe
  exec bash -i
'

我认为在这种情况下,-l如果您需要的话,您可以添加并使最终的 Bash 成为登录 shell。请记住,登录和非登录 shell 会获取不同的文件;这些文件可以相互来源(例如~/.bash_profile可以来源~/.bashrc);即使是非交互式 Bash 也可能会源~/.bashrc;并且有两个按顺序排列的远程 shell,每个都可以获取某些内容。遥控器很可能~/.bashrc会被采购两次。根据其内容,可能会出现副作用。

ssh …一个非常不同的替代方案是从本地脚本调用(无需 shell 代码) expect。该脚本应该注入所需的 shell 代码并最终让您interact.下面的快速而肮脏的示例假设您的远程提示可以通过匹配来检测到@*:*$

expect -c '
 spawn ssh -i alex-kp.pem [email protected]
 expect "@*:*$ "
 send "cd codes/vbe\r"
 expect "@*:*$ "
 send "eval \$(ssh-agent)\r"
 expect "@*:*$ "
 send "ssh-add -D\r"
 expect "@*:*$ "
 send "ssh-add ~/.ssh/id_vbe\r"
 interact
'

我不了解AWS,但一般来说,在您断开连接后ssh-agent,它eval $(ssh-agent)可能会或可能不会生存。如果它要生存,那么ssh-agent -k在退出远程 shell 之前手动清理 ( ) 是个好主意。

相关内容