私は最近bashでいくつかの奇妙な問題を抱えています。スクリプトを簡略化しようとしているときに、次の小さなコードを思いつきました。
$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1
return
は、印刷せずに関数を終了する必要があります$?
、そうではないですか?それでは、パイプだけから戻ることができるかどうかを確認しました。
$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script
同じことがwhile
ループなしでも起こります:
$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.
ここで見逃しているものはありますか?グーグル検索はこれについて何ももたらしませんでした!私のbashバージョンは、Debian Wheezyでは4.2.37(1)-releaseです。
関連: https://stackoverflow.com/a/7804208/49379
サブシェルでexit
またはreturn
を使用してスクリプトを終了したり、関数から戻ることができないのはバグではありません。それらは別のプロセスで実行され、メインプロセスには影響しません。
それ以外にも、(おそらく)未定義の仕様でのbashの文書化されていない動作を見ていると思います。関数では、サブシェルコマンドのトップレベルのreturn
に対してエラーはアサートされず、exit
のように動作します。
私見それは、メインステートメントが関数内にあるかどうかに応じて、return
の一貫性のない動作のbashバグです。
#!/bin/bash
o() {
# Runtime error, but no errors are asserted,
# each $? is set to the return code.
echo | return 10
echo $?
(return 11)
echo $?
# Valid, each $? is set to the exit code.
echo | exit 12
echo $?
(exit 13)
echo $?
}
o
# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?
# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?
出力:
$ bash script.sh
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23
bash
のバグではありませんが、その 文書化された動作 :
パイプラインの各コマンドは、独自のサブシェルで実行されます
return
命令は関数定義内で有効ですが、サブシェルでも有効です。親シェルには影響しないため、次の命令echo
は関係なく実行されます。それでも POSIX標準 はパイプラインを構成するコマンドをサブシェル(デフォルト)または最上位(許可される拡張)のいずれかで実行できるため、移植性のないシェル構造です。
さらに、マルチコマンドパイプラインの各コマンドはサブシェル環境にあります。ただし、拡張機能として、パイプライン内の一部またはすべてのコマンドを現在の環境で実行できます。他のすべてのコマンドは、現在のシェル環境で実行されます。
うまくいけば、いくつかのオプションを使用してbash
に期待どおりの動作をさせることができます。
$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$ <- nothing is printed here
POSIXのドキュメントによれば、 関数またはソーススクリプトの外でのreturn
の使用は指定されていません 。したがって、処理するシェルに依存します。
SystemVシェルはエラーを報告しますが、ksh
では、return
関数外またはソーススクリプトはexit
のように動作します。他のほとんどのPOSIXシェルと schily's osh も同様に動作します。
$ for s in /bin/*sh /opt/schily/bin/osh; do
printf '<%s>\n' $s
$s -c '
o(){ echo | while read l; do return 0; done; echo $?;}; o
'
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0
ksh
およびzsh
は、これらのシェルのパイプの最後の部分がサブシェルではなく現在のシェルで実行されたため、出力されませんでした。 returnステートメントは、関数を呼び出した現在のシェル環境に影響を与え、何も出力せずに関数がすぐに戻ります。
インタラクティブセッションでは、bash
はエラーを報告するだけで、シェルを終了しませんでしたschily's osh
はエラーを報告し、シェルを終了しました:
$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function
(インタラクティブセッションのzsh
と出力はターミナルで終了しない、bash
、yash
とschily's osh
はエラーを報告しましたが、シェルを終了しませんでした)
期待どおりの動作が得られたと思います。bashでは、パイプラインの各コマンドはサブシェルで実行されます。関数のグローバル変数を変更しようとすることで、自分自身を混乱させることができます。
foo(){ x=42; : | x=3; echo "x==$x";}
ちなみに、戻りは機能していますが、サブシェルから戻ります。もう一度確認できます。
foo(){ : | return 1; echo$?; echo "This should not be printed.";}
以下を出力します:
1
This should not be printed.
したがって、returnステートメントはサブシェルを正しく終了しました
。
より一般的な答えは、bashや他のいくつかのシェルは通常、パイプラインのすべての要素を別々のプロセスに入れることです。これは、コマンドラインが
プログラム1 | プログラム2 | プログラム3
とにかく、プログラムは通常別のプロセスで実行されるためです(exec program
)。しかし、それは驚きです
コマンド1 | コマンド2 | コマンド3
ここで、コマンドの一部またはすべてが組み込みコマンドです。簡単な例は次のとおりです。
$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/
もう少し現実的な例は
$ t=0
$ ps | while read pid rest_of_line
> do
> : $((t+=pid))
> done
$ echo "$t"
0
ここで、while
…do
…done
ループ全体がサブプロセスに入れられるため、t
への変更はメインシェルに表示されません。ループは終了します。そして、それがまさにあなたがしていることです。while
ループにパイプを入れ、ループをサブシェルとして実行させてから、サブシェルからの戻りを試みます。