web-dev-qa-db-ja.com

シェルスクリプトの読み取りコマンドを使用して行ごとに入力ファイルを読み取ると、最後の行がスキップされる

通常、readコマンドを使用して、入力ファイルをシェルスクリプトに1行ずつ読み取ります。入力ファイルblah.txtの最後の行の最後に新しい行が挿入されていない場合、次のようなサンプルコードは間違った結果をもたらします。

#!/bin/sh

while read line
do
echo $line
done <blah.txt

したがって、入力ファイルが次のように読み取った場合-

One 
Two
Three
Four

そして、4の後、リターンを押さないと、スクリプトは最後の行の読み取りに失敗し、印刷します

One
Two
Three

4の後に余分な空白行を残すと、次のようになります。

One 
Two
Three
Four
//blank line

出力には、Fourを含むすべての行が印刷されます。ただし、これはcatコマンドを使用して行を読み取る場合には当てはまりません。最後を含むすべての行が印刷され、最後に余分な空白行を追加する必要はありません。

なぜこれが起こるのかについてのアイデアはありますか?私が作成するスクリプトはほとんどが他の人によって実行されるため、すべての入力ファイルの最後に余分な空白行を追加する必要はありません。

私は何年もの間これを理解しようとしてきた。解決策があれば感謝します(もちろん、catコマンドは1つですが、読み取りの背後にある理由も機能しません)。

19
Caife

readは、改行文字またはファイルの終わりが見つかるまで読み取り、ファイルの終わりが検出されるとゼロ以外の終了コードを返します。そのため、行を読み取り、ゼロ以外の終了コードを返すことは非常に可能です。

したがって、入力が改行で終了しない可能性がある場合、次のコードは安全ではありません。

while read LINE; do
  # do something with LINE
done

whileの本体は最後の行で実行されないためです。

技術的に言えば、改行で終了していないファイルはテキストファイルではないため、このようなファイルではテキストツールが奇妙な方法で失敗する可能性があります。しかし、私はいつもその説明に頼るのを嫌がります。

問題を解決する1つの方法は、読み取られたものが空でないかどうかをテストすることです(-n):

while read -r LINE || [[ -n $LINE ]]; do
  # do something with LINE
done

他の解決策には、mapfileを使用してファイルを配列に読み込み、最後の行を適切に終了することが保証されているユーティリティを介してファイルをパイプすることが含まれます(grep .、たとえば、空白行を処理したくない場合)、またはawk(通常は私の好みです)のようなツールを使用して反復処理を実行します。

ご了承ください -rは、ほぼ確実にreadビルトインで必要です。 readが再解釈しないようにします\-入力のシーケンス。

20
rici
8
Steven Penny

1行の回答:

IFS=$'\n'; for line in $(cat file.txt); do echo "$line" ; done
4
sandino

次のようなwhileループを使用します。

while IFS= read -r line || [ -n "$line" ]; do
  echo "$line"
done <file

または、whileループでgrepを使用します。

while IFS= read -r line; do
  echo "$line"
done < <(grep "" file)

grep .の代わりにgrep ""を使用すると、空の行がスキップされます。

注意:

  1. IFS=を使用すると、行のインデントはそのままになります。

  2. ほとんどの場合、-rオプションをreadとともに使用する必要があります

  3. forで行を読み取らない

  4. 最後に改行のないファイルは、標準のUNIXテキストファイルではありません。

4
Jahid

リダイレクトされた「while-read」ループを使用した以下のコードは私のためにうまく機能します

while read LINE
do
      let count++
      echo "$count $LINE"

done < $FILENAME

echo -e "\nTotal $count Lines read"
0
Aniket Thakur