Text-Processing

通過 awk 合併具有 N 個公共列的多個文件,如果任何文件沒有公共鍵,則希望將列值替換為 0

  • August 2, 2022

我想基於公共列合併多個文件,並希望在任何文件沒有該公共列時添加 0。例如見下圖:

a1.txt

111,222,444,5.5
121,321,555,1.2

a2.txt

111,222,444,7.8
333,321,555,4.5
311,555,222,1.1

a3.txt

333,321,555,9.1
311,555,222,8.8
444,666,777,2.5

匹配應該與第一 3 列的組合。

輸出應如下所示:

111,222,444,5.5,7.8,0
121,321,555,1.2,0,0
333,321,555,0,4.5,9.1
311,555,222,0,1.1,8,8
444,666,777,0,0,2.5

3 個輸入文件中第 4 列的值不同,我想按順序排列。像 a1.txt 值應該是輸出文件中的第 4 列。a2.txt 的值應該在輸出文件的第 5 列,a3.txt 的值應該在輸出文件的第 6 列。我在下面嘗試過,但沒有給我預期的結果。

awk '{ a[$1 FS $2 FS $3 FS] = a[$1 FS $2 FS $3 FS] ( a[$1 FS $2 FS $3 FS] == "" ? "" : FS) $4 } END{ for (i in a){print i,a[i,0],a[i]} }' FS="," a1.txt a2.txt a3.txt

這樣我想對 4 個或 5 個或 6 個輸入文件執行相同的操作。有人可以幫我嗎?

將 GNU awk 用於數組和 ARGIND 數組:

$ cat tst.awk
BEGIN { FS=OFS=SUBSEP="," }
{ vals[$1,$2,$3][ARGIND] = $NF }
END {
   for ( key in vals ) {
       printf "%s", key
       for ( i=1; i<=ARGIND; i++ ) {
           printf "%s%g", OFS, vals[key][i]
       }
       print ""
   }
}
$ awk -f tst.awk *.txt
111,222,444,5.5,7.8,0
311,555,222,0,1.1,8.8
333,321,555,0,4.5,9.1
444,666,777,0,0,2.5
121,321,555,1.2,0,0

如果輸出行的順序很重要,這是一個簡單的調整。

使用任何 awk 並在輸出中保留記錄的順序:

awk 'BEGIN{ SUBSEP=OFS=FS="," }
FNR==1 && !reProccss{ fileNr++ }
!reProccss{ keys[$1, $2, $3, fileNr]=$4; next }
 reProccss{ key=($1 OFS $2 OFS $3); recNr++
            for(i=1; i<=fileNr; i++)
                if(seen[key]++<fileNr){
                    join[key]= join[key] OFS ((key, i) in keys ?keys[key, i]:"0")
                    data[recNr]= key join[key]
                }
          }
END{ for(rec=1; rec<=recNr; rec++)
        if(data[rec]!="")
            print data[rec]
}' a[1-3].txt reProccss=1 a[1-3].txt

或者使用 with join+shell 將多個列作為鍵轉換為單個鍵,然後我們使用join類似於按第一列合併多個文件的命令(因為連接僅適用於單列作為鍵)來產生我們想要的輸出。

因此,我們通過-在前兩個文件中使用特定字元(輸入文件中不應存在的字元)將它們分隔並輸出到臨時文件,將多個鍵列轉換為一個*joined.tmp*:

join -t, -a1 -a2 -e 0 -o auto \
   <(<a1.txt sort |awk -F, -v OFS='-' '{ print $1, $2, $3 FS $4 }') \
   <(<a2.txt sort |awk -F, -v OFS='-' '{ print $1, $2, $3 FS $4 }') > joined.tmp

然後我們使用一個shell循環來處理文件的其余*joined.tmp*文件(它會更新每次執行以加入第二個下一個文件);我們也跳過了我們已經處理過的前兩個文件。

for file in ./a*.txt; do
   [ "$file" = "./a1.txt" -o "$file" = "./a2.txt" ] && continue
   join -t, -a1 -a2 -e 0 -o auto \
       joined.tmp <(sort "$file" |awk -F, -v OFS='-' '{ print $1, $2, $3 FS $4 }') >joined.tmp.1
   mv joined.tmp.1 joined.tmp
done

最後改回添加-到他們原來的字元,

sed 's/-/,/g' joined.tmp > joined-final.csv

join由於需要對輸入文件進行排序,因此輸出中的記錄順序將被更改:

$ cat joined-final.csv
111,222,444,5.5,7.8,0
121,321,555,1.2,0,0
311,555,222,0,1.1,8.8
333,321,555,0,4.5,9.1
444,666,777,0,0,2.5 

引用自:https://unix.stackexchange.com/questions/712091