コマンドの複数行出力を含む変数があります。変数から行ごとに出力を読み取る最も効率的な方法は何ですか?
例えば:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
コマンドの複数行出力を含む変数があります。変数から行ごとに出力を読み取る最も効率的な方法は何ですか?
例えば:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
回答:
プロセス置換で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
に名前を付けると、混乱を引き起こす可能性があることに注意してください。これは一般的なシェルコマンドの名前でもあるためです。
echo
しましたprintf %s
。そうすることで、スクリプトが非飼いならされた入力でも機能するようになりました。
/tmp
しますが、一時的な作業ファイルを作成できることに依存しているため、ヒエストリングは常にディレクトリが書き込み可能である必要があることに注意する必要があります。制限されたシステム上で/tmp
読み取り専用(および変更不可)になった場合は、printf
パイプなどの代替ソリューションを使用できる可能性があります。
printf "%s\n" "$var" | while IFS= read -r line
コマンドラインの出力を行ごとに処理するには(説明):
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の結果に発生します$foo
。for
ループは、すべての部分の上に作用する$(jobs)
コマンド出力のすべての非空行です。最後に、グロビングとIFS
設定を復元します。
local IFS=something
。グローバルスコープの値には影響しません。IIRCはunset IFS
、デフォルトに戻しません(また、事前にデフォルトではなかった場合は確かに機能しません)。
問題: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=
while read
bashでループにパイプすると、whileループはサブシェルにあるため、変数はグローバルではありません。 while read;do ;done <<< "$var"
ループ本体をサブシェルではありません。(最近のbashには、cmd | while
kshが常に持っていたように、ループの本体をサブシェルに入れないオプションがあります。)
最近のbashバージョンでは、mapfile
またはreadarray
を使用してコマンド出力を効率的に配列に読み込みます
$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305
免責事項:恐ろしい例ですが、自分でlsを使用するよりも良いコマンドを思いつくことができます
readarray
関数を入れて、その関数を数回呼び出します。
<<<を使用して、改行で区切られたデータを含む変数から単純に読み取ることができます。
while read -r line
do
echo "A line of input: $line"
done <<<"$lines"
while IFS= read
... を使用します。\解釈を防止したい場合はread -r