パイプラインを使用しているときに、読み取ったコマンドをサブシェルに配置する必要があるのはなぜですか[重複]
このコマンドdf .
は、現在使用しているデバイスを表示できます。例えば、
me@ubuntu1804:~$ df .
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdb1 61664044 8510340 49991644 15% /home
次に、文字列を取得します/dev/sdb1
。
私はこのように試しましたが、うまくいきませんでした:df . | read a; read a b; echo "$a"
、このコマンドは私に空の出力を与えました。しかしdf . | (read a; read a b; echo "$a")
、期待どおりに機能します。
私は今ちょっと混乱しています。
それ(read a; read a b; echo "$a")
がサブシェルであることは知っていますが、なぜここでサブシェルを作成する必要があるのかわかりません。私の理解として、x|y
の出力x
をの入力にリダイレクトしますy
。なぜread a; read a b; echo $a
入力を取得できないのにサブシェルは取得できるのですか?
回答
ここでの主な問題は、コマンドを正しくグループ化することです。サブシェルは二次的な問題です。
x|y
の出力x
をの入力にリダイレクトしますy
はい、しかし、x | y; z
の出力をリダイレクトするつもりはないx
の両方にy
とz
。
ではdf . | read a; read a b; echo "$a"
、パイプラインのみ接続さdf .
とread a
、他のコマンドは、パイプラインに接続されていません。あなたはグループに持っていread
ます。■一緒にdf . | { read a; read a b; }
またはdf . | (read a; read a b)
パイプラインは、それらの両方に接続されるため。
ただし、サブシェルの問題が発生します。パイプライン内のコマンドはサブシェルで実行されるため、変数を設定しても親シェルには影響しません。したがって、echo
コマンドはread
sと同じサブシェルにある必要があります。だから:df . | { read a; read a b; echo "$a"; }
。
パイプラインのコマンドはとにかくサブシェルで実行されるため、ここで使用する( ... )
か{ ...; }
、特に違いはありません。
代替は、使用することですプロセスsubstitionを:
{ read header; read filesystem rest; } < <(df .)
echo "$filesystem"
<(...)
プロセス置換は(サブシェルで)含まれているスクリプトを実行しますが、それは、ファイル名のように働くので、あなたが最初に必要<
ブレーススクリプトに(スクリプトの出力である)の内容をリダイレクトします。グループ化されたコマンドは、現在のシェルで実行されます。
これを読みやすくするのは難しいかもしれませんが、中括弧とプロセス置換に任意の空白を入れることができます。
{
read header
read filesystem rest
} < <(
df .
)
echo "$filesystem"
また、外部ツールを使用してファイルシステムを抽出する方が簡単な場合があります。
filesystem=$( df . | awk 'NR == 2 {print $1}' )
あなたの最初のコマンド
df . | read a; read a b; echo "$a"
効果的に次のように解釈されます
( df . | read a ) ; read a b; echo "$a"
したがって、パイプラインはread a
コマンドにのみフィードします。
パイプラインから複数の読み取りが必要なため、コマンドをグループ化する必要があります。
今ではサブシェルである必要はありません。それはグループ化である可能性があります。
bash-4.2$ df | { read a ; read a b ; echo $a ; }
devtmpfs
より一般的には、ループが必要になる場合があります
bash-4.2$ df | while read a > do > read a b > echo $a
> done
devtmpfs
tmpfs
/dev/vda3
/dev/vdb
bash
サブシェルで実行されているパイプラインの右側に2番目の問題があるため$a
$b
、while
ループの外部で値にアクセスできませんが、それは別の問題です。