パイプライン後の複数のコマンドとサブシェルの実行

パイプライン後の複数のコマンドとサブシェルの実行

Bash では (デフォルトでは、'lastpipe' bash オプションが有効になっていない場合)、パイプラインの後に割り当てられたすべての変数はサブシェルで実行され、サブシェルの実行後に変数自体は消滅することがわかっています。親プロセスでは使用できなくなります。しかし、いくつかテストを行ったところ、次のような動作が見つかりました。

A) 2 番目のコマンド (a=2) は値を割り当てて返します。

[root@centos01]# a=1; a=2; a=10 | echo $a
2

B) 3 番目のコマンド (a=10) は値を割り当てて返します。

[root@centos01]# a=1; a=2; a=10; a=20 | echo $a
10

C) 4番目のコマンド (a=20) は値を割り当てて返します。

[root@centos01]# a=1; a=2; a=10; a=20; touch fileA.txt | echo $a
20

それで:

  • コマンドのシーケンス内の最後の変数割り当てが実際には実行されないのはなぜですか? (または、実行されている場合、サブシェルによってキャッチされず、echo コマンドによって返されないのはなぜですか?)

  • テスト C では、「touch」コマンドによって実際にディレクトリにファイル「fileA.txt」が作成されました。では、ステップ A とセット B で行われた変数割り当てのシーケンスの最後のコマンドが機能しなかったのはなぜでしょうか。その技術的な説明を知っている人はいますか。

答え1

まず、いくつかの名前について合意するために、シェルが入力をどのように解釈するかを次に示します。

$ a=1; a=2; a=10 | echo $a
  ^^^  ^^^  ^^^^^^^^^^^^^^
    \    \         \_ Pipeline
     \    \_ Simple command
      \_ Simple command

パイプラインは、次の 2 つの単純なコマンドで構成されています。

$ a=10 | echo $a
  ^^^^   ^^^^^^^
     \       \_ Simple command
      \_ Simple command

