열을 명령 결과로 바꾸기

열을 명령 결과로 바꾸기

파일의 각 줄의 열 값을 명령의 출력으로 빠르고 효율적으로 바꾸는 방법을 찾으려고 합니다. 하루에 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시스템의 각 행에서 코어당 하나의 프로세스를 동시에 실행합니다. 따라서 기본적으로 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가 포함됩니다.

나는 여기에서 십진법을 빌려 점으로 구분된 쿼드 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스크립트 실행 시간이 약 1/3로 단축됩니다. 대신 셸 변환을 사용하면 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!"

한 번의 호출로 인한 출력 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 라이브러리에는 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

관련 정보