gpt4 book ai didi

bash - 如果分配了伪tty,为什么通过ssh运行后台任务失败?

转载 作者:行者123 更新时间:2023-11-29 08:46:03 24 4
gpt4 key购买 nike

我最近在ssh上运行命令时遇到了一些有点奇怪的行为。我想听听以下行为的任何解释。

运行ssh localhost 'touch foobar &'会按预期创建一个名为foobar的文件:

[bob@server ~]$ ssh localhost 'touch foobar &'
[bob@server ~]$ ls foobar
foobar

但是,运行相同的命令但使用 -t选项强制伪伪分配无法创建 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

我当前的理论是,由于触摸过程正在后台运行,因此伪tty在过程有机会运行之前就已分配和未分配。当然,增加一秒钟的睡眠可使触摸按预期运行:
[bob@pidora ~]$ ssh -t localhost 'touch foobar & sleep 1'
Connection to localhost closed.
[bob@pidora ~]$ ls foobar
foobar

如果有人有明确的解释,我将非常有兴趣听到它。谢谢。

最佳答案

哦,那是一个好人。

这与进程组的工作方式,使用-c作为非交互式shell调用bash时的行为以及输入命令中&的效果有关。

答案是假设您熟悉UNIX中的作业控制如何工作。如果不是这样,那么这是一个高级 View :每个进程都属于一个进程组(同一组中的进程通常作为命令管道的一部分放置在该组中,例如cat file | sort | grep 'word'会将运行cat(1)sort(1)grep(1)的进程放在同一进程组)。 bash是一个与其他进程一样的进程,并且它也属于某个进程组。进程组是 session 的一部分(一个 session 由一个或多个进程组组成)。在一个 session 中,最多有一个进程组,称为前台进程组,可能还有许多后台进程组。前台进程组拥有对终端的控制权(如果 session 中连接了控制终端); session 负责人(bash)使用tcsetpgrp(3)将进程从后台移动到前台,并从前台移动到后台。发送到进程组的信号将传递到该组中的每个进程。

如果您对流程组和作业控制的概念完全陌生,我认为您需要仔细阅读一下才能完全理解此答案。 UNIX环境中高级编程(第3版)的第9章是了解这一点的重要资源。

话虽如此,让我们看看这里发生了什么。我们必须把难题的每一部分都放在一起。

在这两种情况下,ssh远程端都使用bash(1)调用-c-c标志使bash(1)作为非交互式shell运行。从联机帮助页:

An interactive shell is one started without non-option arguments and without the -c option whose standard input and error are both connected to terminals (as determined by isatty(3)), or one started with the -i option. PS1 is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.



同样,重要的是要知道当以非交互模式启动bash时, 作业控制已禁用。这意味着bash不会创建单独的进程组来运行该命令,因为已禁用作业控制,因此无需在前台和后台之间移动此命令,因此它可能也和bash一样留在同一进程组中。无论您是否使用-t在ssh上强制分配PTY,都会发生这种情况。

但是,&的使用具有使 shell 程序不等待命令终止的副作用(即使禁用了作业控制)。从联机帮助页:

If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0. Commands separated by a ; are executed sequentially; the shell waits for each command to terminate in turn. The return status is the exit status of the last command executed.



因此,在两种情况下,bash都不会等待命令执行,并且touch(1)将与bash(1)在同一进程组中执行。

现在,请考虑当 session 负责人退出时会发生什么。引用setpgid(2)联机帮助页:

If a session has a controlling terminal, and the CLOCAL flag for that terminal is not set, and a terminal hangup occurs, then the session leader is sent a SIGHUP. If the session leader exits, then a SIGHUP signal will also be sent to each process in the foreground process group of the controlling terminal.



(强调我的)

不使用-t

当您不使用-t时,在远程端没有PTY分配,因此bash并不是 session 领导者,并且实际上没有创建新的 session 。因为sshd作为守护程序运行,所以 fork + exec()的bash进程将没有控制终端。这样,即使shell终止非常快(可能在touch(1)之前),也不会将SIGHUP发送到进程组,因为bash不是 session 领导者(并且没有控制终端)。所以一切正常。

使用-t
-t强制进行PTY分配,这意味着ssh远程将调用setsid(2),分配一个伪终端+用forkpty(3)分配一个新进程,将PTY主设备的输入和输出连接到通向您计算机的套接字端点,最后执行bash(1)forkpty(3)在派生进程中打开PTY从属端,该分支将成为bash;由于当前 session 没有控制终端,并且终端设备正在打开,因此PTY设备成为该 session 的控制终端,bash成为 session 领导者。

然后,同样的事情再次发生:touch(1)在同一个进程组中执行,例如yadda yadda。关键是这次是,有一个 session 负责人和一个控制终端。因此,由于bash不会因为&而烦恼等待,因此退出时,SIGHUP会传递到进程组,并且touch(1)会过早死亡。

关于nohup
nohup(1)在这里不起作用,因为仍然存在竞争条件。如果bash(1)nohup(1)有机会设置必要的信号处理和文件重定向之前终止,则它将无效(这可能会发生)

可能的修复方法

强制重新启用作业控制即可解决该问题。在bash中,您可以使用set -m做到这一点。这有效:
ssh -t localhost 'set -m ; touch foobar &'

或强制bash等待touch(1)完成:
ssh -t localhost 'touch foobar & wait `pgrep touch`'

关于bash - 如果分配了伪tty,为什么通过ssh运行后台任务失败?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32384148/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com