2017-09-21 49 views
2

問題Unix command to find lines common in two filesanswer建議使用comm命令做任務:如何將comm命令的輸出轉換爲3個獨立的文件?

comm -12 1.sorted.txt 2.sorted.txt 

這說明這兩個文件共同的線(-1抑制了僅在第一個文件中的行,並且-2僅抑制第二個文件中的行,只留下兩個文件共同的行作爲輸出)。如文件名稱所示,輸入文件必須按排序順序排列。

comment對於這個問題,bapors問:

一個將如何在不同的文件輸出?

尋求澄清,我問:

如果你想在一個文件只在File1中的行,那些只在文件2中另一個,和那些無論是在第三,然後(前提是沒有文件中的行以標籤開頭),則可以使用sed將輸出分割爲三個文件。

用戶bapors證實:

這正是我問。你會舉一個例子嗎?

答案相對冗長,會破壞對其他問題的答案的簡單性(淹沒了大量信息),所以我在這裏單獨提出了這個問題 - 並提供了一個答案。

回答

2

使用sed的基本解決方案依賴於comm輸出只在第一個文件中沒有前綴的行;它只輸出第二個文件中只有一個標籤的行;並使用兩個選項卡輸出在兩個文件中找到的行。

它還依靠sedw命令寫入文件。含有

鑑於文件1.sorted.txt

1.line-1 
1.line-2 
1.line-4 
1.line-6 
2.line-2 
3.line-5 

和文件2.sorted.txt含有:

1.line-3 
2.line-1 
2.line-2 
2.line-4 
2.line-6 
3.line-5 

comm 1.sorted.txt 2.sorted.txt基本輸出的是:

1.line-1 
1.line-2 
     1.line-3 
1.line-4 
1.line-6 
     2.line-1 
       2.line-2 
     2.line-4 
     2.line-6 
       3.line-5 

給出一個包含有一個文件script.sed

/^\t\t/ { 
    s/// 
    w file.3 
    d 
} 
/^\t/ { 
    s/// 
    w file.2 
    d 
} 
/^[^\t]/ { 
    w file.1 
    d 
} 

可以運行如下所示的命令,並得到這樣所需的輸出:

$ comm 1.sorted.txt 2.sorted.txt | sed -f script.sed 
$ cat file.1 
1.line-1 
1.line-2 
1.line-4 
1.line-6 
$ cat file.2 
1.line-3 
2.line-1 
2.line-4 
2.line-6 
$ cat file.3 
2.line-2 
3.line-5 
$ 

該腳本的工作原理是:

  1. 與2個選項卡開始匹配行,刪除將行寫入file.3,並刪除該行(以便忽略該腳本的其餘部分),
  2. 匹配以1個選項卡開頭的行,刪除將行寫入file.2,並刪除該行(以便忽略該腳本的其餘部分),
  3. 匹配的行不以選項卡開頭,將行寫入file.1,並刪除該行。

步驟3中的匹配和刪除操作比其他任何操作都更對稱;他們可以省略(只留下w file.1),這個腳本的工作原理是一樣的。不過,請參見下面的script3.sed,以獲得保持對稱性的進一步理由。

正如所寫,這需要GNU sed; BSD sed不能識別\t轉義。顯然,該文件可以使用實際製表符代替\t表示法,然後使用BSD sed即可。

可以使它在命令行上全部工作,但它很煩瑣(而且這對它很有禮貌)。使用bash的ANSI C Quoting,你可以寫:

$ comm 1.sorted.txt 2.sorted.txt | 
> sed -e $'/^\t\t/ { s///\n w file.3\n d\n }' \ 
>  -e $'/^\t/ { s///\n w file.2\n d\n }' \ 
>  -e $'/^[^\t]/ {  w file.1\n d\n }' 
$ 

這在一個單獨的-e選項寫入每個script.sed三個「段落」的。 w命令很繁瑣;它需要在腳本的同一行之後的文件名和文件名,因此在腳本中的文件名之後使用\n。有很多可以消除的空間,但是顯示的佈局對稱更清晰。使用-f script.sed文件可能更簡單 - 它肯定是一種值得了解的技術,因爲它可以避免sed腳本必須在單引號,雙引號和反引號上操作時出現問題,這使得難以在Bash命令行上編寫腳本。

最後,如果這兩個文件可以包含以製表符開頭的行,這種技術需要更強大的力量才能使其工作。一種變體解決方案利用Bash的process substitution在文件中的行之前添加前綴,然後在處理sed腳本之前先刪除前綴,然後再寫入輸出文件。

script3.sed(由多達8個空格代替製表符) - 注意,這時候有在第三段需要一個替代s///(該d仍然是可選的,但是也可以被包括在內):

/^    X/ { 
    s/// 
    w file.3 
    d 
} 
/^  X/ { 
    s/// 
    w file.2 
    d 
} 
/^X/ { 
    s/// 
    w file.1 
    d 
} 

和命令行:

$ comm <(sed 's/^/X/' 1.sorted.txt) <(sed 's/^/X/' 2.sorted.txt) | 
> sed -f script3.sed 
$ 

對於相同的輸入文件,這產生相同的輸出,但通過添加,然後在每行的開始除去X,代碼的作用不是C更改數據的排序順序並處理前導標籤(如果它們存在)。

你也可以很容易地編寫使用Perl或Awk的解決方案,甚至不需要使用comm(如果文件適合內存,也可以使用未分類的文件)。

+0

真棒貢獻。希望我們仍然有SO文檔。爲BSD sed的用戶添加一件事。如果你碰巧在FreeBSD中,你的'/ bin/sh'而不是* bash的Almquist shell包含類似於bash的C風格的引用。 – ghoti

+0

這似乎不起作用,如果我在開始(和單詞之間)在行中添加多個製表符/空格 – RomanPerekhrest

+0

@RomanPerekhrest:您是否在說使用'script3.sed'的變體以及過程替換不起作用數據的行中有前導標籤或多個空格或製表符?如果是這樣,我想看看示例數據。您能否通過電子郵件將它發送給我 - 查看我的個人資料。一個可能的問題是,「sort」和「comm」並不認爲數據按排序順序排列意味着什麼。您可能需要在環境中設置「LANG = C」才能使其工作。 –

0

COMM + AWK溶液:

複雜的樣品文件:

的1.txt

1. line-1 with spaces (    | | here 
1.line-2 
1.line-4 with tabs > 
1.line-6 
2.line-2 
     3.line-5 (tabs) 

2.txt

1.line-3 
    2.line-1 with spaces 
2.line-2 
2.line-4 
    2.line-6 with tabs 
     3.line-5 (tabs) 

的工作:

comm -12 1.txt 2.txt > file-common 
awk 'NR==FNR{ a[$0];next }!($0 in a){ print $0 > "file"ARGIND-1 }' file-common 1.txt 2.txt 
  • comm -12 1.txt 2.txt > file-common - 將公共線保存到file-common文件

  • awk ... - 將打印的獨特線條1.txt2.txt到文件file1file2分別


查看結果:

head file* 
==> file1 <== 
1. line-1 with spaces (    | | here 
1.line-2 
1.line-4 with tabs > 
1.line-6 

==> file2 <== 
1.line-3 
    2.line-1 with spaces 
2.line-4 
    2.line-6 with tabs 

==> file-common <== 
2.line-2 
     3.line-5 (tabs) 
相關問題