use*_*310 17 ssh bash jobs tty pty
在ssh上运行命令时,我最近遇到了一些奇怪的行为.我有兴趣听到下面这种行为的任何解释.
正在运行ssh localhost 'touch foobar &'创建一个名为expect的文件foobar:
[bob@server ~]$ ssh localhost 'touch foobar &'
[bob@server ~]$ ls foobar
foobar
Run Code Online (Sandbox Code Playgroud)
但是,运行相同的命令但使用-t强制伪tty分配选项无法创建foobar:
[bob@server ~]$ ssh -t localhost 'touch foobar &'
Connection to localhost closed.
[bob@server ~]$ echo $?
0
[bob@server ~]$ ls foobar
ls: cannot access foobar: No such file or directory
Run Code Online (Sandbox Code Playgroud)
我目前的理论是,由于触摸过程正在被覆盖,因此伪进程在进程有机会运行之前被分配和未分配.当然,添加一秒睡眠可让触摸按预期运行:
[bob@pidora ~]$ ssh -t localhost 'touch foobar & sleep 1'
Connection to localhost closed.
[bob@pidora ~]$ ls foobar
foobar
Run Code Online (Sandbox Code Playgroud)
如果有人有明确的解释,我会非常有兴趣听到它.谢谢.
Fil*_*ves 37
哦,那是一个很好的.
这与进程组的工作方式,bash在作为非交互式shell调用时的行为方式-c以及&输入命令的效果有关.
答案假设您熟悉UNIX中作业控制的工作原理; 如果你不是,这里有一个高层次的看法:每个进程属于一个进程组(在同一组的过程通常放在那里作为一个命令管道的一部分,例如cat file | sort | grep 'word'将放置进程在运行cat(1),sort(1)并且grep(1)同样的过程组).bash是一个像任何其他过程一样的过程,它也属于一个进程组.进程组是会话的一部分(会话由一个或多个进程组组成).在会话中,最多有一个进程组,称为前台进程组,可能还有许多后台进程组.前台进程组控制终端(如果有一个连接到会话的控制终端); 会话负责人(bash)将进程从后台移动到前台,从前台移动到后台tcsetpgrp(3).发送到进程组的信号将传递到该组中的每个进程.
如果过程组和工作控制的概念对您来说是全新的,我认为您需要阅读以完全理解这个答案.学习这个的一个很好的资源是UNIX环境(第3版)中高级编程的第9章.
话虽这么说,让我们看看这里发生了什么.我们必须将拼图的每一部分组合在一起.
在两种情况下,SSH远程侧调用bash(1)用-c.该-c标志导致bash(1)作为非交互式shell运行.从联机帮助页:
交互式shell是在没有非选项参数的情况下启动的,没有-c选项,其标准输入和错误都连接到终端(由isatty(3)确定),或者以-i选项开头.如果bash是交互式的,则设置PS1和$ - 包括i,允许shell脚本或启动文件测试此状态.
此外,重要的是要知道在非交互模式下启动bash时禁用作业控制.这意味着bash不会创建一个单独的进程组来运行该命令,因为禁用了作业控制,不需要在前台和后台之间移动此命令,因此它可能只是保留在与bash相同的进程组中.无论你是否强制在ssh上进行PTY分配,都会发生这种情况-t.
但是,使用会产生&副作用,导致shell不等待命令终止(即使禁用了作业控制).从联机帮助页:
如果命令由控制操作符&终止,则shell在子shell中在后台执行命令.shell不等待命令完成,返回状态为0.命令由a分隔; 按顺序执行; shell等待每个命令依次终止.返回状态是最后执行的命令的退出状态.
因此,在这两种情况下,bash都不会等待命令执行,并且touch(1)将在与之相同的进程组中执行bash(1).
现在,考虑会话领导者退出时会发生什么.从setpgid(2)联机帮助页引用:
如果会话具有控制终端,并且未设置该终端的CLOCAL标志,并且发生终端挂断,则向会话负责人发送SIGHUP.如果会话领导者退出,则SIGHUP信号也将被发送到控制终端的前台进程组中的每个进程.
(强调我的)
当你不使用 -t
当您不使用时-t,远程端没有PTY分配,因此bash不是会话负责人,实际上没有创建新会话.因为sshd作为守护进程运行,所以分叉+ exec()的bash进程将没有控制终端.因此,即使shell很快终止(可能在之前touch(1)),也没有SIGHUP发送到进程组,因为bash不是会话负责人(并且没有控制终端).一切正常.
当你使用 -t
-t强制PTY分配,这意味着ssh远程端将调用setsid(2),分配一个伪终端+ fork一个新进程forkpty(3),将PTY主设备输入和输出连接到通向你的机器的套接字端点,最后执行bash(1).forkpty(3)在forked进程中打开PTY slave端,这将成为bash; 由于当前会话没有控制终端,并且正在打开终端设备,因此PTY设备成为会话的控制终端,bash成为会话负责人.
然后同样的事情再次发生:touch(1)在同一个进程组等中执行,yadda yadda.问题的关键是,这时候,有是会话组长和控制终端.因此,由于bash不会等待,因为&当它退出时,会SIGHUP被传递给进程组并touch(1)过早死亡.
关于 nohup
nohup(1)在这里不起作用,因为仍然存在竞争条件.如果bash(1)之前终止nohup(1)有机会设置必要的信号处理和文件重定向,它将没有任何影响(这可能发生了什么)
可能的解决办法
强制重新启用作业控制可以修复它.在bash中,你这样做set -m.这有效:
ssh -t localhost 'set -m ; touch foobar &'
Run Code Online (Sandbox Code Playgroud)
或者强制bash等待touch(1)完成:
ssh -t localhost 'touch foobar & wait `pgrep touch`'
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3557 次 |
| 最近记录: |