(Bashのマニュアルには明記されていないかもしれませんが、POSIX シェル文法単純なコマンドを単なる変数の割り当てで構成できます。

a=1;パイプラインの一部ではa=2;ありません。Aは;パイプラインを終了しますが、複合コマンドたとえば、次のようになります。

{ a=1; a=2; a=10; } | echo $a

あなたの例では、a=10およびはecho $a次のように実行されます2つの異なる独立したサブシェル環境1、両方ともメイン環境のコピーとして作成されます。サブシェルは親実行環境2を変更しないようにする必要があります。関連するPOSIXセクション:

サブシェル環境は、シェル環境の複製として作成されます [...] サブシェル環境に加えられた変更は、シェル環境に影響を与えません。

そして

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

したがって、例のすべてのコマンドは実際に実行されますが、パイプラインの左側部分の割り当てには目に見える効果はありません。それぞれのサブシェル環境内の のコピーのみが変更されa、サブシェルが終了するとすぐに失われます。

パイプの両端のサブシェルが直接相互作用できる唯一の方法は、パイプ自体、つまり左側の標準出力を右側の標準入力に接続することです。 はa=10パイプを介して何も送信しないため、 に影響を与えることはできませんecho $a


1オプションが設定されている 場合lastpipe(デフォルトではオフですが、shopt組み込みコマンドを使用して有効にすることができます)、Bashは現在のシェルのパイプラインの最後のコマンドを実行することがあります。パイプラインBash マニュアルに記載されています。ただし、これは質問の文脈には関係ありません。

2 U&Lに関する実践的/歴史的観点からのより詳しい情報は、例えば以下で見つけることができます。この答えPOSIX シェル スクリプト パイプラインで最後に実行された関数が変数値を保持しないのはなぜですか?

答え2

私の英語が下手なのはご容赦ください。まだ勉強中です。私も Bash 初心者なので、回答に間違いがあれば訂正してください。よろしくお願いします。

まず、間違いを指摘します。

  • では、コマンドをエコーするためにa=10 | echo $aパイプしています (パイプ演算子; を使用)。パイプにより、コマンドがcommand2に接続されます (つまり、 |) 。a=10stdoutstdincommand | command2

  • a=10は変数の割り当てですが、これはコマンドではないので、存在しないものと想定しますstdout。変数の割り当ての値でコマンド置換を実行しても機能しません。次のことを試してみます。

    user@host$ a=$(b=10); echo $a
    

    echo $a値を返しません10。これを次のように変更すると

    user@host$ a=$(b=10; echo $b)
    

    の呼びかけ

    $ echo $a
    

    返されます10。したがって、変数の割り当てはコマンドではないと想定するのはおそらく正しいでしょう (bash のマニュアル定義でもコマンドではありません)。

次に、echoコマンドは から入力を受け取るのではなくstdin、その引数を出力します。

user@host$ echo "I love linux" | echo

xargs何も返しません。これを克服するには、コマンドを使用します。

user@host$ echo "I love linux" | xargs echo

を返します。したがって、パイプは引数を出力し、 を出力しないため、コマンドI love linuxでは直接機能しません。echostdin

さて、テストです

  • あなたの命令で

    user@host$ a=1; a=2; a=10 | echo $a
    

    変数には最初にa値が割り当てられ1、その後、変数の値は に変更されます2。どちらも現在のシェル環境内です。コマンドは通常、サブシェルで実行されます。はa=10 | echo $aリストです。つまり、 は(a=10 | echo $a)サブシェルで実行されますが、 は をecho受け取らずstdin、引数のみを出力するため機能しません。ここで、引数は で、サブシェル内の$a変数の値はです。a2

  • さらに、a=10は変数割り当てであるため、出力を生成しません。したがって、 は実際にecho $aは引数の値である を出力し2、 からは何も取得しませんa=10 | < ... >。したがって、ここではパイプ演算子を使用しないでください。代わりに、コマンド名と変数割り当てをターミネータ (、セミコロン) で区切ると、 のように正常に機能します(a=10; echo $a)

これをよりよく理解するには、bash デバッグ オプションを有効にして次の例を試してください。

user@host$ a=1; a=2; a=10; echo $a;
  • 上記のコマンドラインでは、echo $aが生成されます10

  • これを変更した場合

    user@host$ a=1; a=2; (a=10; echo $a)
    

    最初と 2 番目の変数の割り当ては現在のシェルで設定され、3 番目の変数の割り当てとechoコマンドはサブシェルで実行されます。したがって、 の値はコマンドも実行されるサブシェル内にaあるため、 が返されます。プロンプトが表示された後、コマンド を発行すると、サブシェルからの変数の割り当てが親シェルに引き継がれないため、 が返されます。10echo10echo $a2

  • もう 1 つ注意すべき点は、;コマンド セパレータはコマンドを順番に実行するということです。

テストケース「A」と「B」では、最後の変数割り当て(a=10テスト A とa=20テスト B)が実際に実行されますが、コマンドの後に実行されるため、サブシェル環境にあるecho $a変数の以前の値の結果が取得され、その後、最後の変数割り当てが実行されます。パイプラインでは、2 つのコマンドの「and」と「」はコマンドが実行される前に接続され、変数割り当てによって stdout に何も生成されません。astdinstdout

要約: 変数の割り当てはパイプラインでは使用しないでください。echoパイプラインでは直接機能しません。

答え3

ここ、

a=20 | echo $a

パイプは混乱を招くだけです。左側の割り当てでは、stdout に何も出力されず、echostdin から何も読み込まれないため、パイプを介してデータは移動されません。引数は、echo以前に設定されたものから拡張されるだけです。

あなたが言ったように、パイプラインの各部分は別々のサブシェルで実行されるため、左側の割り当てはa右側の展開に影響を与えず、逆の場合にもそのような通信は発生しません。

代わりに、次のようにします。

{ a=999; echo a=$a; } | cat

パイプの方が理にかなっており、文字列はa=999パイプに通されることになります。

最後の例のはtouch fileA.txt、シェルの外部のシステムに影響を与えるため機能します。同様に、パイプ内のコマンドから stderr に書き込み、結果がターミナルに表示されるのを確認することもできます。

$ echo a >&2 | echo b >&2 | echo c >&2
b
c
a

(これは実際に私のシステムで Bash を使って得た出力の順序です。)

関連情報