シェルから入力を1行ずつ(空白行を含む)処理します。

シェルから入力を1行ずつ(空白行を含む)処理します。

シェルスクリプトでは、コマンドラインの出力を1行ずつ解析する必要があります。出力には空行を含めることができ、これは関連しています。私はbashではなくashを使用しているので、プロセスの置き換えに頼ることはできません。私はこれを試しています:

    OUT=`my_command`
    IFS=$'\n'              
    i=1
    for line in $OUT; do
        echo $line                                          
        eval VAL$i=$line             
        i=$((i+1))             
    done

ただし、これは$ OUTから空白行を削除します。空行を処理するにはどうすればよいですか?

答え1


動作するシェルループは次のとおりです。

set -f -- "-$-"' -- "$@" '"
    ${IFS+IFS=\$2} ${out+out=\$3}" \
    "$IFS" "$out" "$@"
IFS='
';for out in $(my command|grep -n '.\|')
do  : something with "${out%%:*}" and "${out#*:}"
done
unset IFS out
eval "set +f $1"
shift 3

空白行がないように並べ替えます。最初はこの目的で提案しましたが、もう一度nl考えると、nl論理ページ区切り文字が入力に表示され、出力が歪む可能性があります。(実際には空の行で終わり、番号付きの行に影響しますが、他の目的には非常に便利な機能です。)。外のいいえ論理的な改ページを解釈すると、grep -n '.\|'結果は同じです。

このようなパイプラインといくつかのパラメータ置換を使用すると、空白行の問題を回避できるだけでなく、各反復に同時に番号が事前に割り当てられます。(現在の繰り返し回数は、ユーザーに与えられた各値の先頭にあり、$outその後に:

このset ... IFS=...行の目的は、シェルの状態が変更前の位置に復元されるようにすることです。関数ではなくスクリプトの場合、これらの予防措置は過剰になる可能性があります。それでもしなければならない少なくとも set -fシェルを分割する前に、意図しない入力エラーを避けてください。


しかし、プロセスの代替(d)ashについては<()

それからDebianで( dash)派生ash (例えばbusybox ashファイル記述子のリンク処理と、ここにある文書がよく知られているプロセス置換の<(より良い代替手段を提供することがわかります)

次の例を考えてみましょう。

exec "$((i=3))"<<R "$((o=4))"<<W 3<>/dev/fd/3 4<>/dev/fd/4
R
W

sed -u 's/.*/here I am./' <&"$o" >&"$i" &
echo "hey...sed?" >&"$o"
head -n1          <&"$i"

なぜならデリバティブがここにあるからですdash。代わりに匿名パイプを使用した文書です。(ほとんどの他の殻と同様)これは、通常のファイルと一緒に使用し、/dev/fd/[num]Linuxシステムからのリンクは、ファイル記述子のバックアップファイルを参照する間接的な方法を提供するためです。(ファイルシステムで参照できない場合でも - 例:匿名パイプ)上記の手順は、呼び出すことができるいくつかのシェルを設定する非常に簡単な方法を示しています。共同プロセス。たとえば、Linuxシステムbusybox ashまたはLinuxシステムでdash(他の人については保証しません)上記の内容は次のように印刷されます。

here I am.

$i...そしてシェルがファイル記述子を閉じるまでそれを続けます$o。バッファリングの問題を回避するために、-uGNUが提供するnbufferedスイッチを使用しますが、スイッチがなくても必要に応じてバックグラウンドプロセスへの入力をフィルタリングし、sedパイプのバイト単位で時間を測定できます。conv=sync\0NULdd

sed対話型シェルで上記の内容を一般的に使用する方法は次のとおりです。

: & SEDD=$$$!
sed -un "/^$SEDD$/!H;//!d;s///;x;/\n/!q;s///;s/%/&&/g;l" <&"$o" >&"$i" &

...その背景aは、sed一意の区切り文字が表示されるまで入力を読み取り、保存します。この時点で、以前のバッファ%にあった内容を倍増し、H匿名パイプexec形式に優しいCで単一文字列の定義文字列としてprintfを印刷します。 line - または結果が80文字を超える場合は、複数行に表示されます。最後のエントリ(GNUの場合sed)はw /として扱うことができます。これは絶対に改行しないように指示sed -l0するスイッチです。または次のようになります。sed\

fmt=
while IFS= read -r r <&"$i" 
      case $r in (*$) 
      ! fmt=$fmt$r ;;esac
do    fmt=$fmt${r%?} 
done

とにかく、私は次のようにバッファを構築しました。

echo something at sed >&"$o"
printf '%s\n' more '\lines%' at sed "$SEDD" >&"$o"

そしてそれを引っ張ると次のようになります。

IFS= read -r fmt <&"$i"

次のことは次のとおりです$fmt

printf %s\\n "$fmt"
something at sed\nmore\n\\lines%%\nat\nsed$

sed印刷できない文字のCスタイルの8進エスケープも行われます。

だから私はそれを次のように使用できます...

printf "%d\n${fmt%$}\n" 1 2 3

...印刷...

1
something at sed
more
\lines%
at
sed
2
something at sed
more
\lines%
at
sed
3
something at sed
more
\lines%
at
sed

sedたとえば、必要に応じてパイプをシャットダウンして解放できます。

printf %s\\n "$SEDD" "$SEDD" >&"$o"
exec "$i">&- "$o">&-

fdを一度だけ使用するのではなく、fdを保持すると、この種の操作を実行できます。必要なだけバックパイプを維持することができ、カーネルがそのリンクを所有するプロセスを除くどのプロセスにもこれらのリンクを提供しないため、名前付きパイプよりも安全です。(あなたの殻)、名前付きパイプは見つけることができますが、(盗聴/盗難も含む)参照されたファイルにアクセスできるすべてのプロセスは、ファイルシステムでこれを実行できます。

プロセスの置き換えを実行するシェルで同様の操作を実行するには、おそらく次のようにします。

eval "exec [num]<>"<(:)

...しかし試してみたことはありません。

答え2

この方法:

i=1
my_command | while read line; do
    echo $line
    eval VAL$i="$line"
    i=$((i+1))
done

コマンドの出力は1行ずつ読み取られるため、最初に変数に保存する必要なく、行(空行を含む)が個別に処理されます。出力はメモリに2回保存されず、bashスクリプトはコマンドが完了した直後ではなく、出力直後に行処理を開始できるため、メモリも節約されます。

編集:VALx変数は上記のサブシェルに設定されているため、変更する必要があります。

eval `i=1
my_command | while read line; do
    # echo $line
    echo "VAL$i=\"$line\""
    i=$((i+1))
done`

本当に必要な場合でも、echo $lineいくつかの修正が必要です。

答え3

ここではドキュメントを使用してこれを実装しました。

    i=1
    while read -r line; do
        eval VAL$i=\$line
        i=$((i+1))
    done <<EOF
$(my_command)
EOF

良い結果。

更新済み:Gillesとmikeservからのフィードバックが統合されました。

関連情報