まず、私の問題を詳しく説明します。実は本当に簡単です。私は巨大な.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が指摘したように、問題を引き起こす線を見つけるのは興味深いかもしれません。問題行の番号は、次のコマンドで印刷された(またはファイルに書き込まれた)番号の1つでなければなりません。
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呼び出しと2番目のawk呼び出しを1つの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を使用して最初の2つの手順を実行できます。これがより速いかどうかは、cut … | grep …
データと実装によって異なります。 sed呼び出しで\t
実装が理解できない場合はリテラルタブに、\t
実装が理解できない場合はバックスラッシュの改行に\n
置き換えます。s
sed -n -e 's/[ \t].*//' \
-e 's!/ns/!\n&!' -e 'b' \
-e 's/^.*\n//p'
/ns/
最初のフィールドが常に1回発生する場合は、最後の発生と一致する次のように単純化できます/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