POSIXシェルスクリプトパイプラインで実行された最後の関数が変数値を保持しないのはなぜですか?

POSIXシェルスクリプトパイプラインで実行された最後の関数が変数値を保持しないのはなぜですか?

次のスクリプトを想定します。

#!/bin/sh

func1() {
  eval $1'=$(cat)'
  eval echo "Value$2 inside function : \$$1"
}

func1 x 1 <<'HEREDOC'
Hello World
HEREDOC

echo "Value1 outside function: $x"

x=""

echo "Hello World" | func1 x 2

echo "Value2 outside function: $x"

Bash 4.3.43-4.fc25では、出力は次のようになります。

Value1 inside function : Hello World
Value1 outside function: Hello World
Value2 inside function : Hello World
Value2 outside function: 

bashismを使ってshopt -s lastpipe最後の行に「Hello World」も表示させるようにしましたが、直感的にはこれが自動的に発生しない理由を理解できません。これが期待されるか。

POSIX標準は実際にはこのトピックを議論していないようです。http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_02

答え1

歴史的に、ボンシェルそしてコーエンシェル別の方法で行動してください。パイプの両側が異なるプロセスで並列に実行されるため、残りのスクリプト全体で両方の変数割り当てを維持することは不可能です。与えられた場合、unset a; a=b | a=c変数はa設定されていないか、等しいかb等しいことがcあります。bc

Bourneシェルでは、パイプ演算子の各側面がサブシェル(つまり別々のシェルプロセス)で実行されるため、変数の割り当ては維持されません。 Korn シェルでは、パイプの右側が元のシェルプロセスで実行されます。

POSIXは、既存の動作が他の多くの場合と同様に、両方の動作を許可します。このセクションの説明に従って「シェル実行環境」:

マルチコマンドパイプラインの各コマンドはサブシェル環境にありますが、拡張機能によってパイプラインの一部またはすべてのコマンドを現在の環境で実行できます。他のすべてのコマンドは、現在のシェル環境で実行されます。

一部のシステムでは、現在の環境でパイプラインの最終段階をすでに実装しており、次のコマンドを実行しています。

command | read foo

foo現在の環境で変数を設定します。この拡張は許可されますが、必須ではありません。したがって、シェルプログラマはパイプがサブシェル環境にありますが、それに依存してはいけないことを考慮する必要があります。

実際に生成されたプロセスの数を注意深く調べない限り、同じように機能するいくつかの最適化を除いて、最後のコマンド以外のコマンドは常にサブシェルで実行されます。つまり、シェルにはunset a; a=b | true; echo $a印刷がありませんb

ash、dash、bash、pdksh、mkshなどの他のほとんどのBourne様シェルは、Bourneシェルのように動作します。 ZshはKornシェルのように動作します。最新バージョンのbashでは、kshアクションに切り替えることができますshopt -s lastpipe

答え2

はい、POSIXはこのトピックを扱っています。 ~からここ:

サブシェル環境はシェル環境のコピーとして作成する必要がありますが、無視されない信号トラップはデフォルトの動作に設定する必要があります。サブシェル環境に対する変更は、シェル環境に影響を与えてはいけません。コマンドの置換、括弧でグループ化されたコマンド、および非同期のリストは、サブシェル環境で実行する必要があります。さらに、マルチコマンドパイプラインの各コマンドはサブシェル環境にありますが、拡張機能によってパイプラインの一部またはすべてのコマンドを現在の環境で実行できます。他のすべてのコマンドは、現在のシェル環境内で実行する必要があります。

デフォルトの動作の選択はシェルに依存します。存在する:

echo 1 | read x
echo "$x"

onlyzshksh出力1、他の現代のBourneのようなシェルは空の文字列を出力します。

答え3

POSIX と:

マルチコマンドパイプラインの各コマンドはサブシェル環境にあります。

それから

サブシェル環境への変更は、シェル環境に影響を与えてはいけません。

パイプラインを実装する最も簡単な方法(呼び出し)を考えると、これは意味があります。pipe()、サブシェルを分岐し、片側でstdout / inputを置き換えてから、サブシェルでコマンドを実行します。これもBashで定義された動作

できない頼るしかし、これが起こった場合

ただし、拡張機能を使用すると、パイプラインの一部またはすべてのコマンドを現在の環境で実行できます。

これがlastpipeまさにこの事件がすることです。一部の他のシェルでは、これが特定の状況のデフォルト動作です。

関連情報