C 標準では、テキスト ファイルは改行で終了する必要があると規定されています。そうしないと、最後の改行の後のデータが正しく読み取られない可能性があります。
ISO/IEC 9899:2011 §7.21.2 ストリーム
テキスト ストリームは、行に構成される順序付けられた一連の文字であり、各行は 0 個以上の文字と終了改行文字で構成されます。最後の行に終了改行文字が必要かどうかは、処理系定義です。ホスト環境でテキストを表現するためのさまざまな規則に準拠するために、入力と出力で文字を追加、変更、または削除する必要がある場合があります。したがって、ストリーム内の文字と外部表現内の文字との間に 1 対 1 の対応がある必要はありません。テキスト ストリームから読み込まれたデータは、次の場合にのみ、そのストリームに以前に書き出されたデータと必ず等しくなります。スペース文字の直前に改行文字がありません。最後の文字は改行文字です。読み込み時に改行文字の直前に書き出されたスペース文字が表示されるかどうかは、処理系定義です。
bash
ファイルの最後に改行がないために(または任意の Unix シェルで) 問題が発生するとは予想していませんでしたが、それは再現性のある問題のようです ($
この出力のプロンプトです)。
$ echo xxx\\c
xxx$ { echo abc; echo def; echo ghi; echo xxx\\c; } > y
$ cat y
abc
def
ghi
xxx$
$ while read line; do echo $line; done < y
abc
def
ghi
$ bash -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ ksh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ zsh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ for line in $(<y); do echo $line; done # Preferred notation in bash
abc
def
ghi
xxx
$ for line in $(cat y); do echo $line; done # UUOC Award pending
abc
def
ghi
xxx
$
また、bash
Korn シェル ( ksh
) に限定されずzsh
、そのように動作します。私は生き、学びます。問題を提起してくれてありがとう。
上記のコードで示されているように、cat
コマンドはファイル全体を読み取ります。このfor line in `cat $DATAFILE`
手法は、すべての出力を収集し、空白の任意のシーケンスを単一の空白に置き換えます (ファイルの各行には空白が含まれていないと結論付けています)。
Mac OS X 10.7.5 でテスト済み。
POSIXは何と言っていますか?
POSIXread
コマンド仕様には次のように書かれています。
read ユーティリティは、標準入力から 1 行を読み取ります。
デフォルトでは、-r
オプションが指定されていない限り、<バックスラッシュ> はエスケープ文字として機能します。エスケープされていない <バックスラッシュ> は、<改行> を除いて、次の文字のリテラル値を保持します。<newline> が <backslash> の後に続く場合、read ユーティリティはこれを行の継続として解釈します。<バックスラッシュ> と<newline>
は、入力をフィールドに分割する前に削除する必要があります。入力をフィールドに分割した後、エスケープされていない他のすべての <バックスラッシュ> 文字は削除されます。
-r
標準入力が端末デバイスであり、呼び出し元のシェルが対話型の場合、read は、オプションが指定されていない限り、<バックスラッシュ> <改行> で終わる入力行を読み取るときに、継続行を求めるプロンプトを出します。
終端の <newline> (存在する場合)は入力から削除され、結果は、パラメーター展開の結果のシェルのようにフィールドに分割されます (フィールド分割を参照)。[...]
「(もしあれば)」に注意してください (引用符で強調を追加)! 改行がない場合でも、結果を読み取る必要があるようです。一方で、次のようにも述べています。
標準入力
標準入力はテキストファイルです。
そして、改行で終わらないファイルがテキスト ファイルかどうかの議論に戻ります。
ただし、同じページのドキュメントの根拠は次のとおりです。
標準入力はテキスト ファイルである必要があるため、(空のファイルでない限り) 常に <newline> で終了しますが、-r
オプションが使用されていない場合に継続行を処理すると、入力が で終わらない可能性があります。 <改行>。これは、入力ファイルの最後の行が <バックスラッシュ> <改行> で終わっている場合に発生します。説明の「終端の <改行> (存在する場合) は入力から削除する」で「存在する場合」が使用されているのはこのためです。これは、標準入力がテキスト ファイルであるという要件を緩和するものではありません。
その論理的根拠は、テキスト ファイルが改行で終わるはずであることを意味しているに違いありません。
テキスト ファイルの POSIX 定義は次のとおりです。
3.395テキストファイル
0 行以上に編成された文字を含むファイル。行には NUL 文字が含まれておらず、<newline> 文字を含めて長さが {LINE_MAX} バイトを超えることはできません。POSIX.1-2008 はテキスト ファイルとバイナリ ファイルを区別しませんが (ISO C 標準を参照)、多くのユーティリティは、テキスト ファイルを操作するときに予測可能な出力または意味のある出力しか生成しません。このような制限がある標準ユーティリティは、STDIN または INPUT FILES セクションで常に「テキスト ファイル」を指定します。
これは「<改行>で終わる」ことを直接規定するものではありませんが、C標準に従い、「ゼロ行以上の行に編成された文字を含むファイル」と述べており、「行」のPOSIX定義を見ると」 それは言います:
3.206ライン
0 個以上の非 <newline> 文字と終了 <newline> 文字のシーケンス。
したがって、POSIX の定義によれば、ファイルは行で構成されており、各行は終了改行で終了する必要があるため、ファイルは終了改行で終了する必要があります。
「端末に改行がない」問題の解決策
Gordon Davissonの回答に注意してください。簡単なテストは、彼の観察が正確であることを示しています。
$ while read line; do echo $line; done < y; echo $line
abc
def
ghi
xxx
$
したがって、彼のテクニックは次のとおりです。
while read line || [ -n "$line" ]; do echo $line; done < y
また:
cat y | while read line || [ -n "$line" ]; do echo $line; done
最後に改行のないファイルで機能します(少なくとも私のマシンでは)。
シェルが入力の最後のセグメント (改行で終わっていないため、行と呼ぶことはできません) を削除することにまだ驚いていますが、POSIX にはそうする十分な理由があるかもしれません。また、テキスト ファイルが実際に改行で終わるテキスト ファイルであることを確認するのが最善であることは明らかです。