現在のプロセスを(指定された状態で)確実に終了する関数を書く方法は?

現在のプロセスを(指定された状態で)確実に終了する関数を書く方法は?

以下のスクリプトは、問題を最小限に(人為的に)説明したものです。

## demo.sh

exitfn () {
    printf -- 'exitfn PID: %d\n' "$$" >&2
    exit 1
}

printf -- 'script PID: %d\n' "$$" >&2

exitfn | :

printf -- 'SHOULD NEVER SEE THIS (0)\n' >&2

exitfn

printf -- 'SHOULD NEVER SEE THIS (1)\n' >&2

このスクリプト例では、exitfn現在のプロセスを終了する必要がある操作を実行する関数を示します1

残念ながら、この実装はexitfnこの操作を確実に実行できません。

このスクリプトを実行すると、出力は次のようになります。

% bash ./demo.sh
script PID: 26731
exitfn PID: 26731
SHOULD NEVER SEE THIS (0)
exitfn PID: 26731

(もちろん呼び出されるたびに表示されるPID値が異なります。)

ここで重要なのは、関数が最初にexitfn呼び出されたときにexit 1そのコマンドがその本体に含まれることです。埋め込みスクリプトの実行を終了できません。printf(すぐに実行された最初のコマンドでわかるように)。代わりに、2番目の呼び出しでは、exitfnこのexit 1コマンドはスクリプトの実行を終了します(printf2番目のコマンドが実行されていないことがわかるように)。

2つの呼び出しの唯一の違いは、最初の呼び出しは2つのコンポーネントexitfnパイプラインの最初のコンポーネントとして現れ、2番目の呼び出しは「スタンドアロン」呼び出しであることです。

私はこれについて混乱しています。私はこれexitが現在のプロセス(例えば、PID PIDを持つプロセス$$)を終了させると思いました。明らかに、これは必ずしも真実ではありません。

exitfnそれでもパイプラインで呼び出されても、周囲のスクリプトが終了するように書く方法はありますか?


ところで、上記のスクリプトも有効なzshスクリプトであり、同じ結果を生成します。

% zsh ./demo.sh
script PID: 26799
exitfn PID: 26799
SHOULD NEVER SEE THIS (0)
exitfn PID: 26799

私はzshに関するこの質問に対する答えにも興味があります。


exitfn最後に、これらの実装はまったく機能しないことを指摘したいと思います。

exitfn () {
    printf -- 'exitfn PID: %d\n' "$$" >&2
    exit 1
    kill -9 "$$"
}

...とにかく、このexit 1コマンドは常に関数を実行する最後の行であるためです。 (変える exit 1with はkill -9 $$許可されません。スクリプトの終了ステータスとstderrの出力を制御したいと思います。 )


1実際、これらの機能は、現在のプロセスを終了する前に、診断ログやクリーンアップ操作などの他のタスクを実行します。

ベストアンサー1

私はこれがexit現在のプロセス(つまりで提供されたPIDを持つプロセス$$)を殺すと思いました。

「現在のプロセス」は、「与えられたPIDを持つプロセス」と同じではありません$$exit現在終了サブシェル、またはサブシェルから呼び出されない場合は元のシェルです。

( … )(サブシェルにグループ化されている)、コマンドの置き換え(または)、および$(…)パイプ`…`の各側(または一部のシェルではすぐ左側)の内容など、特定の構成はサブシェルで実行されます。サブシェルは、以下を使用して作成された別のプロセスであるかのように動作します。fork()通常はこのように実装されます(一部のシェルはパフォーマンスの最適化で子プロセスを使用しない場合があります)。サブシェルには、独自の変数コピー、独自のリダイレクトなどがあります。標準Cライブラリの関数がプロセスを終了するexitのと同様に、サブシェルを終了するために呼び出されます。exit()サブシェルの詳細については、次を参照してください。(makeドキュメントの文脈で)サブシェルとは何ですか?「サブシェル」と「サブプロセス」の正確な違いは何ですか?そして$() はサブシェルですか?

$$常に元のシェルプロセスのプロセスID。サブシェル内では変更されません。一部のシェルには、$BASHPIDbashやmksh、${.sh.subshell}ksh、$ZSH_SUBSHELLzshなどのサブシェルで変更される変数があります$sysparams[pid]

exitfnパイプラインで呼び出されても周辺スクリプトが終了するように書く方法はありますか?

完全なものではなく。スクリプトがサブシェルを生成する場所、最上位レベル、またはその両方でさらに作業を行う必要があります。バラよりサブシェルでシェルスクリプトを終了する近似に使用されます。


¹他のシェルとは異なり、パイプの各メンバー(子プロセスで実行されている場合でも)と出力2以外の親プロセスzshのパイプ(左から右)に拡張を実行します。zsh -c 'echo $ZSH_SUBSHELL | cat'0echozsh -c 'n=0; echo $((++n)) | echo $((++n))'

おすすめ記事