「IFS =」ではなく「while IFS = read」が頻繁に使用されるのはなぜですか。読みながら..`?


81

通常の慣行では、IFSの設定はwhileループの外側に置かれ、反復ごとに設定を繰り返さないように思われます。私はman readを読んだか、ここに微妙な(または露骨に明白な)トラップがありませんか?

回答:


82

わなは

IFS=; while read..

IFSループ外のシェル環境全体に設定しますが、

while IFS= read

read呼び出しに対してのみ再定義します(Bourneシェルを除く)。次のようなループを行うことを確認できます

while IFS= read xxx; ... done

そのようなループの後、echo "blabalbla $IFS ooooooo"印刷します

blabalbla
 ooooooo

一方、後に

IFS=; read xxx; ... done

再定義されたIFS 滞在:現在echo "blabalbla $IFS ooooooo"印刷

blabalbla  ooooooo

したがって、2番目のフォームを使用する場合は、リセットすることを忘れないでくださいIFS=$' \t\n'


この質問の2番目の部分はここ統合されているため、関連する回答をここから削除しました。


さて、そうです潜在的な「罠」は、外側IFSをリセットするために無視していることを...しかし、私は進行中何か他のものもあるかしら...私はかなり無我夢中で、ここで物事をテストすることがきない、と私はしましたwhileのコマンドリストでIFSを設定すると、コロンが続くかどうかに応じて、かなり異なる動作をすることに注意してください。私はこの動作を(まだ)理解していません。このレベルで特別な考慮が必要かどうか疑問に思います... while IFS=X readで分割しませんXが、while IFS=X; readありません...
Peter.O

(あなたは意味秒は?右、コロンの)whileための条件-あまり意味がありませんwhile 両端そのセミコロンで、その実際のループはありません... read1要素のループ内だけで最初のコマンドになり...か?何についてdo、その後..?
rozcietrzewiacz

1
いいえ、待ってください-あなたは正しいです、あなたはwhile条件にいくつかのコマンドを持つことができます(前にdo)。
rozcietrzewiacz

ああ..間違いなく、あなたは...あなたが気づいたように...それらを持っていることができます...しかし、彼らはセミコロンが好きではないようです...(そしてループは最後のコマンドがnonを返すまで広告無限をループし続けます-ゼロ終了コード)...今、トラップが完全に別のセクターにあるのではないかと思っています。whileのコマンドリストがどのように機能するか理解すること。なぜ機能しますIFS=が、IFS=X機能しません...(または、しばらくの間、これについてODしました..コーヒーブレークが必要です:)
Peter.O

1
私は私の更新を移動したときの$ rozcietrzewiaczは...おっと...私は、あなたの更新に気づいていなかった(前のコメントで述べたように)..それは面白そうだね、されて始まる理解するために...それでもnight-用私のような鳥、それは非常に遅い...(私はちょうど朝の鳥を聞いた:)...それは言った、私は少し集まってあなたの例を読んだ...私はそれを持っていると思う、実際に私はあなたはそれを持っていると確信していますが、私は眠らなければなりません:) ...これはほとんどユーレカです!瞬間...ありがとう
-Peter.O

45

入念に作成された入力テキストを使用した例を見てみましょう。

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バックスラッシュを使い切ってください:バックスラッシュ-改行は改行を無視し、バックスラッシュ-何でもその最初のバックスラッシュを無視します。バックスラッシュが特別に扱われるのを避けるために、を使用します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は、最後の変数に残りの行が含まれていることを除いて、変数に渡すだけの数の単語を読み取るためです。ただし、最初の単語で始まります。

したがって、各行を文字通り読み取るためには、単語の分割が行われていないことを確認する必要があります。これを行うには、IFS変数を空の値に設定します

$ 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ビルトインされ、変数は実際には外部プロセスの環境で終わることはありません。それにもかかわらず、の値は、実行中で$IFSあればそこに割り当てているものです¹ readread特別なビルトインではないため、割り当てはその期間だけ持続します。

したがって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によって指定されておらず、実際にはシェルに依存します。


4
Gillesに感謝します。非常に良いガイド付きツアーです。(「set -f」を意味しましたか?)....では、読者のために、すでに述べたことを再度述べるために、私が抱えていた問題を強調したいと思います。私はそれを間違った方法で見ています。まず第一に構造という事実であるwhile IFS= read(後にセミコロンなしで=)ではない特殊な形式のwhileかのIFSかのread例:構築物は、一般的な..です。anyvar=anyvalue anycommand。欠如;に設定した後は、anyvarの範囲になりanyvar 地元anycommandしばらく..を- /行わないループは100%であり、無関係のローカルスコープにany_var
Peter.O

3

、およびイディオム(コマンドごととスクリプト/シェル全体の変数スコープ)の(既に明確になっている)IFSスコープの違いは別として、持ち帰りのレッスンは、IFS変数の場合、入力行の先頭末尾のスペースを失うことです(aを含む)スペースに設定されます。while IFS='' readIFS=''; while readwhile IFS=''; readIFS

ファイルパスが処理されている場合、これはかなり深刻な結果を招く可能性があります。

したがって、IFS変数を空の文字列に設定することは、行の先頭と末尾の空白が削除されないことを保証するため、お勧めできません。

関連項目:Bash、IFSを使用してファイルから1行ずつ読み取る

(
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"
)

1優れたデモンストレーション、「*スペースでのrm *ファイル* *」とした後のクリーンアップ
amdn

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.