bashの変数から行ごとに読み取るにはどうすればよいですか?


49

コマンドの複数行出力を含む変数があります。変数から行ごとに出力を読み取る最も効率的な方法は何ですか?

例えば:

jobs="$(jobs)"
if [ "$jobs" ]; then
    # read lines from $jobs
fi

回答:


65

プロセス置換でwhileループを使用できます。

while read -r line
do
    echo "$line"
done < <(jobs)

複数行の変数を読み取る最適な方法は、空白のIFS変数を設定しprintf、変数に後続の改行を設定することです。

# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
   echo "$line"
done < <(printf '%s\n' "$var")

注:shellcheck sc2031では、サブシェルの作成を[微妙に]回避するために、パイプよりもプロセスの置換を使用することをお勧めします。

また、変数jobsに名前を付けると、混乱を引き起こす可能性があることに注意してください。これは一般的なシェルコマンドの名前でもあるためです。


3
すべての空白を保持したい場合はwhile IFS= read... を使用します。\解釈を防止したい場合はread -r
-Peter.O

fred.bearが言及したポイントを修正し、に変更echoしましたprintf %s。そうすることで、スクリプトが非飼いならされた入力でも機能するようになりました。
ジル 'SO-悪であるのをやめる'

複数行の変数から読み取るには、printfからパイピングするよりもヒエストリングが適しています(l0b0の回答を参照)。
ATA

1
@ataこの「好まれる」ことはよく耳に/tmpしますが、一時的な作業ファイルを作成できることに依存しているため、ヒエストリングは常にディレクトリが書き込み可能である必要があることに注意する必要があります。制限されたシステム上で/tmp読み取り専用(および変更不可)になった場合は、printfパイプなどの代替ソリューションを使用できる可能性があります。
構文エラー14

1
2番目の例では、複数行の変数に末尾の改行が含まれていない場合、最後の要素が失われます。:それを変更printf "%s\n" "$var" | while IFS= read -r line
デイヴィッドH.ベネット

26

コマンドラインの出力を行ごとに処理するには(説明):

jobs |
while IFS= read -r line; do
  process "$line"
done

既に変数にデータがある場合:

printf %s "$foo" | 

printf %s "$foo"はとほぼ同じですがecho "$foo"$foo文字どおりに出力echo "$foo"$fooれますが、で始まる場合はechoコマンドのオプションとして解釈さ-$foo、一部のシェルではバックスラッシュシーケンスが展開される場合があります。

一部のシェル(ash、bash、pdksh、kshまたはzshではない)では、パイプラインの右側が別のプロセスで実行されるため、ループで設定した変数はすべて失われることに注意してください。たとえば、次の行カウントスクリプトは、これらのシェルで0を出力します。

n=0
printf %s "$foo" |
while IFS= read -r line; do
  n=$(($n + 1))
done
echo $n

回避策は、スクリプトの残りの部分(または少なくとも$nループからの値を必要とする部分)をコマンドリストに入れることです。

n=0
printf %s "$foo" | {
  while IFS= read -r line; do
    n=$(($n + 1))
  done
  echo $n
}

空でない行に作用するだけで十分であり、入力が大きくない場合は、単語分割を使用できます。

IFS='
'
set -f
for line in $(jobs); do
  # process line
done
set +f
unset IFS

説明:IFS単一の改行を設定すると、単語の分割は改行でのみ行われます(デフォルト設定の空白文字とは異なります)。set -fグロビング(つまり、ワイルドカード拡張)をオフにします。これは、コマンド置換$(jobs)または変数substitutinoの結果に発生します$fooforループは、すべての部分の上に作用する$(jobs)コマンド出力のすべての非空行です。最後に、グロビングとIFS設定を復元します。


IFSの設定とIFSの設定解除に問題があります。正しいことは、IFSの古い値を保存し、IFSをその古い値に戻すことだと思います。私はbashの専門家ではありませんが、私の経験では、これにより元の動作に戻ります。
ビヨンロシュ

1
@BjornRoche:関数内で使用しますlocal IFS=something。グローバルスコープの値には影響しません。IIRCはunset IFS、デフォルトに戻しません(また、事前にデフォルトではなかった場合は確かに機能しません)。
ピーターコーデス

14

問題:whileループを使用すると、サブシェルで実行され、すべての変数が失われます。解決策:forループを使用する

# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'

for line in $variableWithSeveralLines; do
 echo "$line"

 # return IFS back if you need to split new line by spaces:
 IFS=$IFS_BAK
 IFS_BAK=
 lineConvertedToArraySplittedBySpaces=( $line )
 echo "{lineConvertedToArraySplittedBySpaces[0]}"
 # return IFS back to newline for "for" loop
 IFS_BAK=$IFS
 IFS=$'\n'

done 

# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=

どうもありがとうございます!!上記の解決策はすべて失敗しました。
hax0r_n_code

while readbashでループにパイプすると、whileループはサブシェルにあるため、変数はグローバルではありません。 while read;do ;done <<< "$var"ループ本体をサブシェルではありません。(最近のbashには、cmd | whilekshが常に持っていたように、ループの本体をサブシェルに入れないオプションがあります。)
ピーターコーデス

この関連記事も参照してください。
ワイルドカード

10

最近のbashバージョンでは、mapfileまたはreadarrayを使用してコマンド出力を効率的に配列に読み込みます

$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305

免責事項:恐ろしい例ですが、自分でlsを使用するよりも良いコマンドを思いつくことができます


良い方法ですが、私のシステム上の/ var / tmpに一時ファイルが散らばっています。とにかく+1
ユージンヤーマッシュ

@ユージーン:それは面白いです。どのシステム(distro / OS)がオンになっていますか?
sehe

FreeBSD 8です。再現方法:readarray関数を入れて、その関数を数回呼び出します。
ユージンヤーマッシュ

いいね、@ sehe。+1
テレサeジュニア

7
jobs="$(jobs)"
while IFS= read
do
    echo $REPLY
done <<< "$jobs"

参照:


3
-rも良いアイデアです。\` interpretation... (it is in your links, but its probably worth mentioning, just to round out your IFS = `を防ぎます(これは空白をなくすのに不可欠です)
Peter.O

-1

<<<を使用して、改行で区切られたデータを含む変数から単純に読み取ることができます。

while read -r line
do 
  echo "A line of input: $line"
done <<<"$lines"

1
Unix&Linuxへようこそ!これは本質的に4年前の回答と同じです。貢献する新しいものがない限り、回答を投稿しないでください。
G-Manは「Reinstate Monica」と言います
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.