다음과 같은 파일이 여러 개 있습니다.
파일 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*.dat
awk 명령에 전달하여 내가 수행하는 작업을 수행할 수 있는 방법이 있습니까 (아래 참조)?
#!/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
데이터를 정렬하고 datamash
GNU를 사용하여 위에서 생성된 데이터에 대해 작업을 수행하고 동일한(행, 열) 인덱스에서 발생하는 요소를 합산합니다(이 옵션은 누락된 필드를 대체하기 위해 아무것도 출력하지 않습니다).crosstab
--filler ''
datamash
N/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).