web-dev-qa-db-ja.com

パイプからシェル変数に値を読み込む

私はbashにパイプライン処理されるstdinからのデータを処理させようとしていますが、運が悪いです。私が言っているのは、次のような仕事ではないということです。

echo "hello world" | test=($(< /dev/stdin)); echo test=$test
test=

echo "hello world" | read test; echo test=$test
test=

echo "hello world" | test=`cat`; echo test=$test
test=

出力をtest=hello worldにします。私は"$test"のまわりに引用符を入れてみましたが、これもうまくいきません。

178
ldog

つかいます

IFS= read var << EOF
$(foo)
EOF

あなたはcanトリックreadを次のようなパイプから受け入れるようにトリックする:

echo "hello world" | { read test; echo test=$test; }

あるいは、次のような関数を書くこともできます。

read_from_pipe() { read "$@" <&0; }

しかし意味がありません - あなたの変数代入は持続しないかもしれません!パイプラインは、環境が参照ではなく値によって継承されるサブシェルを生成することがあります。これがreadがパイプからの入力を気にしない理由です - それは未定義です。

参考までに、 http://www.etalabs.net/sh_tricks.html は、bourneシェルの奇妙さと非互換性を戦うために必要な、気の利いたコレクションです。

150
yardena

大量のデータを読み込み、各行を別々に処理したい場合は、次のようにします。

cat myFile | while read x ; do echo $x ; done

行を複数の単語に分割したい場合は、次のようにxの代わりに複数の変数を使用できます。

cat myFile | while read x y ; do echo $y $x ; done

あるいは:

while read x y ; do echo $y $x ; done < myFile

しかし、この種のことで本当に賢いことをしたいと思ったらすぐに、Perlのようなスクリプト言語を使ってみてください。

Perl -ane 'print "$F[0]\n"' < myFile

Perlではかなり急峻な学習曲線があります(またはこれらの言語のどれかを使用すると思います)が、最も単純なスクリプト以外のものを実行したい場合は、長期的にははるかに簡単になります。私はPerl Cookbookと、もちろんLarry WallらによるThe Perl Programming Languageをお勧めします。

105
Nick

readはパイプから読み込みません(またはパイプがサブシェルを作成するため結果が失われる可能性があります)。ただし、Bashではhere文字列を使用できます。

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3

しかしlastpipeについては@ chepnerの答えを見てください。

37

これは別の選択肢です

$ read test < <(echo hello world)

$ echo $test
hello world
36
Steven Penny

私はBashのエキスパートではありませんが、なぜこれが提案されていないのでしょうか。

stdin=$(cat)

echo "$stdin"

それが私のために働くというワンライナー証明:

$ fortune | eval 'stdin=$(cat); echo "$stdin"'
29
djanowski

bash 4.2では、現在のシェルのパイプラインでサブシェルではなく最後のコマンドを実行することによって、コードを記述どおりに機能させることができるlastpipeオプションが導入されています。

shopt -s lastpipe
echo "hello world" | read test; echo test=$test
20
chepner

シェルコマンドからbash変数への暗黙のパイプの構文は次のとおりです。

var=$(command)

または

var=`command`

あなたの例では、代入文にデータをパイプしています。代入文は入力を期待していません。

12
mob

最初の試みはかなり近いものでした。このバリエーションはうまくいくはずです。

echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; };

そして出力は次のとおりです。

test =こんにちは世界

Testへの割り当てとエコーを囲むには、パイプの後に中括弧が必要です。

中括弧がないと、testへの代入(パイプの後)は1つのシェル内にあり、エコー "test = $ test"は別のシェル内にあり、その代入についてはわかりません。そのため、出力に "test = hello world"ではなく "test ="が表示されていました。

6
jbuhacoff

私の目でbashの標準入力から読み込むための最良の方法は以下のものです。これにより、入力が終了する前に行に作業を進めることができます。

while read LINE; do
    echo $LINE
done < /dev/stdin
5
Jaleks

落ちたのでメモを落としたい。このスレッドを見つけたのは、古いshスクリプトをPOSIX互換に書き直す必要があるためです。これは基本的に次のようなコードを書き換えることによってPOSIXによって導入されたpipe/subshel​​l問題を回避することを意味します。

some_command | read a b c

に:

read a b c << EOF
$(some_command)
EOF

そしてこのようなコード:

some_command |
while read a b c; do
    # something
done

に:

while read a b c; do
    # something
done << EOF
$(some_command)
EOF

しかし後者は空の入力に対して同じ振る舞いをしません。古い表記法ではwhileループは空の入力では入りませんが、POSIX表記法ではそうです!私はそれがEOFの前の改行のせいだと思うが、それは省略できない。より古い表記法のように振る舞うPOSIXコードはこのようになります:

while read a b c; do
    case $a in ("") break; esac
    # something
done << EOF
$(some_command)
EOF

ほとんどの場合、これで十分です。しかし、残念ながら、some_commandが空の行を表示する場合、これはまだ古い表記法とはまったく異なる動作をします。古い表記法ではwhile本体が実行され、POSIX表記では本体の前で中断します。

これを解決するためのアプローチは、次のようになります。

while read a b c; do
    case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac
    # something
done << EOF
$(some_command)
echo "something_guaranteed_not_to_be_printed_by_some_command"
EOF
4
hd-quadrat

代入を含む式に何かを渡すと、そのようには動作しません。

代わりに試してみてください。

test=$(echo "hello world"); echo test=$test
3
bta

次のコード

echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test )

これはうまくいきますが、パイプの後に別の新しいサブシェルを開きます。

echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; }

しません。


chepnars 'メソッドを使用するには、ジョブ制御を無効にする必要がありました(このコマンドを端末から実行していました)。

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

Bashマニュアルによると

最後のパイプ

設定され、かつジョブ制御がアクティブでない場合、シェルは現在のシェル環境でバックグラウンドで実行されていないパイプラインの最後のコマンドを実行します。

注:非対話型シェルではジョブ制御はデフォルトでオフになっているため、スクリプト内にset +mは必要ありません。

2
Jahid

私はあなたが標準入力から入力を受け取ることができるシェルスクリプトを書き込もうとしていたと思います。しかし、インラインで実行しようとしている間に、そのtest =変数を作成しようとして迷子になりました。インラインで行うのはあまり意味がないと思います。そのため、期待どおりに機能しません。

私は減らそうとしていました

$( ... | head -n $X | tail -n 1 )

さまざまな入力から特定の行を取得します。だから私は入力することができます...

cat program_file.c | line 34

だから私は標準入力から読み取ることができる小さなシェルプログラムが必要です。あなたのように。

22:14 ~ $ cat ~/bin/line 
#!/bin/sh

if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi
cat | head -n $1 | tail -n 1
22:16 ~ $ 

そこに行きます。

1
shigazaru

PIPEとコマンドライン引数からデータを読み取ることができるスマートスクリプトです。

#!/bin/bash
if [[ -p /proc/self/fd/0 ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "ARGS=$@"

出力:

$ bash test arg1 arg2
ARGS=arg1 arg2

$ echo pipe_data1 | bash test arg1 arg2
PIPE=pipe_data1
ARGS=arg1 arg2

説明:スクリプトがパイプ経由でデータを受け取ると、標準入力/ proc/self/fd/0はパイプへのシンボリックリンクになります。

/proc/self/fd/0 -> pipe:[155938]

そうでなければ、現在の端末を指します。

/proc/self/fd/0 -> /dev/pts/5

Bashのif -pオプションは、それがパイプであるかどうかを確認できます。

cat -はfrom stdinから読み取ります。

stdinが存在しないときにcat -を使用すると、それは永遠に待機します。そのため、if条件内に入れます。

0
Seff