web-dev-qa-db-ja.com

スクリプトを停止する回数が少なくなります。それはなぜですか、そしてどのように回避するのですか?

現在のディレクトリにsという名前のこのBashスクリプトがあります。

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

私が電話した場合、例えば.

./s yes less

スクリプトが停止します。 (lessの代わりに試した他のページャーを使用した場合も同様のことが起こります。つまりmoremostです。)fgビルトインで続行できます。でも。

ジョブコントロールを取得したい(set -m)サブシェルが、サブシェルのプロセスの個別のプロセスグループ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
jarno

これが発生する理由は、ジョブ制御(set -m)を有効にすると、プロセスのグループ化だけでなく、「フォアグラウンド」および「バックグラウンド」ジョブを処理するための機構ももたらされるためです。この「機械」は、ジョブ制御が有効になっているときに順番に実行される各コマンドがフォアグラウンドプロセスグループになることを意味します。

したがって、要するに、そのサブシェル(パイプラインの左側)がジョブ制御を有効にすると、それまでにターミナルがあり、例ではlessプロセスが含まれているパイプライン全体から、文字通りターミナルを盗みます。それはバックグラウンドになり、そのため、端末を使用できなくなります。したがって、lessが端末にアクセスし続けるため、停止します。

fgを発行することにより、ターミナルをパイプライン全体、つまりlessに戻し、すべてが正常に終了します。ジョブ制御サブシェル内で追加のコマンドを実行しない限り、そのような場合、追加の各コマンドが端末を再び盗むためです。

それを回避する1つの方法は、ジョブ制御されたサブサブシェルをバックグラウンドで実行することです。

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

$1で表されるコマンドは、個別のプロセスグループで必要に応じて実行できますが、バックグラウンドモードでは端末が盗まれないため、ターミナルがパイプライン、つまり$2に残ります。

当然、これには$1のコマンドが端末自体を読み取らないようにする必要があります。そうしないと、端末が読み取ろうとするとすぐに停止します。

また、上記と同様に、追加するジョブ制御サブサブシェルは、set +mまで、同じ「バックグラウンド」処理が必要になります。それ以外の場合は、ジョブ制御サブサブシェルが追加されます。 -サブシェルは再びターミナルを盗みます。

つまり、プロセスをグループ化する必要があるのがプロセスを強制終了することだけである場合は、pkillを使用してそれらをターゲットにすることを検討できます。たとえば、pkill -Pは、parentが指定されたPIDであるプロセスに信号を送信します。この方法では、サブプロセスのPIDを知るだけで、サブプロセスのすべての子(孫ではない)をターゲットにすることができます。

1
LL3

set -mを削除すると、問題が解決します(とにかく何をするのですか?)。

3つのプロセスがカーネルによってSIGTTOUを介して停止されます。

  • スクリプトプロセス
  • サブシェル
  • less

しかし、yesではありません。そのプロセスは別のプロセスグループに入れられます。おそらくset -mによって。そのため、カーネルはそのパイプライン内のすべてのプロセスをヒットしようとしますが、1つ失敗します。ただし、この欠落は「停止」メッセージの理由ではありません。

通常、SIGTTOUは、端末に書き込もうとするバックグラウンドプロセスによって発生します。しかし、それが唯一の考えられる理由ではありません。

int SIGTTOU
これはSIGTTINに似ていますが、バックグラウンドジョブのプロセスが端末への書き込みまたはそのモードの設定を試みたときに生成されます。この場合も、デフォルトのアクションはプロセスを停止することです。 SIGTTOUは、TOSTOP出力モードが設定されている場合に端末への書き込みを試みた場合にのみ生成されます。出力モードを参照してください。

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]

さまざまなプロセスによって。最後はサブシェルです

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

yesを自身のプロセスグループ(他のメンバーなし)のリーダーにした後、フォアグラウンドプロセスグループにする

setpgid(23718, 23718 <unfinished ...>
0
Hauke Laging