通常の慣行では、反復ごとに設定を繰り返さないようにするために、whisループの外側にIFSの設定を配置するようです...これは、このサルの場合までそうであったように、これは単なる習慣的な「サル見る、サルする」スタイルですか?私はman readを読みますか、またはここに微妙な(または明らかに明白な)トラップがありませんか?
罠は
IFS=; while read..
ループの外側のシェル環境全体にIFS
を設定しますが、
while IFS= read
read
invocation(Bourne Shellを除く)に対してのみ再定義します。あなたはのようなループをしていることを確認することができます
while IFS= read xxx; ... done
そのようなループの後、echo "blabalbla $IFS ooooooo"
は
blabalbla
ooooooo
後に
IFS=; read xxx; ... done
IFS
staysが再定義されました:echo "blabalbla $IFS ooooooo"
が出力されます
blabalbla ooooooo
したがって、2番目の形式を使用する場合は、必ずリセットする必要があります:IFS=$' \t\n'
。
この質問の2番目の部分 ここにマージされました なので、ここから関連する回答を削除しました。
入念に作成された入力テキストを使用した例を見てみましょう。
text=' hello world\
foo\bar'
これは2行です。最初の行はスペースで始まり、バックスラッシュで終わります。最初に、 read
の周りに何も注意を払わずに何が起こるかを見てみましょう(ただし、printf '%s\n' "$text"
を使用して、$text
を拡張のリスクなしに注意深く印刷します)。 (以下、$
はシェルプロンプトです。)
$ printf '%s\n' "$text" |
while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]
read
バックスラッシュを削除します:backslash-newlineは改行を無視し、backslash-anythingは最初のバックスラッシュを無視します。バックスラッシュが特別に扱われるのを避けるために、read -r
を使用します。
$ printf '%s\n' "$text" |
while read -r line; do printf '%s\n' "[$line]"; done
[hello world\]
[foo\bar]
それはましです、予想通り2行あります。 2行にはほぼ目的のコンテンツが含まれています。hello
とworld
の間の二重スペースは、line
変数内にあるため保持されています。一方、最初のスペースはすっかり食べ尽くされていました。これは、read
が変数を渡すのと同じ数の単語を読み取るためです。ただし、最後の変数には行の残りが含まれますが、最初の単語から始まります。つまり、最初のスペースは破棄されます。
したがって、各行を文字どおりに読み取るには、 Word splitting が実行されていないことを確認する必要があります。 IFS
variable を空の値に設定してこれを行います。
$ printf '%s\n' "$text" |
while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello world\]
[foo\bar]
IFS
をread
ビルトインの期間に具体的に設定する方法に注意してください。 IFS= read -r line
は、IFS
の実行専用に環境変数read
を(空の値に)設定します。これは、一般的な 単純なコマンド 構文のインスタンスです:変数割り当てのシーケンス(空の可能性があります)の後にコマンド名とその引数が続きます(また、いつでもリダイレクトをスローできます)。 read
は組み込みなので、変数が実際に外部プロセスの環境に到達することはありません。それにもかかわらず、read
が実行されている限り、$IFS
の値はそこで割り当てられますwhat。 read
は 特別な組み込み ではないので、割り当てはその期間だけ持続します。
したがって、IFS
の値を変更しないように注意して、それに依存する可能性のある他の命令について説明します。このコードは、周囲のコードがIFS
を最初に設定したものに関係なく機能し、ループ内のコードがIFS
に依存している場合でも問題は発生しません。
コロンで区切られたパスでファイルを検索するこのコードスニペットとは対照的です。ファイル名のリストは、1行に1つずつ、ファイルから読み取られます。
IFS=":"; set -f
while IFS= read -r name; do
for dir in $PATH; do
## At this point, "$IFS" is still ":"
if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
done
done <filenames.txt
ループがwhile IFS=; read -r name; do …
の場合、for dir in $PATH
は$PATH
をコロンで区切られたコンポーネントに分割しません。コードがIFS=; while read …
の場合、ループ本体でIFS
が:
に設定されていないことがさらに明白になります。
もちろん、IFS
の実行後にread
の値を復元することは可能です。ただし、そのためには以前の値を知る必要があり、これは追加の労力です。 IFS= read
は簡単な方法です(便利なことに、最短の方法でもあります)。
¹ そして、トラップが実行されている間に、トラップされた信号によってread
が中断された場合—これはPOSIXでは指定されておらず、実際にはシェルに依存しています。
(すでに明らかにされている)IFS
スコープ指定の違いとは別に、while IFS='' read
、IFS=''; while read
およびwhile IFS=''; read
イディオム(コマンドごとvsスクリプト/シェル全体のIFS
変数スコープ)、持ち帰りのレッスンは、リーダーを失うことです そして IFS変数がスペースを含む(含む)場合、入力行の末尾のスペース。
ファイルパスが処理されている場合、これはかなり深刻な結果をもたらす可能性があります。
したがって、IFS変数を空の文字列に設定することは、行の先頭と末尾の空白が削除されないことが保証されるため、決して悪い考えではありません。
参照: Bash、IFSを使用してファイルから行ごとに読み取る
(
shopt -s nullglob
touch ' file with spaces '
IFS=$' \t\n' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
IFS='' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
)
ユゼムの答え に触発されました
IFS
を実際の文字に設定したい場合、これは私にとってうまくいきました
iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
echo "$line"
done