web-dev-qa-db-ja.com

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

わかりました。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
JohnC

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

$ 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=1; a=2; a=10; } | echo $a

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

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

そして

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

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

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


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

2U&Lの実践的/歴史的観点から詳細を見つけることができます。 in this answer to なぜPOSIX Shellスクリプトパイプラインで実行された最後の関数が変数値を保持しないのですか?

1
fra-san

すみません、まだ英語を勉強中です。私はBashの初心者学習者でもありますので、答えの間違いを訂正してください。ありがとうございます。

まず、エラーを指摘します。

  • a=10 | echo $aで、(パイプ演算子を使用して; |a=10をエコーコマンドにパイプします。パイピングは、stdout aコマンドをcommand2のstdin、つまりcommand | 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を返します。そのため、echoではなく引数を出力するため、stdinコマンドでは直接パイプは機能しません。

さて、あなたのテストに

  • あなたの命令で

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

    変数aには最初に値1が割り当てられ、次に変数の値が現在のシェル環境で2に変更されます。コマンドは通常、サブシェルで実行されます。 a=10 | echo $aはリストです。つまり、サブシェルで実行される(a=10 | echo $a)と同等ですが、echostdinを受け取らないため、機能しません。しかし、それは引数を出力するだけです。ここで、引数は$aであり、サブシェルの変数aの値は2です。

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

これをよりよく理解するために、bashデバッグオプションを有効にして次の例を試すことができます。

user@Host$ a=1; a=2; a=10; echo $a;
  • 上記のコマンドラインで、echo $a10を生成します。

  • に変更すると

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

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

  • もう1つ注意すべき点として、;コマンドセパレーターはコマンドを順次実行します。

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

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

1
FuRinKaZan_001

ここに、

a=20 | echo $a

パイプは混乱を増すだけです。左側の割り当てはstdoutに何も出力せず、echoはstdinから何も読み取らないため、パイプを介してデータは移動されません。 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を使用して取得した出力の順序です。)

1
ilkkachu