web-dev-qa-db-ja.com

bashのテキストファイルから複数の行を読み取る

私がシェルスクリプトを使用している場合、私がしていることの大部分は他のモジュールのI/Oをpythonやmatlabなどでラップしています。これを行うには、通常、テキストファイルまたはそのような性質の入出力パスを使用します。使用できる1つのファイルから1行を読み取ることを知っています。

for file in $(cat $1);
do
    code using $file
done

しかし、両方のファイルの同等の行を使用して何かを実行したい場合はどうなりますか?同等のJavaのようなもの:

while((line1 = file1.readLine()) != null) {
    line2 = file2.readLine();
    //do something with both lines...
}

これをbashで行うための標準的な方法は何ですか?

5
Eric
_exec 3<file1
exec 4<file2
while read line1 <&3 && read line2 <&4
do
        echo "line1=$line1 and line2=$line2"
done
exec 3<&-
exec 4<&-
_

討論

  • 上記では、先頭と末尾の空白は入力行から削除されています。この空白を保持する場合は、_read …_を_IFS= read …_に置き換えます

  • 上記では、入力のバックスラッシュはエスケープ文字として解釈されます。それが必要ない場合は、_read …_を_read -r …_に置き換えます

  • _read line1 <&3_は、ファイル記述子3から_line1_を読み取ります。これは、_read -u3 line1_と同等に書き込むこともできます。

  • for file in $(cat $1);などのステートメントには、知っておくべき問題がいくつかあります。シェルは、Word分割パス名展開をファイルの内容に適用します。これを予期していない限り、さまざまなエラーが発生する可能性があります。

オルタナティブ

_while read line1 <&3 && read line2 <&4
do
        echo "line1=$line1 and line2=$line2"
done 3<file1 4<file2
_
5
John1024

ファイルの行を反復するには:

while IFS= read -r line; do
  echo "read $line"
done <input-file

複数のファイルを反復処理するには、それらを異なるファイル記述子で開きます( いつ追加のファイル記述子を使用しますか? を参照)。

while IFS= read -r line1 <&8 || IFS= read -r line2 <&9; do
  echo "read '$line1' from file 1 and '$line2' from file 2"
done 8<input-file1 9<input-file2

read <&8 || read <&9を使用すると、最長のファイルと一致するように、空の行で最短のファイルが完成します。どちらかのファイルの終わりに達したらすぐに終了するには、&&ではなく||を使用します。すべてのケースを検出する場合は、戻りコードを個別に確認してください。

{
  while
    IFS= read -r line1 <&8; empty1=$?
    IFS= read -r line2 <&9; empty2=$?
    [ "$empty1" -ne 0 ] && [ "$empty2" -ne 0 ]
  do
    echo "read '$line1' from file 1 and '$line2' from file 2"
  done
  if [ "$empty1" -ne 0 ]; then
    echo "Finishing processing file 1"
    …
  fi
  if [ "$empty2" -ne 0 ]; then
    echo "Finishing processing file 2"
    …
  fi
} 8<input-file1 9<input-file2

または、2つのファイルを結合することもできます。 paste コマンドが便利です。デフォルトでは、行をタブで区切り(-dを渡して別の区切り文字を選択します)、空の行でファイルを完成させます。ファイルにタブが含まれていない場合、これによって入力行が明確に区切られます。

tab=$(printf \\t)
paste input-file1 input-file2 |
while IFS=$tab read -r line1 line2; do … done

シェルはテキスト処理を高速で実行できないことに注意してください。中規模から大規模な入力には、より専門的なツールが最適です。 pasteによる前処理は、後処理のために2つのファイルをZip圧縮するのに便利です。行が読み取られるタイミングをさらに制御する必要がある場合、awkはgetlineコマンドを使用してそれを実行できます(シェルのreadと同様)。