Awk

匹配文件之間的 2 個主要列;並在這些主列匹配時將其他列粘貼到輸出文件中。保持第一個文件的行大小不變

  • September 14, 2020

這是我大約 24 小時前提出的上一個問題的後續問題:在文件之間同時匹配兩個主要列,並在這些主要列匹配時將補充列粘貼到輸出文件中

G-Man 用一個有用的程式碼解決了這個問題,但我有一個後續問題。我已經接受了答案,因此第二篇文章……

我有 3 個文件,每個文件都有唯一數量的列,全部以製表符分隔,但有些列在 3 個文件之間共享。這是我想用來創建某種“聚合”文件的 3 個文件之間的共享列。

下表顯示了文件的外觀範例。基本上我想在文件之間匹配列 MAIN1 和 MAIN2 。三個文件之間的兩列都必須匹配。

當兩個文件之間的 MAIN1 和 MAIN2 匹配時,我想將 file2 中的列“minor8”添加到 file1 中表的右側。隨後,對於兩個文件之間的 MAIN1 和 MAIN2 匹配的情況,我想在 file1 表的右側從 file3 添加“minor9”。因為“minor8”應該緊挨著file1的最右邊的列(列名:“minor3”),所以我希望“minor9”在“minor8”旁邊進入新的輸出文件。OUTPUT 文件給出了我理想的最終文件應該是什麼樣子的想法。

基本上這些是 3 個文件的範例(“選項卡”有點混亂)

文件1:

MAIN1   minor1  MAIN2   minor3
1  bla1    a    blabla1
1  bla2    b    blabla2
1  bla3    c    blabla3
2  bla4    a    blabla4
2  bla5    d    blabla5
3  bla6    e    blabla6
4  bla7    f    blabla7
5  bla8    a    blabla8
5  bla9    g    blabla9

文件2:

minor8  MAIN1   MAIN2
yes1    2   d
yes2    3   e
yes3    4   f
yes4    5   a
yes5    5   g
yes6    1   a
yes7    1   b
yes8    1   c
yes9    2   a

文件3:

MAIN1   MAIN2   minor9
5   a   sure1
5   g   sure2
1   a   sure3
1   b   sure4
1   c   sure5
2   a   sure6
2   d   sure7
3   e   sure8
4   f   sure9

所需的輸出文件:

MAIN1   minor1  MAIN2   minor3  minor8  minor9
1   bla1    a   blabla1 yes6    sure3
1   bla2    b   blabla2 yes7    sure4
1   bla3    c   blabla3 yes8    sure5
2   bla4    a   blabla4 yes9    sure6
2   bla5    d   blabla5 yes1    sure7
3   bla6    e   blabla6 yes2    sure8
4   bla7    f   blabla7 yes3    sure9
5   bla8    a   blabla8 yes4    sure1
5   bla9    g   blabla9 yes5    sure2

如前所述,G-Man 提供了一個有用的程式碼,完全符合我的要求(請參閱上一篇文章)。我可能會問 G-Man(或其他有時間的人)一些關於我還不太了解的個別程式碼行的具體問題,但在那之前,我有這個後續問題。

G-Man 的程式碼能夠重新創建上述 OUTPUT 文件,所以謝謝 G-Man!

後續問題:

我忘記提到的一件事是,程式碼無法做到(據我所知),如果文件之間的列 MAIN1 和 MAIN2 不匹配,它將從 file1 中刪除行。這是我的錯,因為我沒有具體說明。我的目標是有一個 OUTPUT 文件,其中沒有刪除 file1 中的任何行。

基本上 file1 是我的優先文件。無論這個文件有多少行(接近一百萬),這也是 OUTPUT 文件應該有的行數。如果沒有列 MAIN1、MAIN2 匹配,則某些行的列“minor8”和“minor9”可以為空。但是,當“minor8”或“minor9”(或兩者)存在“缺失/空”值時,我想保留這些行 file1。

我將嘗試使用上述文件 2 和 3 的略微不同版本來說明這一點(因此 file1 保持不變)。

調整後的 file2(沒有 MAIN1、MAIN2 組合:2、d):

minor8  MAIN1   MAIN2
yes2    3   e
yes3    4   f
yes4    5   a
yes5    5   g
yes6    1   a
yes7    1   b
yes8    1   c
yes9    2   a

調整後的file3(沒有​​MAIN1,MAIN2組合:5,a):

MAIN1   MAIN2   minor9
5   g   sure2
1   a   sure3
1   b   sure4
1   c   sure5
2   a   sure6
2   d   sure7
3   e   sure8
4   f   sure9

調整後的所需輸出(即,對於 MAIN1、MAIN2 組合 2-d,minor8 列中的空值;對於 MAIN1、MAIN2 組合 5-a,minor9 列中的空值):

MAIN1   minor1  MAIN2   minor3  minor8  minor9
1   bla1    a   blabla1 yes6    sure3
1   bla2    b   blabla2 yes7    sure4
1   bla3    c   blabla3 yes8    sure5
2   bla4    a   blabla4 yes9    sure6
2   bla5    d   blabla5     sure7
3   bla6    e   blabla6 yes2    sure8
4   bla7    f   blabla7 yes3    sure9
5   bla8    a   blabla8 yes4    
5   bla9    g   blabla9 yes5    sure2

我希望我的解釋方式足夠清楚。我看到表格的選項卡有點亂。你們喜歡這樣,還是讓我在視覺上理順表格?(我可以想像,唯一可能導致的問題是,當您複製粘貼我的範例數據時,您將擁有不應該存在的其他選項卡……)

