让我首先详细解释一下我的问题。其实很简单。我有一个巨大的 .txt 文件,更精确地说是 300GB,我想将第一列中与我的模式匹配的所有不同字符串放入不同的 .txt 文件中。
awk '{print $1}' file_name | grep -o '/ns/.*' | awk '!seen[$0]++' > test1.txt
这是我尝试过的,据我所知它工作正常,但问题是在一段时间后我收到以下错误:
awk: program limit exceeded: maximum number of fields size=32767
FILENAME="file_name" FNR=117897124 NR=117897124
对于解析这么大的文件有什么建议吗?
答案1
对我来说,听起来像是awk
一条巨大的线,这将导致 32767 或更多字段。不过,我无法用我的 重现这一点awk
:
> echo | awk 'BEGIN {for(i=1;i<100000;i++) printf "%d ",i}; { print ""; }' >file
> awk '{ print $50000; }' too_long_line_for_awk.txt
50000
> awk --version
GNU Awk 4.1.0, API: 1.0
您可以使用更强大的工具来应对长线。您必须决定第一个字段的最大长度是多少。如果我们假设 100 那么你可以尝试这个:
cut -b -100 file | awk ...
此外(但这与你的问题无关)你的awk | grep | awk
构造没有意义。可以这样完成:
awk '$1 ~ "/ns/" {sub("^.*/ns/","/ns/",$1); if( !seen[$1]++ ) print $1}' \
file_name >test1.txt
调试建议
正如 Ramesh 指出的那样:找到导致问题的线路可能会很有趣。问题行的编号应该是以下命令打印(或写入文件)的编号之一:
awk '{ print NR;}' | tail -n 1 >crashline.txt
如果awk
在“崩溃”之前清空其缓冲区,那么它应该是下一个数字(+1)。
答案2
您的工具似乎awk
限制了字段数量。
示例mawk
:
field.c
:
/*------- more than 1 fbank needed ------------*/
/*
compute the address of a field with index
> MAX_SPLIT
*/
CELL *
slow_field_ptr(int i)
{
....
if (i > MAX_FIELD)
rt_overflow("maximum number of fields", MAX_FIELD);
....
}
rt_overflow
(定义在error.c
)是一个在运行时生成错误消息的函数:
/* run time */
void
rt_overflow(const char *s, unsigned size)
{
errmsg(0, "program limit exceeded: %s size=%u", s, size);
rt_where();
mawk_exit(2);
}
并在文件中size.h
:
#define FBANK_SZ 256
#define FB_SHIFT 8 /* lg(FBANK_SZ) */
#else
#define FBANK_SZ 1024
#define FB_SHIFT 10 /* lg(FBANK_SZ) */
#endif
#define NUM_FBANK 128 /* see MAX_FIELD below */
#define MAX_SPLIT (FBANK_SZ-1) /* needs to be divisble by 3 */
#define MAX_FIELD (NUM_FBANK*FBANK_SZ - 1)
可以看到,MAX_FIELD
默认是256*128 - 1 = 32767
。
使用gawk
可以解决这个问题。
答案3
一般来说,工具越专业,处理非常大的文件的能力就越好。请注意,您可以在 awk 中处理该文件 - 您只需要手动提取第一个字段,而不是使用内置字段处理。您也可以将 grep 调用和第二个 awk 调用合并为一个 awk 调用。
awk -F '\n' '
{ sub(/[\t ].*/,"");
if (match($0, "/ns/")) $0 = substr($0,RSTART); else next; }
!seen[$0]++
'
然而,通过专门工具的管道可能会更快。如果您的字段始终使用制表符作为分隔符,则可以使用cut
来隔离第一个字段。如果分隔符是空格,则将其设为cut -d ' '
。
cut -f 1 | grep … | …
或者,您可以使用 sed 来执行前两个步骤。这是否更快cut … | grep …
取决于您的数据和您的实施。在 sed 调用中,\t
如果您的实现无法理解,请替换为文本制表符\t
;如果您的实现无法理解\n
替换s
,请用反斜杠换行符替换它。
sed -n -e 's/[ \t].*//' \
-e 's!/ns/!\n&!' -e 'b' \
-e 's/^.*\n//p'
/ns/
如果第一个字段中始终出现一次,您可以将其简化为以下内容,它与最后一次出现的 匹配/ns
:
sed -n -e 's/[ \t].*//' -e 's!.*/ns/!/ns/!p'
转到最后一步,如果有很多匹配项,那么 awk 命令将使用大量内存。如果更改输出中的行顺序是可以接受的,则可以sort -u
改为使用。
cut -f 1 | grep -o '/ns/.*' | sort -u
sed -n -e 's/[ \t].*//' -e 's!.*/ns/!/ns/!p' | sort -u