通过 ssh 在主机上面执行命令时,遇到 ssh 连接不会自动断开,在程序结束时才断开的情况。如何才能让 ssh 执行完命令后自动退出?以及这种问题该如何分析?背后的原理又是什么?
现象
一般情况下,执行简单的命令,会在命令执行完成后,立刻返回;若执行的程序堵塞,ssh 会一直不返回,直到堵塞的程序自己结束,才会断开 ssh 连接。
1 2 3
| ssh devcloud pwd ssh devcloud sleep 3s ssh devcloud java -jar balabala.jar
|
效果如下:
同时还可以发现平时常用的 nohup + &
没有作用:
1 2
| ssh devcloud "nohup sleep 3s &" ssh devcloud "nohup java -jar balabala.jar &"
|
正确的姿势应该是
1 2
| ssh devcloud "sleep 3s >/dev/null &" ssh devcloud "java -jar balabala.jar >/dev/null 2>&1 &"
|
且调换 >/dev/null
与 2>&1
的顺序,会导致 ssh 命令不会退出。
1 2
| ssh devcloud "sleep 3s 2>&1 >/dev/null &" ssh devcloud "java -jar balabala.jar 2>&1 >/dev/null &"
|
ssh 执行远程命令的过程
一般通过 ssh 远程执行某个命令时,会在远程机器上先建立一个 sshd 的子进程(父进程是 sshd daemon),然后由这个 sshd 进程启动一个 bash 进程(取决于该用户所设置的默认 shell)来执行传递过来的命令。
1 2 3 4 5 6 7 8 9 10
| $ ssh devcloud "ps -ef | grep -v grep | grep sshd" root 9425 1 0 2020 ? 00:00:07 /usr/sbin/sshd -D root 10130 9425 0 13:49 ? 00:00:00 sshd: root@notty root 10633 9425 0 13:51 ? 00:00:00 sshd: root@notty $ ssh devcloud "ps -ef | grep -v grep | grep sleep" root 10132 10130 0 13:49 ? 00:00:00 sleep 1000s root 10559 8630 0 13:51 ? 00:00:00 sleep 60 $ ssh devcloud "pstree -p 9425" sshd(9425)-+-sshd(10130)---sleep(10132) `-sshd(10642)---pstree(10645)
|
针对这次任务建立的 sshd 进程和 bash 进程在文件描述符方面有一定关系:bash 进程的 0
、1
、2
三个文件描述符通过管道与 sshd 的相应文件描述符联系起来。可以通过如下的脚本来查看上述对应关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| $ ssh devcloud "TMPSPID=\$(ps -ef | grep -v grep | grep -e 'sshd.*notty' | awk '{print \$2}');echo SSHD子进程:\$TMPSPID;ps -ef | grep -v grep | grep sshd;echo ;ls -l /proc/\$TMPSPID/fd;echo ;echo SHELL 进程:\$\$;ps -ef | grep \$\$;echo ;ls -l /proc/\$\$/fd" SSHD子进程:688 root 688 9425 0 15:49 ? 00:00:00 sshd: root@notty root 9425 1 0 2020 ? 00:00:07 /usr/sbin/sshd -D
总用量 0 lrwx------ 1 root root 64 4月 29 15:49 0 -> /dev/null lrwx------ 1 root root 64 4月 29 15:49 1 -> /dev/null l-wx------ 1 root root 64 4月 29 15:49 11 -> pipe:[2517193862] lr-x------ 1 root root 64 4月 29 15:49 12 -> pipe:[2517193863] lr-x------ 1 root root 64 4月 29 15:49 14 -> pipe:[2517193864] lrwx------ 1 root root 64 4月 29 15:49 2 -> /dev/null lrwx------ 1 root root 64 4月 29 15:49 3 -> socket:[2517182996] lrwx------ 1 root root 64 4月 29 15:49 4 -> socket:[2517189863] lr-x------ 1 root root 64 4月 29 15:49 5 -> pipe:[2517189866] l-wx------ 1 root root 64 4月 29 15:49 6 -> /run/systemd/sessions/360922.ref l-wx------ 1 root root 64 4月 29 15:49 7 -> pipe:[2517189866]
SHELL进程:690 root 690 688 0 15:49 ? 00:00:00 zsh -c TMPSPID=$(ps -ef | grep -v grep | grep -e 'sshd.*notty' | awk '{print $2}');echo SSHD子?程:$TMPSPID;ps -ef | grep -v grep | grep sshd;echo ;ls -l /proc/$TMPSPID/fd;echo ;echo SHELL?程:$$;ps -ef | grep $$;echo ;ls -l /proc/$$/fd root 699 690 0 15:49 ? 00:00:00 ps -ef root 700 690 0 15:49 ? 00:00:00 grep 690 root 26903 1 0 2020 ? 01:18:33 /usr/local/sa/agent/plugins/sap1015
总用量 0 lr-x------ 1 root root 64 4月 29 15:49 0 -> pipe:[2517193862] l-wx------ 1 root root 64 4月 29 15:49 1 -> pipe:[2517193863] l-wx------ 1 root root 64 4月 29 15:49 2 -> pipe:[2517193864] lr-x------ 1 root root 64 4月 29 15:49 3 -> /proc/690/fd
|
可以看出 shell 进程的 0
、1
、2
文件描述符均来自 sshd 进程的管道。
如果远程执行的命令是后台执行,那么新启动的命令的父进程成了 1
,而输入即描述符 0
重定向到了 /dev/null
。
nohup 的误解
可以从 man nohup
中看到这个命令说明。其中它的作用是将命令运行成不受 SIGHUP
信号的模式;它主要做的事为如下 3 点:
- 若标准输入是终端,将它重定向到一个不可达的文件(
/dev/null
)
- 若标准输出是终端,将它重定向到
nohup.out
文件。若当前目录的 nohup.out
不可用,则使用 ${HOME}/nohup.out
文件。
- 若标准错误是终端,将它重定向到标准输出。
也就是说,如果标准输入不是终端的话,那么 nohup
将不会发挥出作用。因此,在上述上下文里面,nohup
是可以不要的。
SIGHUP
信号是当终端关闭时,发送给该终端所控制的进程。当进程收到这个信号后,默认操作为终止进程;但是也可以对此信号做捕捉,比如 wget
能捕获SIGHUP
信号,并忽略它,这样就算退出了 Linux 登录,wget
也 能继续下载。
那 ssh 执行命令,能获取到终端吗?
先来一个简单的表述:
- TTY。TeleTypeWriter,通过 console 登录。
- pts。pseudo terminal slave,通过 ssh 登录。
- notty。通过 SFTP 或其他不需要终端的方式登录。
通过 ssh 执行命令获取到的是 notty。
终端和我们想象中的终端可能有一些差别,内容也非常长,可以参考下面的文章进行详细的了解:
参考: