
次のファイルがいくつかあります。
ファイル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
anypaste
と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
最初の2つのフィールドの行インデックスと列インデックスを含み、その場所のデータ値を3番目のフィールドとして含むすべてのファイルのタブ区切りデータセットを出力するために使用されます。
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-
問題の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
これをベンチマークして比較してみてください。ムルのソリューション、2つの小さなデータファイルでは、速度は4倍遅くなりません(3.7)。