区切り文字を含むレコードを含む大容量テキストファイル(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
マルチキャラクタ動作はRS
POSIX仕様で定義されていないため、「ショートモード」で使用される方法です。しかし、もしあれば空ファイルにレコードがある場合は無視されるため、awk
出力には表示されません。これが望まない場合は、おそらく明示的なRS="\n\n"
表記法を使用する必要があります。ほとんどのawk
実装では、これを正規表現として処理し、「素早く」期待どおりに実行します。
答え3
レコード区切り文字を使用するたびに、\n\n
Perlモードと段落モード(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.
これにより、次のことができます。
100,000より長いすべてのレコードを削除します。数値(ファイルエンコードによってバイト列と異なる場合があります):
perl -00 -ne 'print unless length()>100000' file
最初の100,000文字以降のすべての文字を削除して、100,000文字を超えるレコードを切り捨てます。
perl -00 -lne 'print substr($_,0,100000)' file
149
次に始まる行を削除します。perl -00 -pe 's/(^|\n)149\s+[^\n]+//g;' file
149
次に始まる行のみを削除しますが、レコードが100000文字を超える場合:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g if length()>100000; ' file
レコードが100,000文字を超える場合は、
149
レコードが100,000文字未満になるか、149で始まる行がなくなるまで149で始まる行を削除します。perl -00 -pe 'while(length()>100000 && /(^|\n)149\s/){s/(^|\n)149\s+[^\n]+//}' file
レコードが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
最後に、上記のように実行しますが、レコードが切り捨てられないように正しいサイズが得られるまで、文字だけでなく行全体を削除します。
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
パターンを指定された文字列に置き換え、置換/代替回数を返します。
それはインスピレーションを得たこの回答。