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=10
stdout
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)
サブシェルで実行されますが、 は をecho
受け取らずstdin
、引数のみを出力するため機能しません。ここで、引数は で、サブシェル内の$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 $a
が生成されます10
。これを変更した場合
user@host$ a=1; a=2; (a=10; echo $a)
最初と 2 番目の変数の割り当ては現在のシェルで設定され、3 番目の変数の割り当てと
echo
コマンドはサブシェルで実行されます。したがって、 の値はコマンドも実行されるサブシェル内にa
あるため、 が返されます。プロンプトが表示された後、コマンド を発行すると、サブシェルからの変数の割り当てが親シェルに引き継がれないため、 が返されます。10
echo
10
echo $a
2
- もう 1 つ注意すべき点は、
;
コマンド セパレータはコマンドを順番に実行するということです。
テストケース「A」と「B」では、最後の変数割り当て(a=10
テスト A とa=20
テスト B)が実際に実行されますが、コマンドの後に実行されるため、サブシェル環境にあるecho $a
変数の以前の値の結果が取得され、その後、最後の変数割り当てが実行されます。パイプラインでは、2 つのコマンドの「and」と「」はコマンドが実行される前に接続され、変数割り当てによって stdout に何も生成されません。a
stdin
stdout
要約: 変数の割り当てはパイプラインでは使用しないでください。echo
パイプラインでは直接機能しません。
答え3
ここ、
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 を使って得た出力の順序です。)