我有这个:
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 之前手动清理 ( ) 是个好主意。