Bashスクリプトと大容量ファイル(バグ):リダイレクトに組み込まれた読み取り機能を使用して入力すると、予期しない結果が発生する

Bashスクリプトと大容量ファイル(バグ):リダイレクトに組み込まれた読み取り機能を使用して入力すると、予期しない結果が発生する

私は大容量ファイルを扱っていますbash。内容は次のとおりです。

  • 私は大きなファイルを持っています:75Gと400,000,000行を超えています(ログファイルです。残念ながら大きくなりました)。
  • 各行の最初の10文字は、YYYY-MM-DD形式のタイムスタンプです。
  • ファイルを分割したいです。 1日に1ファイルずつ。

次のスクリプトを試しましたが、うまくいきません。 私の問題は、代替ソリューションではなく、このスクリプトが機能しないことです。

while read line; do
  new_file=${line:0:10}_file.log
  echo "$line" >> $new_file
done < file.log

デバッグ後、問題がnew_file変数にあることがわかりました。このスクリプトは次のとおりです。

while read line; do
  new_file=${line:0:10}_file.log
  echo $new_file
done < file.log | uniq -c

次の結果を提供します(データを非公開にするためにesと入力し、x他の文字は本当です)。注dhと短い文字列:

...
  27402 2011-xx-x4
  27262 2011-xx-x5
  22514 2011-xx-x6
  17908 2011-xx-x7
...
3227382 2011-xx-x9
4474604 2011-xx-x0
1557680 2011-xx-x1
      1 2011-xx-x2
      3 2011-xx-x1
...
     12 2011-xx-x1
      1 2011-xx-dh
      1 2011-xx-x1
      1 208--
      1 2011-xx-x1
      1 2011-xx-dh
      1 2011-xx-x1    
...

私のファイル形式は問題ではありません。スクリプトはcut -c 1-10 file.log | uniq -c有効なタイムスタンプのみを提供します。興味深いことに、上記の出力の一部は次のとおりですcut ... | uniq -c

3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1

4474604uniq countの後、初期スクリプトが失敗したことがわかります。

bashで私が認識しない限界に達しましたか? bashでバグを見つけましたか(可能性が低い)。それとも何か間違っていますか?

修正する:

2Gファイルを読み込んだ後に問題が発生しました。継ぎ目readとリダイレクトは2Gより大きいファイルが好きではありません。しかし、より正確な説明はまだ探索されています。

アップデート2:

バグのように見えます。次のように再現できます。

yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c

しかし、これは回避策としてもうまく機能します(私が見つけた便利な使い方のようですcat)。

cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c 

GNUとDebianにバグが届きました。影響を受けるバージョンはbash6.0.4のDebian Squeeze 6.0.2と4.1.5です。

echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu

アップデート3:

私のバグレポートに迅速に対応したAndreas Schwabに感謝します。このパッチはこれらの不適切な動作を解決します。影響を受けるファイルは次のとおりです。lib/sh/zread.cGilesが先に指摘したように:

diff --git a/lib/sh/zread.c b/lib/sh/zread.c index 0fd1199..3731a41 100644
--- a/lib/sh/zread.c
+++ b/lib/sh/zread.c @@ -161,7 +161,7 @@ zsyncfd (fd)
      int fd; {   off_t off;
-  int r;
+  off_t r;

  off = lused - lind;   r = 0;

このr変数は戻り値を格納するために使用されますlseek。 as はlseekファイルの先頭からオフセットを返します。オフセットは2 GBを超えると負であるため、成功する必要がある場所intでテストが失敗します。if (r >= 0)

ベストアンサー1

Bashで何らかのバグを見つけました。これは既知のバグであり、修正されました。

プログラムは、ファイルのオフセットを有限サイズの整数型変数として表します。以前はintほとんどすべての人が使用していましたが、intその種類が符号ビットを含めて32ビットに制限されていたため、-2147483648から2147483647までの値を格納できました。今は違うさまざまなアイテムの名前を入力してください。off_tファイルのオフセットを含みます。

デフォルトでは、off_t32ビットプラットフォームでは32ビットタイプ(最大許容2GB)、64ビットプラットフォームでは64ビットタイプ(最大許容8EB)です。ただし、型をoff_t64ビット幅に切り替えてプログラムに適切な関数実装を呼び出すようにするLARGEFILEオプションを使用してプログラムをコンパイルするのが一般的です。lseek

32ビットプラットフォームでbashを実行していて、bashバイナリが大容量ファイルサポートでコンパイルされていないようです。通常のファイルから1行を読み取ると、bashは内部バッファを使用して文字を一括して読み取ってパフォーマンスを向上させます。 (詳細はソースコード参照)builtins/read.def)。行が完了すると、bash呼び出しはlseek他のプログラムがファイルの場所に興味を持っている場合に備えて、ファイルオフセットを行末に巻き戻します。関数lseekで呼び出しが発生します。zsyncfclib/sh/zread.c

ソースコードを詳しく読むことはできませんでしたが、絶対オフセットが負数のときに切り替え点で何かスムーズに起こらないのではないかと推測しています。したがって、bashが2 GBの表示を通過した後にバッファを再充填すると、最終的に誤ったオフセットが読み取られます。

私の結論が間違っていて、あなたのbashが実際に64ビットプラットフォームで実行されているか、大容量ファイルサポートでコンパイルされている場合、これは間違いなくバグです。この事実をディストリビューションに報告するか、上流

とにかく、シェルはこれらの大容量ファイルを処理するのに適したツールではありません。非常に遅いでしょう。可能であれば sed を使用し、そうでない場合は awk を使用します。

おすすめ記事