これはbashのバグですか?パイプから呼び出すと、 'return' は関数を終了しません。

これはbashのバグですか?パイプから呼び出すと、 'return' は関数を終了しません。

最近、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.

ここで何か抜けましたか? Google 検索結果がありません。私のバッシュバージョンは4.2.37(1)-リリースDebian Wheezy から。

答え1

これはバグではありませんが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

答え2

関連:https://stackoverflow.com/a/7804208/4937930

exitサブシェルを介してスクリプトを終了したり、関数から戻ることができないのはバグではありませんreturn。他のプロセスで実行され、メインプロセスには影響しません。

それに加えて、(おそらく)未定義の仕様でbashの文書化されていない動作を見ることになると思います。関数内のreturnサブシェルコマンドの最上位レベルはエラーを主張しませんexit

IMHO これはbashのバグで、returnデフォルトの文が関数にあるかどうかに応じて一貫して動作しません。

#!/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

答え3

POSIX文書によると、return外部関数またはソーススクリプトの未指定の使用。したがって、これを処理することはシェルに依存します。

SystemVシェルは、他のほとんどのPOSIXシェルと同様にksh動作するエラーを報告します。returnexitヒリーのオッシュ次のような動作もあります。

$ 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出力が表示されます)bashyashschily's osh

答え4

より一般的な答えは、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ループは子プロセスに配置されるため、ループが終了した後にデフォルトのシェルにその変更を表示することはできません。これがまさにあなたがやっていることです。ループにパイプを接続し、ループをサブシェルとして実行し、サブシェルから戻ります。dodonetwhile

関連情報