無論如何,我非常感謝你們的幫助。希望在不久的將來,我能夠為這個論壇做出貢獻,而不僅僅是尋求幫助……

您對如何編輯 G-Man 的程式碼以使其成為可能有什麼建議嗎?或者,如果您有一個完全不同的建議,如何編寫一個考慮到這一額外要求的有用程式碼,請告訴我。

創建以下文件:

merge21:

開始 {
FS = "\t"
OFS = "\t"
}
NR==FNR { # **file2** 
key = **$2 "," $3**
存在[鍵] = 1
       **次要8** [鍵] = **1**
下一個
}
{ #**文件1**
鍵 = $1 "," $3
if (present[key]) 列印 $1, $2, $3, $4, **minor8[key]**
否則列印 $1, $2, $3, $4, "-"
}

merge312:

開始 {
FS = "\t"
OFS = "\t"
}
NR==FNR { # **file3** 
key = **$1 "," $2**
存在[鍵] = 1
       **次要 9** [鍵] = **3 美元**
下一個
}
{ #**文件 1 + 文件 2**
鍵 = $1 "," $3
if (present[key]) 列印 $1, $2, $3, $4, **$5, minor9[key]**
否則列印 $1, $2, $3, $4, **$5** , "-"
}

它們幾乎相同;我已經加粗了差異。現在輸入命令

awk -f merge21 file2 file1 | awk -f merge312 file3 -

這假定您的關鍵欄位都不包含逗號,並且您的數據都不包含連字元,但這實際上僅取決於數據中沒有出現一些字元串。擴展它以支持更多列將是微不足道的;我希望這是顯而易見的。這可以增強以在一次awk執行中完成所有操作,但這會有點複雜,並且(IMNSHO)不值得付出努力。

這會產生文件中數據的所謂“左外連接”;有關某些定義,請參閱Stack Overflow 上的INNER 和 OUTER 聯接之間的區別。(“左外連接”在該問題的公認答案中定義為(釋義)«第一個表中的所有行,加上其他表中的任何公共行»。)

你的輸出將是

MAIN1   minor1  MAIN2   minor3  minor8  minor9
1       bla1    a       blabla1 yes6    sure3
1       bla2    b       blabla2 yes7    sure4
1       bla3    c       blabla3 yes8    sure5
2       bla4    a       blabla4 yes9    sure6
2       bla5    d       blabla5 -       sure7
3       bla6    e       blabla6 yes2    sure8
4       bla7    f       blabla7 yes3    sure9
5       bla8    a       blabla8 yes4    -
5       bla9    g       blabla9 yes5    sure2

而且,顯然,您可以使用 . 刪除**-**字元sed。(當然,如果您的真實數據實際上包含連字元,請選擇一些未使用的字元或字元串作為缺失數據的佔位符。)


筆記

  • FSOFS分別是輸入欄位分隔符和輸出欄位分隔符。(顯然IFS在 中沒有意義awk;這是我的錯誤。)您可能並不真正需要FS="\t"- awk預設情況下將製表符辨識為輸入欄位分隔符。(它允許您擁有包含空格的欄位,但您似乎對此不感興趣。)  OFS="\t"很重要;正因為如此,我可以說print $1, $2, $3, $4 並讓輸入欄位與它們之間的選項卡一起輸出。如果我不說OFS="\t",它們將被空格分隔,除非我說print $1 "\t" $2 "\t" $3 "\t" $4,這很乏味並且有損可讀性。
  • 如果您對 MAIN1 和 MAIN2 給出了額外的限制——例如,它們總是每個只有一個字元,或者 MAIN1 總是一個數字,而 MAIN2 總是以字母開頭——我就不需要,key. 但是您的第一個問題的原始版本沒有顯示出這樣的限制。考慮以下數據:
MAIN1 ($2)         MAIN2 ($3)         badkey = $2 $3         goodkey = $2 "," $3
   2              34151                  234151                   2,34151
  23               4151                  234151                   23,4151

如果我們不在鍵中包含一些不會出現在鍵欄位(MAIN1 和 MAIN2)中的分隔符,我們可以key為不同的行獲得相同的值。

  • 冒著分叉的風險,我不會“告訴 Linux”任何事情。我在告訴我awk該怎麼做。
  • 關於程式碼
NR==FNR { # 文件 3
鍵 = $1 "," $2
存在[鍵] = 1
次要9 [鍵] = $ 3
下一個
}

考慮 的倒數第七行file3,其中包含1 a sure3。顯然我們有$1= 1$2=a$3= sure3,所以key= 1,a。  present[key] = 1表示我正在設置present["1,a"]1標誌以指示file3 一條1,a線;即,=有一個minor9值。由於沒有行 in ,沒有被設置,所以程式碼的 " " 部分知道沒有for = ,它應該列印出來。這個名字只是我的一個任意選擇。它表示該行存在key``1,a``5,a``file3``present["5,a"]``file1 + file2``minor9``key``5,a-present``1,a``file3 (並且該5,a行不是)。習慣上用它1來表示“TRUE”。

  • 您可以替換print $1, $2, $3, $4for (n=1; n<=4; n++) printf "%s\t", $n. 您應該通過對最後一個欄位使用普通print (而不是printf)或通過執行來結束該行printf "\n"。您可以通過執行類似的操作來進一步簡化
for (n=1; n<=4; n++) printf "%s\t", $n
if (present[key]) print minor8[key]
否則列印“-”

請閱讀awk(1)POSIX 規範awkGNU Awk 使用者指南,並查看Awk.info了解更多資訊。

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