ファイル内の各行の列値をコマンドの出力にすばやく効率的に置き換える方法を見つけようとしています。 1日に50万行程度の複数のファイルを処理する必要がありますが、できるだけ早く作業を終了できることを探しています。
コンマで区切られた行の8番目の列を入力に取り、コマンドを実行し、その列をコマンドの出力に置き換える必要があります。
私が試した方法は次のとおりです。動作しますが、非常に遅いです。
awk -F "," 'NR > 1 {
cmd = "cdrtoip " $8
cmd | getline ip
close(cmd)
$8=ip
print
}' $1.csv >> $1.csv.tmp
私はBashやLinuxサーバーにプリインストールできる他のLinuxプログラムを使用することを好みます。
編集:申し訳ありません。 cdrtoipが何であるかを含める必要がありました。
# Convert CISCO format (signed integer) to Hex
# Capitalize or else conversion from hex to decimal doesn't work later
HEXIP=$(printf '%x\n' $1 | tr '[:lower:]' '[:upper:]')
# Negative numbers will get 8 'f' in front of them
# Trim that part off
if [[ ${#HEXIP} -eq 16 ]]; then
HEXIP=${HEXIP:8:8}
fi
# Convert hex to decimal, separate into octets, put in order
OCTETS[0]=$(echo "ibase=16; ${HEXIP:6:2}" | bc)
OCTETS[1]=$(echo "ibase=16; ${HEXIP:4:2}" | bc)
OCTETS[2]=$(echo "ibase=16; ${HEXIP:2:2}" | bc)
OCTETS[3]=$(echo "ibase=16; ${HEXIP:0:2}" | bc)
# Print the IP
echo ${OCTETS[0]}.${OCTETS[1]}.${OCTETS[2]}.${OCTETS[3]}
cdripの実行時間は次のとおりです。
0.23s real 0.00s user 0.02s system
答え1
ネイティブアプリを使い続けたいと言っているのはわかりますが、GNUパラレル別々のプロセスを並列に実行できるため、より高速に実行できます。
sudo apt-get update
sudo apt-get install parallel
awk -F',' '{print $8}' file.csv | parallel -j+0 cdrtoip {}
これを呼び出す方法はいくつかありますが、parallel
上記の方法は.csvファイルの列8から出力を取得し、cdrtoip
システムの各行でコアごとに1つのプロセスを同時に実行します。したがって、デフォルトで4つのコアを実行すると、通常の実行時間の25%以内にタスクを実行できます。
利点parallel
は、出力を追跡し、実行中のタスクであるかのように順番に生成することです。
インストールしたら、man parallel
実行方法の詳細を確認してください(またはリンクのマニュアルを確認してください)。これがあなたが探しているものではない場合は申し訳ありません。しかし、過去には何度も役に立ちました。
編集する:出力を.csvに追加して列8を置き換えるには、次の例を使用します。〜する働いて、持つテスト済みです。デュアルコアMacbook Proで5,000行の.csvファイルを実行するのに約3.25分かかります。
設定:
$ cat file.tmp
blah1,blah2,blah3,blah4,blah5,blah6,blah7,1175063050,blah9,blah10,blah11
$ for i in {1..5000}; do cat file.tmp; done > file.csv
$ wc -l < file.csv
5000
スクリプト(cdrtoip
提供されたスクリプトを使用):
$ cat csvjob.sh
#!/bin/bash
fragment1="$(cut -d, -f1-7 file.csv | tr ',' "\t")"
fragment2="$(cut -d, -f8 file.csv | parallel -j+0 cdrtoip {})"
fragment3="$(cut -d',' -f9- file.csv | tr ',' "\t")"
paste <(echo "$fragment1") <(echo "$fragment2") <(echo "$fragment3") | sed "s/\t/,/g" > newfile.csv
結果:
$ time ./csvjob.sh
real 3m23.092s
user 1m22.245s
sys 2m57.794s
$ head -3 newfile.csv
blah1,blah2,blah3,blah4,blah5,blah6,blah7,10.10.10.70,blah9,blah10,blah11
blah1,blah2,blah3,blah4,blah5,blah6,blah7,10.10.10.70,blah9,blah10,blah11
blah1,blah2,blah3,blah4,blah5,blah6,blah7,10.10.10.70,blah9,blah10,blah11
別の編集: 以下はクアッドコアのMac Miniで実行されました(他のものも実行)。
$ time ./csvjob.sh
real 2m12.171s
user 2m59.816s
sys 2m15.787s
また、5,000行ではなく500,000行と言われたことに気づきました。その価値が何であるかについては、cdrtoip
5,000回の連続実行に関する以下の統計を参照してください。
$ time for i in {1..5000}; do cdrtoip 1175063050; done > /dev/null
real 2m32.487s
user 1m26.537s
sys 1m8.270s
最終編集: 以下は、前述のように、すでに複数のアプリケーションを実行しているクアッドコアMac Miniで実行されている500,000行のファイルです。
$ time ./csvjob.sh
real 216m22.780s
user 301m40.694s
sys 239m44.404s
何を言うのか正確に知っている、OP。
並行して実行しても実行にはかなり長い時間がかかります。
OPはより良い解決策を見つけました。ファイルあたり126秒は他の追随を許しません。繰り返しますが、cdrtoip
元々提供された500,000行の.csvを8コアDebian VM(OPがインストールできないことを知っています)で実行した統計は次のとおりです。parallel
$ time ./csvjob.sh
real 14m7.467s
user 6m3.883s
sys 4m18.556s
答え2
以下は、awk
ユーザー定義関数だけでなく、組み込み関数sprintf()
とrshift()
関数をサポートするすべてのバージョンにも適用する必要があります。これにはGNU awkが含まれます。
私はここで10進法を借りて、ドットで区切られたクワッドIPアドレスアルゴリズムに適用しました。
私のコメントで述べたように、cdrtoip
外部スクリプトをawk関数で書き換えると、外部スクリプトを500,000回以上呼び出す必要はありません。
awk -F, '
function cdrtoip(addr) {
return sprintf ("%d.%d.%d.%d",
rshift(and(addr,0xff000000),24),
rshift(and(addr,0x00ff0000),16),
rshift(and(addr,0x0000ff00),08),
rshift(and(addr,0x000000ff),00))
};
NR > 1 {
$8 = cdrtoip($8);
print
}' "$1.csv" >> "$1.csv.tmp"
500,000行を含むテストファイルでこれを実行しましたが、2秒で完了しました。
$ wc -l input.csv
500000 input.csv
$ time ./michael.sh < input.csv > output.csv
real 0m1.956s user 0m1.935s sys 0m0.018s
答え3
cdrtoip
実際にはかなり遅くて便利なユーティリティスクリプトのように見えますが、ループで何百回も呼び出すつもりはありません。私はこれが他のスクリプトやユーザーが使用する一般的なツールであると仮定しており、使い続けながらより速くしたいと思います。
4回ではなく1回だけ呼び出すと、bc
スクリプトの実行時間が約3分の1に短縮されます。代わりに、シェル変換を使用すると、bc
スクリプトの実行時間を約1/5に短縮できます。
私は一連のサンプル入力(約500行)を生成するために短いフレームワークを作成し、スクリプトorig.sh
(元のバージョンcdrtoip
)とnew.sh
修正されたバージョンの両方を実行してタイミングを合わせて出力を比較しました。次のようになります。
INPUT_SIZE=500
SAMPLE_FILE=in.txt
rm -f $SAMPLE_FILE orig.out new.out
x=0
while [[ $((x++)) -le $INPUT_SIZE ]]; do
tr -cd '[:digit:]' < /dev/urandom | head -c 10 | sed s/^0/1/ >> $SAMPLE_FILE
echo >> $SAMPLE_FILE
if [[ $((x%10)) -eq 0 ]]; then echo -n .; fi
if [[ $((x%20)) -eq 0 ]]; then echo -n '-' >> $SAMPLE_FILE; fi # next num is negative
done
echo
echo new cdrtoip:
time while read line; do ./new.sh $line >> new.out; done < $SAMPLE_FILE
echo original cdrtoip:
time while read line; do ./orig.sh $line >> orig.out; done < $SAMPLE_FILE
diff -q orig.out new.out || echo "Output was different!"
1回の呼び出しによる出力bc
:
$ ./generate.sh
..................................................
new cdrtoip:
real 0m1.431s
user 0m0.036s
sys 0m0.072s
original cdrtoip:
real 0m4.381s
user 0m0.040s
sys 0m0.084s
これは私のものですnew.sh
。より速いバージョンが必要な場合は、その行をbc
コメントアウトし、その下の変換のコメントを外します(約0.85秒)。また、大文字を削除することもできます${HEXIP^^}
。保持している場合は、すべてのシェルで動作しないため、shebangを${HEXIP^^}
含める必要があります(特にダッシュでは失敗します)。bash
#!/bin/bash
# Convert CISCO format (signed integer) to Hex
# Capitalize or else conversion from hex to decimal doesn't work later
HEXIP=$(printf '%x' $1)
HEXIP=${HEXIP^^}
# Negative numbers will get 8 'f' in front of them
# Trim that part off
if [[ ${#HEXIP} -eq 16 ]]; then
HEXIP=${HEXIP:8:8}
fi
# Convert hex to decimal, separate into octets, put in order
bc <<< "ibase=16; ${HEXIP:6:2}; ${HEXIP:4:2}; ${HEXIP:2:2}; ${HEXIP:0:2}" | tr '\n' . | sed 's/[\.]$/\n/'
# Convert hex to decimal, separate into octets, put in order
# using just bash: doesn't require hex characters to be upper case
#o0=$((16#${HEXIP:6:2}))
#o1=$((16#${HEXIP:4:2}))
#o2=$((16#${HEXIP:2:2}))
#o3=$((16#${HEXIP:0:2}))
# Print the IP
#echo $o0.$o1.$o2.$o3
答え4
John1024が指摘したように、速度低下の最大の容疑者はcdrtoipへの500,000回の呼び出しです。
編集:提供されたcdrtoipスクリプトに基づいて、完全な実装がPythonで書かれています。外部スクリプトを呼び出す必要がないので、はるかに高速です。
Pythonを一度見てください。 Pythonのパフォーマンスはこれらのタスクに非常に優れており、標準のPythonライブラリにはcsvファイルを処理する既存のモジュールが含まれています。
以下はPythonで実装した例です。この例では、awkスクリプトのようにstdin / stdoutを読み書きしますが、ファイルを開くように簡単に変更できます。編集:変換エラーのクリーンアップと処理が改善されました。処理が終了すると、stderrに要約を提供します。
#!/usr/bin/python
import sys,csv
# Convert CISCO format (signed integer) to Hex
# Based on original cdrtoip script in bash
# Note that a ValueError is raised if conversion cannot be done.
def cdrtoip(addrfield):
intaddr=int(addrfield) # ValueError if not a valid int
# Range-check the integer, make it unsigned
# If out of range, raise a ValueError
if intaddr < 0: intaddr=intaddr+0x100000000
if intaddr < 0: raise ValueError
if intaddr > 0xffffffff : raise ValueError
return ".".join( [ str(intaddr >> i & 0xff) for i in (24,16,8,0) ] )
# There are other options, depending on the exact file format
# you want. See: https://docs.python.org/2/library/csv.html
indata=csv.reader(sys.stdin)
outdata=csv.writer(sys.stdout)
header=True
no_convert=0
invalid_row=0
row_converted=0
blank_row=0
for row in indata:
# Write the first line unchanged...
if header:
header=False
else:
# Note that columns are numbered from 0
if len(row) == 0:
blank_row=blank_row+1
continue
elif len(row) > 7:
try:
row[7]=cdrtoip(row[7])
row_converted=row_converted+1
except ValueError:
# if conversion fails, we count and leave the field unchanged.
no_convert=no_convert+1
else:
# if there is no column 8 we count as invalid row.
invalid_row=invalid_row+1
outdata.writerow(row)
# Print a summary of work done (to stderr).
print >> sys.stderr,"%d values converted." % row_converted
if no_convert > 0:
print >> sys.stderr,"%d values not converted." % no_convert
if invalid_row > 0:
print >> sys.stderr,"%d rows not valid." % invalid_row
if blank_row > 0:
print >> sys.stderr,"%d blank rows removed." % blank_row