여러 파일의 줄을 합산하기 위한 Awk 기반 솔루션

여러 파일의 줄을 합산하기 위한 Awk 기반 솔루션

다음과 같은 파일이 여러 개 있습니다.

파일 1.dat:

1 1
1 3 4
5 9 10 11

파일 2.dat:

3 0
8 9 0
3 9 2 4

일반적으로 더 많은 행이 있습니다(각 행에는 이전 행보다 하나 더 적은 열이 포함됩니다). 위의 예를 사용하여 각 파일의 줄을 합산하는 하이브리드 bash/awk 스크립트를 설계했습니다.

출력 데이터:

4 1
9 12 4
8 18 12 15

스크립트는 예상대로 작동하지만 꽤 느립니다. 내 컴퓨터에서는 각각 10,000줄로 구성된 100개의 파일을 처리하는 데 30분 이상이 걸립니다. 스크립트는 모든 파일에서 n번째 줄을 수집하는 데 대부분의 시간을 소비하는 것 같습니다. file*.datawk 명령에 전달하여 내가 수행하는 작업을 수행할 수 있는 방법이 있습니까 (아래 참조)?

#!/bin/bash
ROWS=$1; shift
OUT_FILE=$1; shift
IN_FILE=("$@")

for i in `seq 1 1 ${ROWS}`; do
    # Get ith row from all input files
    for j in "${IN_FILE[@]}"; do
        tail -n+${i} ${j} | head -1 >> "temp.dat"
    done
    # Sum the rows 
    awk '{for (j=1;j<=NF;j++) a[j]+=$j} END {for (j in a) printf a[j] " "}' temp.dat >> ${OUT_FILE}
    echo >> ${OUT_FILE}
    rm temp.dat
done

위의 예를 기반으로 한 스크립트 사용법은 다음과 같습니다../RowSums.sh 3 out.dat file*.dat

답변1

any paste및 any를 사용하십시오 awk.

$ cat tst.sh
#!/usr/bin/env bash

paste "${@}" |
awk -v numFiles="$#" '{
    numFldsPerFile = NF / numFiles
    for ( outFldNr=1; outFldNr<=numFldsPerFile; outFldNr++ ) {
        sum = 0
        for ( fileNr=1; fileNr<=numFiles; fileNr++ ) {
            inFldNr = outFldNr + (fileNr - 1) * numFldsPerFile
            sum += $inFldNr
        }
        printf "%d%s", sum, (outFldNr<numFldsPerFile ? OFS : ORS)
    }
}'

$ ./tst.sh file1.dat file2.dat
4 1
9 12 4
8 18 12 15

설명적인 변수 이름과 명시적인 inFldNr계산을 통해 그것이 수행하는 작업을 명확하게 할 수 있기를 바랍니다.

답변2

다음 awk 스크립트는 거의 전체 쉘 스크립트를 대체할 수 있습니다.

# cat rowsum.awk
FNR <= rows {
    for (i = 1; i <= NF; i++)
        sum[FNR,i] += $i
}
END {
    for (i = 1; i <= rows; i++) {
        for (j = 1; j <= rows + 1; j++) {
            printf "%s ", sum[i, j]
        }
        printf "\n";
    }
}

예:

% awk -f rowsum.awk -v rows=2 file*.dat
4 1
9 12 4
% awk -f rowsum.awk -v rows=3 file*.dat
4 1
9 12 4
8 18 12 15

이는 각 줄에 대해 모든 파일을 반복해서 확인하는 것보다 빠릅니다.

참고: 나는 가정한다N행에는n+1목록. 그렇지 않은 경우 행당 열 수(예 cols[FNR]=NF: )를 저장하고 최종 루프에서 사용합니다.


메모리 효율성이 더 높은 또 다른 옵션은 paste각 파일에서 관련 라인을 모두 가져오는 것입니다.

% paste -d '\n' file*.dat                                                                                                                                                
1 1
3 0
1 3 4
8 9 0
5 9 10 11
3 9 2 4

그런 다음 다음 awk을 사용하십시오.

# cat rowsum-paste.awk
NR > 1 && NF != prevNF {
    for (i = 1; i <= prevNF; i++) {
        printf "%s ", sum[i];
        sum[i] = 0
    };
    printf "\n"
}
{
    for (i = 1; i <= NF; i++)
        sum[i] += $i;
    prevNF = NF
}
% (paste -d '\n' file*.dat; echo) | awk -f rowsum-paste.awk
4 1 
9 12 4 
8 18 12 15 

이 awk 코드는 필드 수가 변경될 때까지 행을 합산한 다음 현재 합계를 인쇄하고 재설정합니다. 추가 기능은 echo끝 부분의 필드 수를 변경하고 최종 인쇄를 시작하는 것입니다. 이는 END블록의 인쇄 코드를 복사하여 수행할 수도 있습니다.

답변3

awk처음 두 필드의 행 인덱스와 열 인덱스를 포함하고 해당 위치의 데이터 값을 세 번째 필드로 포함하는 모든 파일에 대해 탭으로 구분된 데이터 세트를 출력하는 데 사용됩니다 .

awk -v OFS='\t' '{ for (i = 1; i <= NF; ++i) print FNR, i, $i }' file*.dat

데이터를 정렬하고 datamashGNU를 사용하여 위에서 생성된 데이터에 대해 작업을 수행하고 동일한(행, 열) 인덱스에서 발생하는 요소를 합산합니다(이 옵션은 누락된 필드를 대체하기 위해 아무것도 출력하지 않습니다).crosstab--filler ''datamashN/A

sort -n | datamash --filler '' crosstab 1,2 sum 3

각 열에 추가된 헤더와 datamash출력 행 번호가 있는 초기 열을 잘라냅니다.

tail -n +2 | cut -f 2-

문제의 두 파일을 고려하면 이 모든 것이 출력과 함께 표시됩니다.

$ awk -v OFS='\t' '{ for (i = 1; i <= NF; ++i) print FNR, i, $i }' file*.dat | sort -n | datamash --filler '' crosstab 1,2 sum 3 | tail -n +2 | cut -f 2-
4       1
9       12      4
8       18      12      15

이것을 벤치마킹하고 비교해 보세요.무루의 솔루션, 두 개의 작은 데이터 파일에서 속도는 4배나 느리지 않습니다(3.7).

관련 정보