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

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

例:

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という名前を付けると、一般的なシェルコマンドの名前でもあるため、混乱が生じる可能性があることに注意してください。

コメント

  • すべての空白を保持する場合は、while IFS= read ….を使用します。 \の解釈を防ぎたい場合は、read -r
  • ‘ fred.bearが言及したポイントを修正し、echo

、これにより、スクリプトは、飼いならされていない入力でも機能します。

  • 複数行の変数から読み取るには、printfからのパイプよりもherestringの方が適しています(l0b0

    の回答)。

  • @ata私は’これを聞いたことがありますが” Preferred “十分な頻度で、herestringは常に/tmpディレクトリが書き込み可能である必要があることに注意する必要があります。一時的な作業ファイルを作成できます。 /tmpが読み取り専用である(そしてあなたが変更できない)制限されたシステムにいることに気付いた場合は、別のソリューションを使用する可能性に満足するでしょう。 g。 printfパイプを使用します。
  • 2番目の例では、複数行の変数に’が含まれていない場合末尾の改行は、最後の要素を失います。次のように変更します:printf "%s\n" "$var" | while IFS= read -r line
  • 回答

    処理するにはコマンドラインを1行ずつ出力(説明):

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

    すでに変数内のデータ:

    printf %s "$foo" | … 

    printf %s "$foo"echo "$foo"ですが、$fooを文字通り出力しますが、echo "$foo"$fooをオプションとして解釈する場合があります-で始まり、一部のシェルでは$fooのバックスラッシュシーケンスを展開する可能性がある場合はechoコマンドに変換します。

    一部のシェル(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 } 

    If空でない行を操作するだけで十分であり、入力はそれほど大きくありません。単語分割を使用できます。

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

    説明:設定IFSを単一の改行にすると、単語の分割は改行でのみ発生します(デフォルト設定の空白文字とは対照的です)。 set -fは、グロブ(つまり、ワイルドカード展開)をオフにします。これは、コマンド置換$(jobs)または変数置換

    forループは、コマンド出力のすべての空でない行である$(jobs)のすべての部分に作用します。最後に、globbingとIFSの設定をデフォルトと同等の値に復元します。

    コメント

    • IFSの設定とIFSの設定解除に問題がありました。正しいことは、IFSの古い値を保存し、IFSをその古い値に戻すことだと思います。私は’ bashの専門家ではありませんが、私の経験では、これにより元の動作に戻ることができます。
    • @BjornRoche:関数内で

      。 ‘グローバルスコープの値には影響しません。 IIRC、unset IFSは’デフォルトに戻りません(そして確かに’ ‘事前にデフォルトでなかった場合は機能します)。

    • setを途中で使用するかどうか疑問に思います。最後の例に示されているのは正しいです。コードスニペットは、set +fが最初にアクティブであったことを前提としているため、最後にその設定を復元します。ただし、この仮定は間違っている可能性があります。 set -fが最初にアクティブだった場合はどうなりますか?
    • @Binarusデフォルトと同等の設定のみを復元します。実際、元の設定を復元したい場合は、さらに作業を行う必要があります。 set -fの場合、元の$-を保存します。 IFSの場合、’がividを持っていない場合は、’面倒です。 = “6d857e61ef”>

    そして未設定のケースをサポートしたい;復元する場合は、IFSが設定されたままになるように不変条件を適用することをお勧めします。

  • localを使用するとlocal -はシェルオプションをローカルにし、local IFSIFSローカル。残念ながら、localは関数内でのみ有効であるため、コードの再構築が必要になります。 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= 

    コメント

    • どうもありがとうございました!!上記の解決策はすべて失敗しました。
    • bashのwhile readループにパイプすることは、whileループがサブシェルにあることを意味するため、変数は’ tグローバル。 while read;do ;done <<< "$var"は、ループ本体をサブシェルではなくします。 (最近のbashには、kshが常に持っていたように、cmd | whileループの本体をサブシェルに配置しないオプションがあります。)
    • この関連記事
    • 同様の状況で、IFSを正しく扱うのは驚くほど難しいことがわかりました。このソリューションにも問題があります。最初にIFSがまったく設定されていない(つまり、定義されていない)場合はどうなりますか?それは、そのコードスニペットの後にすべての場合に定義されます。これは’正しくないようです。

    回答

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

    参照:

    コメント

    • -rも良い考えです。 \` interpretation... (it is in your links, but its probably worth mentioning, just to round out your IFS = `(空白の喪失を防ぐために不可欠です)
    • このソリューションのみが機能しました。ありがとうブラ。
    • ‘このソリューションには、@ dogbane ‘ dogbaneへのコメントに記載されているのと同じ問題があります。

    の答え?変数の最後の行が改行文字で終了していない場合はどうなりますか?

  • この回答は、変数のコンテンツをwhile readにフィードする最もクリーンな方法を提供します。構築します。
  • 回答

    最近のbashバージョンでは、mapfileまたはreadarrayコマンド出力を配列に効率的に読み取る

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

    免責事項:恐ろしい例ですが、大げさに言うことができます自分よりも優れたコマンドを使用してください

    コメント

    • ‘いい方法です、しかし、私のシステム上の一時ファイルには/ var / tmpが散らばっています。とにかく+1
    • @eugene:’おもしろい。それはどのシステム(ディストリビューション/ OS)にありますか?
    • ‘ s FreeBSD 8.再現方法:put readarrayを実行し、関数を数回呼び出します。
    • いいですね、@ sehe。 +1

    回答

    この問題を解決するための一般的なパターンは他の回答に記載されています。

    ただし、どの程度効率的かはわかりませんが、アプローチを追加したいと思います。ただし、(少なくとも私にとっては)非常に理解しやすく、元の変数(readは、問題の変数の末尾に改行が含まれている必要があるため、変数を追加します)、サブシェルを作成しません(すべてのパイプベースのソリューションが行います)、ここでは使用しません-文字列(独自の問題があります)、およびプロセス置換を使用しません(それに反対するものはありませんが、理解するのが少し難しい場合があります)。

    実際、の統合REはめったに使用されません。移植性がない可能性がありますが、OPがbashタグを使用しているため、停止することはありません:-)

    #!/bin/bash function ProcessLine() { printf "%s" "$1" } function ProcessText1() { local Text=$1 local Pattern=$"^([^\n]*\n)(.*)$" while [[ "$Text" =~ $Pattern ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done ProcessLine "$Text" } function ProcessText2() { local Text=$1 local Pattern=$"^([^\n]*\n)(.*)$" while [[ "$Text" =~ $Pattern ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done } function ProcessText3() { local Text=$1 local Pattern=$"^([^\n]*\n?)(.*)$" while [[ ("$Text" != "") && ("$Text" =~ $Pattern) ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done } MyVar1=$"a1\nb1\nc1\n" MyVar2=$"a2\n\nb2\nc2" MyVar3=$"a3\nb3\nc3" ProcessText1 "$MyVar1" ProcessText1 "$MyVar2" ProcessText1 "$MyVar3" 

    出力:

    root@cerberus:~/scripts# ./test4 a1 b1 c1 a2 b2 c2a3 b3 c3root@cerberus:~/scripts# 

    いくつかの注意事項:

    動作は、を使用します。上記の例では、ProcessText1を使用しました。

    注意

    • ProcessText1は行末に新行文字を保持します
    • ProcessText1は変数の最後の行(テキストを含む)を処理しますc3その行には末尾のnewline文字が含まれていません。末尾のnewlineがないため、スクリプト実行後のコマンドプロンプトが最後に追加されます。出力から分離されていない変数の行。
    • ProcessText1は常に、変数の最後の新しい行と変数の終わりの間の部分を行と見なします。 、空であっても;もちろん、その行は、空であるかどうかに関係なく、末尾に改行文字がありません。つまり、変数の最後の文字が改行であっても、ProcessText1は、その最後の改行と変数の終わりの間の空の部分(null文字列)を(まだ空の)行とそれを行処理に渡します。 ProcessLineへの2回目の呼び出しを適切な空のチェック条件にラップすることで、この動作を簡単に防ぐことができます。ただし、そのままにしておく方が論理的だと思います。

    ProcessText1ProcessLineは2つの場所にあります。これは、行を処理する関数を呼び出す代わりに、行を直接処理するコードのブロックをそこに配置する場合は不快な場合があります。エラーが発生しやすいコードを繰り返す必要があります。

    対照的に、ProcessText3は行を処理するか、それぞれの関数を1か所でのみ呼び出し、置換を行います。コードブロックによる関数呼び出しは簡単です。これには、1つではなく2つのwhile条件が必要です。実装の違いを除けば、ProcessText3ProcessText1とまったく同じように動作しますが、最後の改行文字の間の部分は考慮されません。変数と、その部分が空の場合は行としての変数の終わり。つまり、ProcessText3は、その改行文字が変数の最後の文字である場合、変数の最後の改行文字の後に行処理に入りません。

    ProcessText2ProcessText1と同じように機能しますが、行の末尾に改行文字が必要です。つまり、変数の最後の改行文字と変数の終わりの間の部分は、行とは見なされず、静かに破棄されます。 したがって、変数に改行文字が含まれていない場合、行処理はまったく行われません。

    上記の他のソリューションよりもそのアプローチが好きですが、おそらく何かを見逃しています( bashプログラミングであり、他のシェルにはあまり関心がありません。

    回答

    < < <を使用すると、改行を含む変数から簡単に読み取ることができます。 -分離されたデータ:

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

    コメント

    • Unixへようこそ& Linux!これは本質的に4年前の答えと重複しています。何か新しい貢献がない限り、回答を投稿しないでください。

    コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です