
最近、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
動作するエラーを報告します。return
exit
ヒリーのオッシュ次のような動作もあります。
$ 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
答え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
ループは子プロセスに配置されるため、ループが終了した後にデフォルトのシェルにその変更を表示することはできません。これがまさにあなたがやっていることです。ループにパイプを接続し、ループをサブシェルとして実行し、サブシェルから戻ります。do
done
t
while