シェルスクリプトの入力リダイレクトに関する奇妙な点
-
08-06-2019 - |
質問
誰かこの行動を説明できますか?ランニング:
#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2
何も出力されませんが、次のようになります。
#!/bin/sh
echo "hello world" > test.file
read var1 var2 < test.file
echo $var1
echo $var2
期待される出力が生成されます。
hello
world
パイプは、2 番目の例で test.file へのリダイレクトが行ったことを 1 ステップで行うべきではないでしょうか?ダッシュ シェルと bash シェルの両方で同じコードを試してみましたが、両方とも同じ動作が得られました。
解決
最近追加されたもの bash
それは lastpipe
このオプションを使用すると、ジョブ制御が非アクティブ化されている場合に、パイプライン内の最後のコマンドをサブシェルではなく現在のシェルで実行できるようになります。
#!/bin/bash
set +m # Deactiveate job control
shopt -s lastpipe
echo "hello world" | read var1 var2
echo $var1
echo $var2
確かに出力されます
hello
world
他のヒント
#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2
パイプラインはサブシェル内で各コンポーネントを実行するため、出力は生成されません。サブシェル コピーを継承する 親シェルの変数を共有するのではなく、それらを共有します。これを試して:
#!/bin/sh
foo="contents of shell variable foo"
echo $foo
(
echo $foo
foo="foo contents modified"
echo $foo
)
echo $foo
括弧はサブシェルで実行されるコードの領域を定義し、$foo は括弧内で変更された後も元の値を保持します。
今度はこれを試してください:
#!/bin/sh
foo="contents of shell variable foo"
echo $foo
{
echo $foo
foo="foo contents modified"
echo $foo
}
echo $foo
中かっこは単にグループ化するためのもので、サブシェルは作成されません。中かっこの内側で変更された $foo は、中かっこの外側で変更された $foo と同じです。
今度はこれを試してください:
#!/bin/sh
echo "hello world" | {
read var1 var2
echo $var1
echo $var2
}
echo $var1
echo $var2
中かっこ内では、読み取り組み込み関数によって $var1 と $var2 が適切に作成され、それらがエコーされることがわかります。中括弧の外側には、もう存在しません。中かっこ内のすべてのコードはサブシェルで実行されています パイプラインのコンポーネントの 1 つであるため、.
中括弧の間には任意の量のコードを入れることができるので、他のものの出力を解析するシェル スクリプトのブロックを実行する必要があるときはいつでも、このブロックへのパイピング構造を使用できます。
これはすでに正しく回答されていますが、解決策はまだ述べられていません。bash ではなく ksh を使用してください。比較する:
$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | bash -s
に:
$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | ksh -s
hello
world
ksh は、このような小さな機能があるため、優れたプログラミング シェルです。(私の意見では、bash の方が対話型シェルとして優れています。)
read var1 var2 < <(echo "hello world")
この投稿には適切に回答されていますが、おそらく何らかの役に立つかもしれない別のワンライナーを提供したいと思います。
echo (またはさらに言えば stdout) からのスペース区切りの値をシェル変数に割り当てるには、シェル配列の使用を検討できます。
$ var=( $( echo 'hello world' ) )
$ echo ${var[0]}
hello
$ echo ${var[1]}
world
この例では、var は配列であり、構成要素 ${var[index]} を使用して内容にアクセスできます。ここで、index は配列のインデックス (0 から始まります) です。
こうすることで、必要なだけパラメータを関連する配列インデックスに割り当てることができます。
この問題に対する私の見解 (Bash を使用):
read var1 var2 <<< "hello world"
echo $var1 $var2
よし、分かった!
これは見つけるのが難しいバグですが、シェルによるパイプの処理方法に起因します。パイプラインのすべての要素は別のプロセスで実行されます。read コマンドが var1 と var2 を設定すると、それらは親シェルではなく独自のサブシェルに設定されます。したがって、サブシェルが終了すると、var1 と var2 の値は失われます。ただし、次のことを試してみることはできます
var1=$(echo "Hello")
echo var1
期待される答えが返されます。残念ながら、これは単一の変数に対してのみ機能し、一度に多くの変数を設定することはできません。一度に複数の変数を設定するには、1 つの変数を読み取って複数の変数に分割するか、次のようなものを使用する必要があります。
set -- $(echo "Hello World")
var1="$1" var2="$2"
echo $var1
echo $var2
パイプを使用するほどエレガントではないことは認めますが、機能します。もちろん、read はファイルから変数に読み取ることを目的としているため、標準入力から読み取るようにするのは少し難しいことに注意する必要があります。
これは、パイプ バージョンがサブシェルを作成し、変数をそのローカル空間に読み取り、サブシェルが終了すると破棄されるためです。
このコマンドを実行します
$ echo $$;cat | read a
10637
pstree -p を使用して実行中のプロセスを確認すると、メイン シェルからぶら下がっている追加のシェルが表示されます。
| |-bash(10637)-+-bash(10786)
| | `-cat(10785)
試す:
echo "hello world" | (read var1 var2 ; echo $var1 ; echo $var2 )
問題は、複数の人が述べているように、var1 と var2 がサブシェル環境で作成され、そのサブシェルが終了すると破棄されることです。上記により、結果がエコーされるまでサブシェルが破棄されることが回避されます。別の解決策は次のとおりです。
result=`echo "hello world"`
read var1 var2 <<EOF
$result
EOF
echo $var1
echo $var2