![大きなレコード/段落を処理する](https://rvso.com/image/178469/%E5%A4%A7%E3%81%8D%E3%81%AA%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%2F%E6%AE%B5%E8%90%BD%E3%82%92%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B.png)
区切り文字としてレコードを含む大きなテキスト ファイル (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 フィールドで始まるすべての行を削除すると、制限を超えるレコードはなくなるという仮説は受け入れられます。
おそらく、制限内に収まるように特定のフィールド/タグの出現を十分に削除するには、完全なスクリプトが必要です。最初に最後のものを削除するとさらに良いでしょう。
これは、古代のライブラリアンファイル形式に関連しています。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
、制限が維持されるか、これ以上の削減が不可能になるまで (実際の置換数が 0 で示される)、それらの行が「何もない」に置き換えられます。その後、最終的な長さが制限よりも小さいレコードのみが印刷されます。
不利益:149
最初の行から順に削除されるため、連続したテキストの個々の要素を構成している場合は、そのテキストが多少理解不能になります。
注記:RS=""
明示的ではなく指定することRS="\n\n"
はポータブルawk
POSIX仕様では複数文字の動作が定義されていないため、「段落モード」で使用する方法はRS
ない。ただし、空のファイルにレコードがない場合、それらは によって無視されawk
、結果的に出力には表示されません。これが望ましくない場合は、RS="\n\n"
代わりに明示的な表記法を使用する必要があります。ほとんどのawk
実装では、これを正規表現として扱い、「単純に」期待される動作を実行します。
答え3
\n\n
レコード区切り文字として を使用する場合は、perl と段落モード ( から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
最初の 100000 文字以降のすべての文字を削除して、100000 文字を超えるレコードをトリミングします。
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
レコードが 100000 文字より長い場合は、
149
レコードが 100000 文字未満になるか、149 文字の行がなくなるまで、次の行を削除します。perl -00 -pe 'while(length()>100000 && /(^|\n)149\s/){s/(^|\n)149\s+[^\n]+//}' file
レコードが100000文字を超える場合、
149
レコードが100000文字未満になるか、149文字の行がなくなるまで、149文字で始まる行を削除します。まだ100000 文字を超える場合は、最初の 100000 文字のみを出力します。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 はその場合に削除する行の先頭 (フィールド名) です。
非貪欲法を使用して、\n149\t[^\n]*\n/
になるものだけを削除します^149\t.*$
。
gsub
パターンを指定された文字列に置き換え、行われた置換/置き換えの数を返します。
それはこの答え。