簡體   English   中英

比較`n`純文本文件並打印每個文件的唯一行數

[英]Compare `n` plaintext files and print number of unique lines per file

我有n個明文文件,其中包含文本行。
有些行在某些文件之間重復。
bash中是否有一種方法可以比較文件並打印出與其他文件相比每個文件有多少行?

例:

# file1
1
2
3
10

# file2
2
10
50
3

# file3
100
2
1
40
6

我基本上在尋找一種類似於以下內容的解決方案:
$filename:$unique_lines

一個使用grepsorttruniqn > 1:

$ grep ^ file[123] | tr : ' ' | sort -k2 | uniq -f 1 -u
file3 100
file3 40
file2 50
file3 6

另一個使用GNU awk:

$ awk '{
    a[$0]++
    f[FILENAME][FNR]=$0
}
END {
    for(i in f)
        for(j in f[i])
            if(a[f[i][j]]==1)
                print i,f[i][j]
}' file[123]
file2 50
file3 100
file3 40
file3 6

對於任何兩個文件,例如file1file2 ,您可以輸出file1的唯一行(即file1中未出現在file2 ),如下所示:

> fgrep -vx -f file2 file1
1

使用file1file2file3其他示例:

> fgrep -vx -f file3 file1  # Show lines in file1 that do not appear in file3
3
10

> fgrep -vx -f file2 file3  # Show lines in file3 that do not appear in file2
100
1
40
6

請注意,在大多數(如果不是全部)系統中, fgrep實際上只是grep -F的同義詞,其中-F告訴grep比較固定字符串而不是嘗試匹配正則表達式。 因此,如果由於某種原因沒有fgrep ,你應該能夠使用grep -Fvx而不是fgrep -vx

要使用多個文件進行比較,它會變得更加棘手,但是對於任何給定的文件,您可以在臨時文件中保留一個運行的唯一行列表,然后通過逐個比較臨時文件與其他文件來減少它:

# Show all lines in file3 that do not exist in file1 or file2
fgrep -vx -f file1 file3 > file3_unique
fgrep -vx -f file2 file3_unique
100
40
6

因為您想要的只是唯一行數的計數,您可以將最后一個命令傳遞給wc -l

> fgrep -vx -f file2 file3_unique | wc -l
3

如果使用3個以上的文件執行此操作,您將發現需要使用額外的臨時文件。 我們假設你有一個file4

> cat file4
1
3
40
6

這意味着你需要第三個fgrep命令來完成削減唯一線列表。 如果你這樣做,你會遇到一個問題:

# Show all lines in file3 that do not exist in file1, file2, or file4
> fgrep -vx -f file1 file3         > file3_unique
> fgrep -vx -f file2 file3_unique  > file3_unique
grep: input file 'file3_unique' is also the output 

換句話說,您無法將結果傳回給grep -ed的同一文件。 因此,您需要每次輸出到單獨的臨時文件,然后重命名:

# Show all lines in file3 that do not exist in file1, file2, or file4
> fgrep -vx -f file1 file3         > temp
> mv temp file3_unique
> fgrep -vx -f file2 file3_unique  > temp
> mv temp file3_unique
> fgrep -vx -f file4 file3_unique
100

請注意,我離開了| wc -l 最后一行的| wc -l只是為了表明它按預期工作。

當然,如果您的文件數量是任意的,您將需要在循環中進行比較:

files=( file* )
for ((i=0; i<${#files[@]}; ++i)); do
  cp -f "${files[i]}" unique
  for ((j=0; j<${#files[@]}; ++j)); do
     if (( j != i )); then
       fgrep -vx -f "${files[j]}" unique > temp
       mv temp unique
     fi
  done
  echo "${files[i]}:$(wc -l <unique)"
  rm unique
done

這會產生輸出:

file1:0
file2:1
file3:1
file4:0

如果tempunique是現有文件或目錄,則可能需要考慮使用mktemp 例如:

unique=$(mktemp)
temp=$(mktemp)

fgrep -vx file2 file3 > "$temp"
mv "$temp" "$unique"

這樣,實際文件將類似於/tmp/tmp.rFItj3sHVQ等,並且您不會在運行此代碼的目錄中意外覆蓋名為tempunique任何內容。

更新 :只是因為踢,我決定縮小一點。 首先,我並不過分喜歡嵌套循環或臨時文件。 這是一個擺脫兩者的版本。 這種改進是基於削下來的觀察,比如說, file1通過對比較file2file3file4在繼承的是同樣的事情之間做比較單一file1和的級聯file2 + file3 + file4 然后訣竅就是弄清楚如何在沒有循環的情況下連接每個其他文件。 但事實證明,你可以用陣列拼接在bash中相當容易地做到這一點。 例如:

files=( file1 file2 file3 file4 )

# Concatenate all files *except* ${files[2]}, i.e., file3
> cat "${files[@]:0:2}" "${files[@]:3}"
1
2
3
10
2
10
50
3
1
3
40
6

將此與前面的解決方案相結合,我們可以用一行代替內部循環和臨時文件:

files=(file1 file2 file3 file4)
for ((i=0; i<${#files[@]}; ++i)); do
  echo "${files[i]}:$(fgrep -vxc -f <(cat "${files[@]:0:i}" "${files[@]:i+1}") <(sort -u "${files[i]}"))"
done

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM