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


205

パイプで送られるstdinからのデータをbashで処理しようとしていますが、うまくいきません。私が意味するのは、次の作業のどれもではない:

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"もうまくいきません。


1
あなたの例.. echo "hello world" | テストを読む; echo test = $ testは私にとってはうまくいきました。結果:test = hello world; これを実行している環境は何ですか?私はbashの4.2 ..使用しています
alex.pilon

1回の読み取りで複数行が必要ですか?例では1行しか表示されていませんが、問題の説明は不明確です。
Charles Duffy

2
@ alex.pilon、私はBashバージョン4.2.25を実行していますが、彼の例も私にはうまくいきません。それはBashランタイムオプションまたは環境変数の問題なのでしょうか?この例はShでも動作しないので、BashはShとの互換性を試すことができるのでしょうか?
Hibou57 14

2
@ Hibou57-bash 4.3.25でこれをもう一度試したところ、機能しなくなりました。私の記憶はこれについて曖昧であり、それを機能させるために私が何をしたのかわかりません。
alex.pilon 2014年

2
@ Hibou57 @ alex.pilonパイプ内の最後のcmdがでbash4> = 4.2でvarsの影響を与えなければならないshopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
IMZ -イワンZakharyaschev

回答:


162

使用する

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

あなたはできるトリックread、このようなパイプから受け入れるように:

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

または、次のような関数を記述します。

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

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

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


代わりにこれを行うことで、割り当てを最後にすることができます: `test =` `echo" hello world "| {テストを読む; echo $ test; } `` `
Compholio 2012年

2
:のは、(明らかにこのマークアップでバッククォートをエスケープすることは楽しいです)もう一度、これを試してみましょうtest=`echo "hello world" | { read test; echo $test; }`
Compholio

これらの2つのコマンドをグループ化する{}代わりに使用した理由を尋ねてもいい()ですか?
ユルゲンポール

4
トリックはread、パイプから入力を取得することではなく、を実行する同じシェルで変数を使用することreadです。
chepner 2013年

1
bash permission deniedこのソリューションを使用しようとしたときに得ました。私の場合はかなり異なっているが、私はどこでもそれのための答えを見つけることができませんでした、どのような私のために働いたことは(別の例が、同様の用法)でした:pip install -U echo $(ls -t *.py | head -1)。場合によっては、誰かが同様の問題を抱えて、私のようにこの答えに出くわすことがあります。
ivan_bilan

111

大量のデータを読み取り、各行を個別に処理したい場合は、次のようなものを使用できます。

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らによるPerlプログラミング言語もお勧めです。


8
「代わりに」が正しい方法です。UUoCもサブシェルもありません。BashFAQ / 024を参照してください。
追って通知があるまで一時停止。

43

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

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

ただし、に関する情報については、@ chepnerの回答を参照してくださいlastpipe


わかりやすくシンプルなワンライナー。この回答にはもっと賛成票が必要です。
デビッドパークス

42

これは別のオプションです

$ read test < <(echo hello world)

$ echo $test
hello world

24
重要な利点<(..)を超える持っては$(..)ということで<(..)、すぐにコマンドとして、それが実行するように、呼び出し元に戻り、それぞれの行は、それが利用できるようになります。 $(..)ただし、は、コマンドが完了するまで待機し、すべての出力を生成してから、呼び出し元が出力を使用できるようにします。
デレクマハール2013

37

私はBashの専門家ではありませんが、なぜこれが提案されていないのでしょうか。

stdin=$(cat)

echo "$stdin"

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

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

4
これはおそらく、「read」がbashコマンドであり、catがサブプロセスで起動される個別のバイナリであるため、効率が悪いためです。
dj_segfault 2013

10
時々単純さと明快さの切り札の効率:)
ロンド

5
間違いなく最も簡単な答え
drwatsoncode 2015年

私がこれに遭遇した問題は、パイプが使用されていない場合、スクリプトがハングすることです。
デールアンダーソン

@DaleAまあ、もちろん。標準入力から読み取ろうとするプログラムは、何も入力されないと「ハング」します。
djanowski、2015年

27

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

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

2
ああ!とても良い、これ。インタラクティブシェルでテストする場合、「set + m」(。shスクリプトでは不要)
XXL

14

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

var=$(command)

または

var=`command`

例では、データを割り当てステートメントにパイプしていますが、入力はありません。


4
$()は簡単にネストできるからです。JAVA_DIR = $(dirname $(readlink -f $(which java)))で考え、 `で試してください。あなたは3回脱出する必要があります!
albfan 2012年

8

最初の試みはかなり近いものでした。このバリエーションは機能するはずです:

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

そして出力は:

test = hello world

テストへの割り当てとエコーを囲むには、パイプの後にブレースが必要です。

中括弧がない場合、テストへの割り当て(パイプの後)は1つのシェルにあり、エコー "test = $ test"はその割り当てを知らない別のシェルにあります。そのため、出力に「test = hello world」ではなく「test =」が表示されていました。


8

私の目には、bashでstdinから読み取る最良の方法は次の方法です。これにより、入力が終了する前に行で作業することもできます。

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

1
私はこれを見つける前にほとんど夢中になりました。共有してくれてありがとう!
Samy Dindane、2016

6

落ちるのでメモを書きたいと思います。古いスレッドをPOSIX互換にするために書き直す必要があるため、このスレッドを見つけました。これは基本的に、次のようなコードを書き直すことにより、POSIXによって導入されたパイプ/サブシェルの問題を回避することを意味します。

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

5

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

説明:スクリプトがパイプを介してデータを受信すると、stdin / proc / self / fd / 0はパイプへのシンボリックリンクになります。

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

そうでない場合は、現在の端末をポイントします。

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

bash [[ -pオプションは、パイプであるかどうかを確認できます。

cat -から読み取りますstdin

がない場合に使用するとcat -stdin永久に待機するため、if条件内に入れます。


また、使用することができます/dev/stdinへのリンクである/proc/self/fd/0
エリーG.

3

代入を含む式に何かをパイプしても、そのようには動作しません。

代わりに、試してください:

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

2

次のコード:

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

バッシュマニュアルは言う

ラストパイプ

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

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


1

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

削減しようとしていました

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

さまざまな入力から特定の行を取得します。入力できるように...

cat program_file.c | line 34

そのため、stdinから読み取れる小さなシェルプログラムが必要です。あなたのように。

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

これはどう:

echo "hello world" | echo test=$(cat)

基本的には以下の私の答えからのだまし。
djanowski、2015年

たぶん、私は少しクリーンで元の質問に投稿されたコードに近いと主張します。
ビングル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.