ssh终止后,ssh命令在其他系统上意外地继续


11

我正在运行以下命令,并监视其他系统上的输出文件:

ssh $ip_address 'for n in 1 2 3 4 5; do sleep 10; echo $n >>/tmp/count; done'

如果我使用^C或通过杀死我登录的终端来终止ssh命令,我希望远程命令也会终止。但是,这不会发生:/tmp/count无论如何获取所有数字1-5,并ps -ejH显示外壳及其sleep子级继续运行。

这是预期的行为吗,并且在任何地方都有记录吗?我可以禁用它吗?通过阅读,我希望必须使用nohup显式启用这种行为,而不是将其作为默认行为。

我浏览了ssh和sshd的手册页,但没有发现明显的地方,Google向我指出了开启此行为而不是关闭它的说明。

我正在运行Red Hat Enterprise Linux 6.2,在两个系统上都具有root登录和bash shell。

Answers:


11

uther的答案告诉您分配终端,但没有说明原因。原因不是特定于ssh,而是信号生成和传播的问题。我邀请您阅读导致各种信号发送的原因是什么?以获得更多背景。

在远程主机上,有两个相关过程:

  • ssh守护程序(sshd)的实例,它将远程程序的输入和输出中继到本地终端;
  • 一个正在运行该for循环的外壳。

当外壳到达循环末端或遇到致命错误时,外壳可能自然死亡,也可能信号死亡。问题是,为什么外壳会收到信号?

如果远程外壳程序sshd通过管道连接到管道,这是在ssh命令行上指定命令时发生的情况,则如果退出并且外壳程序尝试写入管道,它将死于SIGPIPEsshd。只要外壳程序不写入管道,就不会收到SIGPIPE。在这里,shell从不向其标准输出写入任何内容,因此它可以永远存在。

您可以将-t选项传递给ssh,以告诉它在远程端模拟终端并在该终端中运行指定的命令。然后,如果SSH客户端消失,则sshd关闭连接并退出,从而破坏终端。终端设备离开时,其中运行的任何进程都会收到SIGHUP。因此,如果您杀死SSH客户端(使用kill或通过关闭客户端运行于其上的终端),则远程外壳将被SIGHUPped。

如果通过该-t选项,SSH还将中继SIGINT。如果按Ctrl+ C,则远程外壳将收到SIGINT。


如果ssh本身未分配tty,请使用-tt代替-t。如果通过诸如-f或的选项在调用时立即使ssh成为后台,则SSH将不会获得分配的tty -n
米龙五世

3

尝试使用ssh命令分配psuedo-tty 。

ssh -t $ip_address 'for n in 1 2 3 4 5; do sleep 10; echo $n >>/tmp/count; done'

当您断开ssh会话时,该过程应终止。

由于这是一个伪tty,因此您的shell初始化可能不会访问源配置文件,从而使您的命令处于非常裸露的环境中。您可能需要设置一个.ssh/environment定义环境变量(例如PATH)的文件。从man 1 ssh

Additionally, ssh reads ~/.ssh/environment, and adds lines of the format 
“VARNAME=value” to the environment if the file exists and users are allowed
to change their environment.  For more information, see the 
PermitUserEnvironment option in sshd_config(5).

2

作为使用该-t选项ssh使远程命令在ssh客户端退出或被杀死时的替代方法,当sshd关闭标准输入时,可以使用命名管道将“ EOF转换为SIGHUP” (请参见Bug 396-sshd未分配pty时,孤立进程进行处理)。

# sample code in Bash
# press ctrl-d for EOF
ssh localhost '
TUBE=/tmp/myfifo.fifo
rm -f "$TUBE"
mkfifo "$TUBE"
#exec 3<>"$TUBE"

<"$TUBE" sleep 100 &  appPID=$!

# cf. "OpenSSH and non-blocking mode", 
# http://lists.mindrot.org/pipermail/openssh-unix-dev/2005-July/023090.html
#cat >"$TUBE"
#socat -u STDIN "PIPE:$TUBE"
dd of="$TUBE" bs=1 2>/dev/null
#while IFS="" read -r -n 1 char; do printf '%s' "$char"; done > "$TUBE"

#kill -HUP -$appPID 
kill $appPID

rm -f "$TUBE"
'

1

这是我发现的最好方法。您希望服务器端的某个东西尝试读取stdin,然后在失败时终止该进程组,但是您还希望客户端的stdin阻塞,直到服务器端进程完成并且不会留下诸如<(无限睡眠)可能。

ssh localhost "sleep 99 < <(cat; kill -INT 0)" <&1

它实际上似乎没有将stdout重定向到任何地方,但它确实起到了阻塞输入的作用,并且避免了捕获击键。

相关的openssh错误:https://bugzilla.mindrot.org/show_bug.cgi id = 396#c14


0

如果要禁用该功能(看似默认行为),则需要在客户端或服务器端启用ssh-keep-alive

如果查看手册页中的ssh-keep-alive-options,可以看到默认情况下它们是禁用的。


0

我的答案是基于teru的。我~/helper在服务器server.ip上需要一个辅助脚本:

#!/bin/bash
TUBE=/tmp/sshCoLab_myfifo.$$;
mkfifo "$TUBE"
( <"$TUBE" "$@" ; rm "$TUBE" ; kill -TERM 0 ) &  
cat >"$TUBE" ;
rm "$TUBE" ;
kill -TERM 0 ;

如果它被调用例如

ssh server.ip ~/helper echo hello from server

echo hello from server在server.ip上执行,然后ssh客户端将终止。

如果它被调用例如

ssh server.ip ~/helper sleep 1000 &
CHID=$!

kill -9 $CHID也将在服务器上停止脚本。对我来说,kill -INT $CHID不起作用,但我不知道为什么。

用teru的答案来说,ssh命令将在远程命令完成后永远等待,因为cat永远不会结束。

ssh -t的所有答案都不适用于kill,但仅适用于Ctrl-C。

编辑:我发现这可以从新的Ubuntu 14.01到更老的科学linux盒-但反之则不行。因此,我认为没有通用的解决方案。奇怪的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.