我有一个大文本文件(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
这将打印小于或等于 100k 个字符的每条记录。
如果记录太大,要删除以特定正整数开头的字段:
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”开头的行,直到保留限制或不再可能减少(由实际替换的数量表示)为 0)。然后它只会打印最终长度小于限制的记录。
坏处:它将删除149
从第一行开始的行,因此如果它们构成连续文本的各个元素,则该文本将变得有些难以理解。
笔记:指定RS=""
而不是显式的RS="\n\n"
是便携的在“段落模式”中使用的方式awk
,因为多字符的行为RS
不是由 POSIX 规范定义的。然而,如果可以有空的文件中的记录,它们将被忽略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 个字符时才删除以 but 开头的行: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 的行,并且如果是仍然超过 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
用指定的字符串替换模式并返回所做的替换/替换的数量。
它的灵感来自于这个答案。