大容量記録/短絡処理

大容量記録/短絡処理

区切り文字を含むレコードを含む大容量テキストファイル(300 MB)があります\n\n。各行は、数字(フィールドラベル/名前)で始まり、その後にタブ文字とフィールドの内容/値が続くフィールドです。

110    something from record 1, field 110
149    something else
111    any field could be repeatable
111    any number of times
120    another field

107    something from record 2, field 107
149    fields could be repeatable
149    a lot of times
149    I mean a LOT!
130    another field

107    something from record 3
149    something else

各レコードは100 KBを超えることはできません。

以下では、問題のある履歴(制限より大きい)を見つけることができます。このレコード/「段落」から行末を削除します。そして長さを調べる:

cat records.txt | awk ' /^$/ { print; } /./ { printf("%s ", $0); } ' | awk '{print length+1}' | sort -rn | grep -P "^\d{6,}$"

次のいずれかで、間違ったレコードを処理する方法を見つけようとします。

  • 制限より大きいレコードを削除します。
  • 既知の問題を持つ特定のタグ(上記の例では149)のすべてのエントリを削除します。 149フィールドで始まるすべての行を削除すると、制限を超えるレコードがないと想定できます。

おそらく、制限に合わせて特定のフィールド/タグを十分に削除するには、スクリプト全体が必要です。最後のエントリを最初に削除することをお勧めします。

これは古いライブラリファイル形式に関連しています。ISO 2709

答え1

問題のある記録だけをスキップしたい場合:

awk 'BEGIN { ORS=RS="\n\n" } length <= 100*1000' file

これにより、100,000文字以下のすべてのレコードが印刷されます。

レコードが大きすぎる場合、特定の正の整数で始まるフィールドを削除するには、次の手順を実行します。

awk -v number=149 'BEGIN { ORS=RS="\n\n"; OFS=FS="\n" }
    length <= 100*1000 { print; next }
    {
        # This is a too long record.
        # Re-create it without any fields whose first tab-delimited
        # sub-field is the number in the variable number.

        # Split the record into an array of fields, a.
        nf = split($0,a)

        # Empty the record.
        $0 = ""

        # Go through the fields and add back the ones that we
        # want to the output record.
        for (i = 1; i <= nf; ++i) {
            split(a[i],b,"\t")
            if (b[1] != number) $(NF+1) = a[i]
        }

        # Print the output record.
        print
    }' file

以前と同様に、短いレコードが印刷されます。長いレコードは削除され、最初のタブで区切られたサブフィールドが数字number(ここではコマンドラインで149と指定されている)のすべてのフィールドが削除されます。

大規模なレコードの場合、不要なフィールドなしでレコードが再生成されます。内部ループはタブのフィールドを分割し、タブで区切られた最初のサブフィールドではなくフィールドを追加して出力レコードを再作成しますnumber

for (i = 1; i <= nf; ++i) {
    split(a[i],b,"\t")
    if (b[1] != number) $(NF+1) = a[i]
}

POSIX仕様は、awk複数文字の値を指定しない場合に発生する状況を公開するため(ほとんどの実装ではこれを正規表現として扱う)、厳密に一貫した実装の代わりにRS使用できます。これにより、データの複数の空行が空のレコードを分離しなくなります。RS=""; ORS="\n\n"ORS=RS="\n\n"awk

答え2

別のawk方法:

awk -v lim=99999 'BEGIN{RS=""; ORS="\n\n"}\
 {while (length()>=lim) {if (!sub(/\n149\t[^\n]*/,"")) break;}} length()<lim' file

149レコード長が変数で指定された制限を超えると、制限が維持されるか、縮小がlim不可能になるまで(実際の置換回数がゼロで示される)、「nothing」で始まる行は徐々に削除されます。次に、最終長が制限より小さいレコードのみを印刷します。

ダメージ:最初の行から始まる行を削除するので、連続した149テキストの個々の要素を形成すると、テキストを多少読み取ることができなくなります。

メモ:明示的RS=""でなく指定RS="\n\n"持ち運べるawkマルチキャラクタ動作はRSPOSIX仕様で定義されていないため、「ショートモード」で使用される方法です。しかし、もしあればファイルにレコードがある場合は無視されるため、awk出力には表示されません。これが望まない場合は、おそらく明示的なRS="\n\n"表記法を使用する必要があります。ほとんどのawk実装では、これを正規表現として処理し、「素早く」期待どおりに実行します。

答え3

レコード区切り文字を使用するたびに、\n\nPerlモードと段落モード(from man perlrun)を考慮してください。

-0[octal/hexadecimal]
        specifies the input record separator ($/) as an octal or hexadecimal number.  
   [...]
        The special value 00 will cause Perl to slurp files in paragraph mode. 
        

これにより、次のことができます。

  1. 100,000より長いすべてのレコードを削除します。数値(ファイルエンコードによってバイト列と異なる場合があります):

     perl -00 -ne 'print unless length()>100000' file
    
  2. 最初の100,000文字以降のすべての文字を削除して、100,000文字を超えるレコードを切り捨てます。

     perl -00 -lne 'print substr($_,0,100000)' file
    
  3. 149次に始まる行を削除します。

     perl -00 -pe 's/(^|\n)149\s+[^\n]+//g;' file
    
  4. 149次に始まる行のみを削除しますが、レコードが100000文字を超える場合:

     perl -00 -pe 's/(^|\n)149\s+[^\n]+//g if length()>100000; ' file
    
  5. レコードが100,000文字を超える場合は、149レコードが100,000文字未満になるか、149で始まる行がなくなるまで149で始まる行を削除します。

     perl -00 -pe 'while(length()>100000 && /(^|\n)149\s/){s/(^|\n)149\s+[^\n]+//}' file
    
  6. レコードが100000文字を超える場合は、149レコードが100000文字未満または149行がなくなるまで開始する行を削除します。まだ100,000文字を超えると、最初の100,000文字のみが印刷されます。

     perl -00 -lne 'while(length()>100000 && /(^|\n)149\s/){
                         s/(^|\n)149\s+[^\n]+//
                    }
                    print substr($_,0,100000)' file
    
  7. 最後に、上記のように実行しますが、レコードが切り捨てられないように正しいサイズが得られるまで、文字だけでなく行全体を削除します。

     perl -00 -ne 'while(length()>100000 && /(^|\n)149\s/){
                     s/(^|\n)149\s+[^\n]+//
                   }
                   map{
                     $out.="$_\n" if length($out . "\n$_")<=100000
                   }split(/\n/); 
                   print "$out\n"; $out="";' file
    

答え4

よりエレガントかもしれませんが、解決策は次のとおりです。

cat records.txt | awk -v RS='' '{if (length>99999) {gsub(/\n149\t[^\n]*\n/,"\n");print $0"\n"} else {print $0"\n"} }'

私は猫の役に立たない用途を知っていると信じています左から右への流れがより明確になった。

ここで、99999はしきい値サイズ、149はこの場合削除する行(フィールド名)の先頭です。

私はnon-greedyを使用\n149\t[^\n]*\n/して^149\t.*$

gsubパターンを指定された文字列に置き換え、置換/代替回数を返します。

それはインスピレーションを得たこの回答

関連情報