less 停止我的脚本;为什么会这样以及如何避免?

less 停止我的脚本;为什么会这样以及如何避免?

s我在当前目录中有一个名为的 Bash 脚本:

#!/bin/bash
pipe_test() {
    ( set -m; (
        $1
    ); set +m ) | 
    (
        $2
    )
}
pipe_test "$1" "$2"

如果我打电话给例如

./s yes less

脚本被停止。 (如果我使用我尝试过的任何其他寻呼机而不是lessiemore和,也会发生类似的情况most。)不过,我可以通过fg内置继续它。

我希望set -m子 shell 具有作业控制(由 启用),以便为子 shell 的进程提供不同的进程组 ID。

有关我的系统的信息:

$ bashbug
...
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS: -g -O2 -fdebug-prefix-map=/build/bash-cP61jF/bash-5.0=. -fstack-protector-strong -Wformat -Werror=format->
uname output: Linux jarnos-OptiPlex-745 5.4.0-29-generic #33-Ubuntu SMP Wed Apr 29 14:32:27 UTC 2020 x86_64 x86_64 x86_64 GNU>
Machine Type: x86_64-pc-linux-gnu

Bash Version: 5.0
Patch Level: 16
Release Status: release
$ less --version
less version: 551

答案1

发生这种情况的原因是因为启用作业控制 ( set -m) 不仅带来了进程分组,还带来了处理“前台”和“后台”作业的机制。这种“机制”意味着在启用作业控制时依次运行的每个命令都会成为前台进程组。

因此,简而言之,当该子 shell(管道的左侧部分)启用作业控制时,它实际上会从整个管道中窃取终端,直到那时为止,该管道一直拥有该终端,并且在您的示例中包括该less进程,从而使其成为成为后台,因此不再允许使用该终端。因此它会被停止,因为less 它会继续访问终端。

通过发出,fg您将终端返回到整个管道,因此返回到less,一切都结束了。除非您在作业控制子 shell 中运行附加命令,否则每个附加命令都会再次窃取终端。

解决这个问题的一种方法是简单地在后台运行作业控制的子 shell:

( set -m; (
        $1
    ) & set +m ) | 
    (
        $2
    )

您将根据需要在其不同的进程组中使用 run 表示的命令$1,而后台模式可防止窃取终端,从而将其留给管道,从而将其留给$2.

当然,这要求 in 命令$1不想读取终端本身,否则一旦尝试执行此操作,它将被停止。

另外,与我上面所说的类似,您可能想要添加的任何其他作业控制的子子 shell 都需要相同的“背景”处理,直到您添加为止set +m,否则每个附加的作业控制子子 shell 都会再次窃取终端。

也就是说,如果您需要进程分组的目的只是杀死进程,那么您可能会考虑使用进程分组pkill来定位它们。例如pkill -P将向其进程发送信号家长是指示的PID。这样,您只需知道子进程的 PID,就可以定位子进程的所有子进程(但不是孙子进程)。

答案2

删除它set -m可以解决问题(无论如何要做什么?)。

内核通过以下方式停止三个进程SIGTTOU

  • 脚本过程
  • 一个子shell
  • less

但不是yes。其进程被放入一个单独的进程组;可能由set -m.因此,内核尝试访问该管道中的所有进程,但错过了一个。不过,这种缺失并不是“已停止”消息的原因。

通常SIGTTOU是由后台进程尝试写入终端引起的。但这并不是唯一可能的原因:

int SIGTTOU
这与 SIGTTIN 类似,但当后台作业中的进程尝试写入终端或设置其模式时生成。同样,默认操作是停止该进程。如果设置了 TOSTOP 输出模式,则仅在尝试写入终端时生成 SIGTTOU;请参阅输出模式。

https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html

之前的最后一个系统调用是(通过less):

ioctl(3, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig -icanon -echo ...}) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)

所以我的评估是,由于某种奇怪的原因(即set -m)管道被置于后台。有几个系统调用,例如

ioctl(255, TIOCSPGRP, [23715]

通过不同的过程。最后一个是通过子shell

ioctl(2, TIOCSPGRP, [23718]) = 0

yes在使其成为自己的进程组的领导者(没有其他成员)后,使其成为前台进程组

setpgid(23718, 23718 <unfinished ...>

相关内容