これはbashのバグですか?`return`はパイプから呼び出されても関数を終了しません


16

私は最近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検索ではこれについて何ももたらされませんでした!私のbashバージョンは、Debian Wheezy上の4.2.37(1)-releaseです。


返信で提案した設定で、スクリプトが期待したとおりに直感的に動作できるようにするために何か問題がありますか?
jlliagre

@jlliagreこれは、数千行にわたるかなり複雑なスクリプトです。他の何かを壊す心配があるので、関数内でパイプを実行するのは避けたいので、プロセス置換に置き換えました。ありがとう!
テレサeジュニア

while複製に必要ない場合は、最初の2つの例を削除しないのはなぜですか?それはポイントから気をそらします。
モニカとの軽さレース

@LightnessRacesinOrbit whileループは、を持つパイプの非常に一般的な使用法ですreturn。第二の例は、ポイントをよりストレートですが、それは私が誰もが今まで使用するとは思わない何か...である
テレサ電子ジュニア

1
残念ながら、私の正解は削除されました...指定されていないことをすると、灰色のゾーンにいます。この動作は、シェルがパイプをどのように解釈するかによって異なり、kshがshソースから派生した場合でも、Bourne ShellとKorn Shellの間ではさらに異なります。Bourneシェルでは、whileループはサブシェルにあるため、bashと同様にエコーが表示されます。kshでは、whileループがフォアグラウンドプロセスであるため、kshは例でechoを呼び出しません。
気味悪い

回答:


10

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

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

それに加えて、(おそらく)未定義の仕様で文書化されていないbashの動作を見ていると思います。関数ではreturn、サブシェルコマンドのトップレベルでエラーはアサートされず、単にのように動作しexitます。

私見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

エラーの冗長性の欠如は文書化されていない可能性があります。しかし、returnサブシェルのトップレベルのコマンドシーケンスでは機能せず、特にサブシェルを終了しないという事実は、既存のドキュメントがすでに私に期待させたことです。OPは、使用exit 1 || return 1しようとしている場所を使用returnでき、期待される動作を取得するはずです。編集:@herbertの答えはreturn、サブシェルのトップレベルが(サブシェルexitからのみ)機能していることを示しています。
-dubiousjim

1
@dubiousjimはスクリプトを更新しました。私が意味するreturn、単純なサブシェル順序で実行時エラーとしてアサートされなければならない任意の例では、それがfucntionに発生したときに、実際にそうではありません。この問題はgnu.bash.bugでも議論されていますが、結論はありません。
八重ashi

1
whileループがサブシェルにあるか、フォアグラウンドプロセスであるかは不明であるため、答えは正しくありません。実際のシェルの実装に関係なく、returnステートメントは関数内にあるため、有効です。ただし、結果の動作は指定されていません。
気味悪い

パイプコンポーネントがサブシェルにあるという事実は、bashのマニュアルページに文書化されていますが、文書化されていない動作であると書くべきではありません。POSIXが許可された動作を指定している間、動作はおそらく未定義の仕様に基づいていると書くべきではありません。bashがPOSIX標準に従っている間、bashのバグを疑うべきではありません。関数では戻りが許可されますが、外部では許可されません。
jlliagre

17

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

1
return関数を終了しないのでbash: return: can only `return' from a function or sourced script、ユーザーに関数が返された可能性があるという誤った感覚を与えるのではなく、シェルがちょうど印刷された場合、それはより理にかなっていますか?
テレサeジュニア

2
サブシェル内の戻り値が有効であると書かれているドキュメントには、どこにも記載されていません。この機能はkshからコピーされたもので、関数またはソーススクリプトの外のreturnステートメントはexitのように振る舞います。オリジナルのBourneシェルについてはわかりません。
クオンルム

1
@jlliagre:たぶんテレサは彼女が求めている用語について混乱しているかもしれませんがreturn、サブシェルからを実行した場合にbashが診断を発行するのはなぜ「難しい」のかわかりません。結局のところ、$BASH_SUBSHELL変数によって証明されるように、サブシェル内にあることを知っています。最大の問題は、これが誤検知につながる可能性があることです。サブシェルの仕組みを理解しているユーザーは、サブシェルを終了するreturn代わりに使用するスクリプトを作成できますexit。(そして、もちろん、変数を設定したりcd、サブシェルでaを実行したりする有効なケースがあります。)
Scott

1
@Scott私は状況をよく理解していると思う。パイプはサブシェルを作成しreturn、実際の関数内にあるため、失敗する代わりにサブシェルから戻ります。問題は、help return具体的に次のように述べていることです。Causes a function or sourced script to exit with the return value specified by N.ドキュメントを読むと、どのユーザーも少なくとも失敗するか、警告を出力することを期待しますが、のように動作することはありませんexit
テレサeジュニア

1
関数のreturn サブシェルで(メインシェルプロセスで)関数から戻ることを期待する人は、サブシェルをあまり理解していないように思えます。逆に、サブシェルを理解する読者return は、サブシェルを終了する関数内のサブシェルに期待することを期待exitます。
スコット

6

POSIXのドキュメントごとに、関数またはソーススクリプトの外部での使用returnは規定されていません。したがって、処理するシェルに依存します。

SystemVシェルはエラーを報告しますが、in kshではreturn、関数またはソーススクリプトの外ではのように動作しますexit。他のほとんどのPOSIXシェルとschilyの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対話型セッションでは、出力は終了しない端末bashyashありschily's osh、エラーを報告しましたが、シェルを終了しませんでした)


1
ここでは、関数returnで使用されいると主張できます。
jlliagre

1
@jlliagre:わからないあなたが何を意味するかは、return内部で使用したサブシェルの内部機能を除くkshzsh
クオンルム

2
それ自体が関数の内部にあるサブシェルの内部にあるということは、必ずしもその関数の外部にあることを意味しません。つまり、標準状態のパイプラインコンポーネントは、それらが配置されている関数の外部にあると見なされません これは、Open Groupによって明らかにされるに値するでしょう。
jlliagre

3
違うと思う。それは関数の外です。関数を呼び出したシェルとreturnを実行したサブシェルは異なります。
クオンルム

私は問題を正しく説明するあなたの推論を理解しています。私のポイントはPOSIX標準に記述されているシェルの文法に従っており、パイプラインは関数の本体である複合コマンドの一部である複合リストの一部です。パイプラインコンポーネントが機能の外側にあると見なされることはどこにも記載されていません。私が車に乗っていて、その車がガレージに駐車しているように、私もそのガレージにいると仮定できます
;

4

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ステートメントはサブシェルを正しく終了しました


2
したがって、関数を終了するにはを使用foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?し、の結果を取得します2。しかし、明確にするため、私はなるだろうreturn 1BEをexit 1
dubiousjim

ところで、パイプラインのすべてのメンバー(すべてではなく1つ)がサブシェルで実行されるという事実の実証はありますか?
Incnis Mrsi

@IncnisMrsi:jlliagreの答えをご覧ください。
スコット

1

より一般的な答えは、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ループにパイプし、ループをサブシェルとして実行させてから、サブシェルから戻ります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.