以下のスクリプトは、問題を最小限に(人為的に)説明したものです。
## 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
コマンドはスクリプトの実行を終了します(printf
2番目のコマンドが実行されていないことがわかるように)。
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 1
with はkill -9 $$
許可されません。スクリプトの終了ステータスとstderrの出力を制御したいと思います。 )
1実際、これらの機能は、現在のプロセスを終了する前に、診断ログやクリーンアップ操作などの他のタスクを実行します。
答え1
私はこれが
exit
現在のプロセス(つまりで提供されたPIDを持つプロセス$$
)を殺すと思いました。
「現在のプロセス」は、「与えられたPIDを持つプロセス」と同じではありません$$
。exit
現在終了サブシェル、またはサブシェルから呼び出されない場合は元のシェルです。
( … )
(サブシェルにグループ化されている)、コマンドの置き換え(または)、および$(…)
パイプ`…`
の各側(または一部のシェルではすぐ左側)の内容など、特定の構成はサブシェルで実行されます。サブシェルは、以下を使用して作成された別のプロセスであるかのように動作します。fork()
通常はこのように実装されます(一部のシェルはパフォーマンスの最適化で子プロセスを使用しない場合があります)。サブシェルには、独自の変数コピー、独自のリダイレクトなどがあります。標準Cライブラリの関数がプロセスを終了するexit
のと同様に、サブシェルを終了するために呼び出されます。exit()
サブシェルの詳細については、次を参照してください。(makeドキュメントの文脈で)サブシェルとは何ですか?、「サブシェル」と「サブプロセス」の正確な違いは何ですか?そして$() はサブシェルですか?。
$$
常に元のシェルプロセスのプロセスID。サブシェル内では変更されません。一部のシェルには、$BASHPID
bashやmksh、${.sh.subshell}
ksh、$ZSH_SUBSHELL
zshなどのサブシェルで変更される変数があります$sysparams[pid]
。
exitfn
パイプラインで呼び出されても周辺スクリプトが終了するように書く方法はありますか?
完全なものではなく。スクリプトがサブシェルを生成する場所、最上位レベル、またはその両方でさらに作業を行う必要があります。バラよりサブシェルでシェルスクリプトを終了する近似に使用されます。
¹他のシェルとは異なり、パイプの各メンバー(子プロセスで実行されている場合でも)と出力2以外の親プロセスzsh
のパイプ(左から右)に拡張を実行します。zsh -c 'echo $ZSH_SUBSHELL | cat'
0
echo
zsh -c 'n=0; echo $((++n)) | echo $((++n))'