列をコマンドの結果に置き換える

列をコマンドの結果に置き換える

ファイル内の各行の列値をコマンドの出力にすばやく効率的に置き換える方法を見つけようとしています。 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行と言われたことに気づきました。その価値が何であるかについては、cdrtoip5,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アドレスアルゴリズムに適用しました。

https://stackoverflow.com/questions/29025177/how-can-i-convert-a-hex-ip-address-to-dotted-decimal-notation

私のコメントで述べたように、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

関連情報