2016-12-20 31 views
2

私は数百万行のテキストファイルを200 MBタブで区切りました。このファイルでは、私は今、私はこのコラムに基づいて、このファイルを壊したくAUなどシェルスクリプトでのパフォーマンスの問題

米国、英国、などの複数の場所でカラムを持っています。このコードはうまくいきますが、場所に基づいてファイルを複数のファイルに分割するのに1時間以上かかるため、パフォーマンス上の問題に直面しています。

#!/bin/bash 

read -p "Please enter the file to split " file 
read -p "Enter the Col No. to split " col_no 

#set -x 

header=`head -1 $file` 

cnt=1 
while IFS= read -r line 
do 
     if [ $((cnt++)) -eq 1 ] 
     then 
       echo "$line" >> /dev/null 
     else 
       loc=`echo "$line" | cut -f "$col_no"` 
       f_name=`echo "file_"$loc".txt"` 
       if [ -f "$f_name" ] 
       then 
         echo "$line" >> "$f_name"; 
       else 
         touch "$f_name"; 
         echo "file $f_name created.." 
         echo "$line" >> "$f_name"; 
         sed -i '1i '"$header"'' "$f_name" 
       fi 
     fi 

done < $file 

ここに適用されるロジックは、我々が作成し、それにデータを追加している私たちは一度だけ、ファイル全体を読んで、場所に応じているということである:ここではコードです。

コードのパフォーマンスを向上させるために必要な改善を提案してください。

次のサンプルデータは、タブの代わりにコロンで区切られています。国コードは、4列目にされています

ID1:ID2:ID3:ID4:ID5 
100:abcd:TEST1:ZA:CCD 
200:abcd:TEST2:US:CCD 
300:abcd:TEST3:AR:CCD 
400:abcd:TEST4:BE:CCD 
500:abcd:TEST5:CA:CCD 
600:abcd:TEST6:DK:CCD 
312:abcd:TEST65:ZA:CCD 
1300:abcd:TEST4153:CA:CCD 
+2

'while read 'を使用してファイルを読み込んでサブプロセスを実行することはどちらも遅いです。これは、awkなどのテキスト処理ツールの仕事です。 1つのawkスクリプトですべてのことを行うことができます。これははるかに効率的です。いくつかのサンプル入力/出力を持つ簡単な例を示す必要があります。 –

+4

なぜ何かをどこにも印刷しない( '/ dev/null'))? – Jdamian

+0

'f_name = \'エコー "ファイル_" $ loc ".txt" \ ''はあまりにも難しく無用です: 'f_name =" file_ $ loc.txt "を使用してください – Jdamian

答えて

2

心に留めするものがいくつかあります:while readを使用してファイルを読み込む

  1. は遅い
  2. サブシェルを作成し、外部プロセスを実行すると遅いです

これは、AWKなどのテキスト処理ツールのための仕事です。

# save first line 
NR == 1 { 
    header = $0 
    next 
} 

{ 
    filename = "file_" $col ".txt" 

    # if country code has changed 
    if (filename != prev) { 
     # close the previous file 
     close(prev) 
     # if we haven't seen this file yet 
     if (!(filename in seen)) { 
      print header > filename 
     } 
     seen[filename] 
    } 

    # print whole line to file 
    print >> filename 
    prev = filename 
} 

次の線に沿って何かを使用してスクリプトを実行します:あなたはこのようなものを使用することを

私が示唆し$col_noが持つ列番号を含むシェル変数をある

awk -v col="$col_no" -f script.awk file 

国コード。

国コードが多すぎない場合は、すべてのファイルを開いたままにしておくことができます。その場合は、close(filename)への呼び出しを削除できます。

あなたはこのような質問で提供されるサンプルでスクリプトをテストすることができます。私は:に入力フィールドセパレータを変更する-F:を追加しました

awk -F: -v col=4 -f script.awk file 

注意。

+1

私はコロンでsaperatedサンプルデータを追加しました。だから、国番号の列番号は4 –

1

私はTomが正しい軌道にいると思いますが、これを少し簡略化します。

Awkはいくつかの点で魔法です。これらの方法の1つは、明示的に閉じない限り、入出力ファイルハンドルをすべて開いたままにすることです。したがって、出力ファイル名を含む変数を作成した場合は、変数にリダイレクトして、指定した場所にデータを送信し、入力ファイルが処理不能になったときに出力ファイルを閉じます。

(N.B.この魔法の延長は、リダイレクトに加えて、あなたは複数のパイプを維持することができるということです。あなたがcmd="gzip -9 > file_"$4".txt.gz"; print | cmdの場合)

次の例では、各出力ファイルにヘッダーを追加せずにファイルを分割しています。

awk -F: 'NR>1 {out="file_"$4".txt"; print > out}' inp.txt 

ヘッダーを追加することが重要な場合は、もう少しコードを入力する必要があります。しかしそれほど多くはありません。

awk -F: 'NR==1{h=$0;next} {out="file_"$4".txt"} !(out in files){print h > out; files[out]} {print > out}' inp.txt 

それとも、このワンライナーは今少し長いので、我々は、説明のためにそれを分割することができます:

awk -F: ' 
    NR==1 {h=$0;next}  # Capture the header 
    {out="file_"$4".txt"} # Capture the output file 
    !(out in files){   # If we haven't seen this output file before, 
    print h > out;   # print the header to it, 
    files[out]    # and record the fact that we've seen it. 
    } 
    {print > out}   # Finally, print our line of input. 
' inp.txt 

私はあなたがあなたの質問に設けられた入力データに成功し、これらの2つのスクリプトをテスト。このタイプのソリューションでは、入力データをソートする必要はありません。各ファイルの出力は、そのサブセットのレコードが入力データに表示される順番になります。

注:異なるバージョンのawkを使用すると、異なる数の開いているファイルを開くことができます。 GNU awk(gawk)には数千の制限があります。これは対処しなければならない国の数よりもはるかに多いです。 BSD awkバージョン20121220(FreeBSD版)は21117個のファイルの後に使い果たされているようです。 BSD awkバージョン20070501(OS X El Capitan)は17ファイルに制限されています。

あなたが開いているファイルのあなたの潜在的な数に自信がないなら、あなたはこのようなawkのusig何かのバージョンを試すことができます:

mkdir -p /tmp/i 
awk '{o="/tmp/i/file_"NR".txt"; print "hello" > o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random 

あなたはまた、オープンパイプの数をテストすることができます。

awk '{o="cat >/dev/null; #"NR; print "hello" | o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random 

(あなただけのテキストうんざりのラインを出してくれる/dev/yesか何かを持っている場合は、その入力のために/ dev/randomを使用するよりも良いだろう。)

多くの出力ファイルをたくさん作成する必要があったので、私はいつもgawkを使用していたので、以前は自分のawkプログラミングでこの限界に遭遇していませんでした。 :-P

+0

です。 –