查找所有输入文件共有的项目

时间:2016-09-15 06:53:37

标签: bash shell awk grep

我有一系列类型的文件 -

f1.txt     f2.txt    f3.txt
A          B         A
B          G         B
C          H         C
D          I         E
E          L         G
F          M         J

我想找出所有三个文件共有的条目。在这种情况下,预期输出将是B,因为这是唯一出现的字母是所有三个文件。

如果我只有两个文件,我可以使用comm -1 -2 f1.txt f2.txt找到常用条目。

但这不适用于多个文件。我想过像

这样的东西

sort -u f*.txt > index #to give me the total unique entries

while read i ; do *test if entry is present in all the files* ; done < index

我想过迭代地执行comm -12 f1.txt f2.txt | comm -12 - f3.txt,但我有100多个文件,因此不实用。表现很重要。

修改

我实施了以下内容 -

sort -u f* > index

while read i 
do 
   echo -n "$i " 
   grep -c "$i" f*.txt > temp 
   awk -F ":" '{a+=$2} END {print a}' temp 
done < index | sort -rnk2

这给出了输出 -

B 3
G 2
E 2
C 2
A 2
M 1
L 1
J 1
I 1
H 1
F 1
D 1

从这里我可以看到文件数是3,B的出现次数是3.因此它出现在所有文件中。我仍然在寻找更好的解决方案。

5 个答案:

答案 0 :(得分:1)

使用python

这个python脚本可以找到大量文件中的公共行:

#!/usr/bin/python
from glob import glob
fnames = glob('f*.txt')
with open(fnames[0]) as f:
    lines = set(f.readlines())
for fname in fnames[1:]:
    with open(fname) as f:
        lines = lines.intersection(f.readlines())
print(''.join(lines))

示例运行:

$ python script.py
B

工作原理:

  • fnames = glob('f*.txt')

    收集感兴趣的文件名称。

  • with open(fnames[0]) as f: lines = set(f.readlines())

    这将读取第一个文件并从其行创建。此集称为lines

  • for fname in fnames[1:]: with open(fname) as f: lines = lines.intersection(f.readlines())

    对于每个后续文件,这将lines与此文件的行相交。

  • print(''.join(lines))

    这将打印出一组公共线。

使用grep和shell

尝试:

$ grep -Ff f1.txt f2.txt | grep -Ff f3.txt
B

这分两步进行:

  1. grep -Ff f1.txt f2.txt从f2.txt中选择也出现在f1.txt中的那些行。换句话说,此命令的输出由f1.txt和f2.txt共有的行组成。

  2. grep -Ff f3.txt从输入中选择同样位于f3.txt的所有行。

  3. 注意:

    • -F选项告诉grep将其输入视为固定字符串,而不是正则表达式。

    • -f选项告诉grep从名称所在的文件中获取它所寻找的模式。

    • 上面的命令查找完整的匹配行。这意味着,对于一个人来说,领先或尾随的空白区域是重要的。

答案 1 :(得分:1)

awk '{cnt[$0]++} END{for (i in cnt) if (cnt[i]==(ARGC-1)) print i}' *.txt

以上假设每个值在给定文件中出现的次数不超过一次,如您的示例所示。如果值CAN在一个文件中多次出现,则:

awk '!seen[FILENAME,$0]++{cnt[$0]++} END{for (i in cnt) if (cnt[i]==(ARGC-1)) print i}' *.txt

或GNU awk用于真正的多维数组和ARGIND:

awk '{cnt[$0][ARGIND]} END{for (i in cnt) if (length(cnt[i])==ARGIND) print i}' *.txt

答案 2 :(得分:0)

请注意,Ed的答案比我的建议要快得多,但我会把它留给后代: - )

我使用 GNU Parallel comm并行应用于成对文件(因此它应该很快)并重复执行,将每次迭代的输出作为输入传递给下一个。

当只剩下一个文件要处理时,它会收敛。如果在任何阶段存在奇数个文件,奇数文件将被提升到下一轮并稍后处理。

#!/bin/bash
shopt -s nullglob
# Get list of files
files=(f*.txt)
iter=0
while : ; do
   # Get number of files
   n=${#files[@]}
   echo DEBUG: Iter: $iter, Files: $n
   # If only one file left, we have converged, cat it and exit
   [ $n -eq 1 ] && { cat ${files[0]}; break; }
   # Check if odd number of files, and promote and delete one if odd
   if (( n % 2 )); then
      mv ${files[0]} s-$iter-odd;
      files=( ${files[@]:1} )
   fi
   parallel -n2 comm -1 -2 {1} {2} \> s-$iter-{#} ::: "${files[@]}"
   files=(s-$iter-*)
   (( iter=iter+1 ))
done

示例输出

DEBUG: Iter: 0, Files: 110
DEBUG: Iter: 1, Files: 55
DEBUG: Iter: 2, Files: 28
DEBUG: Iter: 3, Files: 14
DEBUG: Iter: 4, Files: 7
DEBUG: Iter: 5, Files: 4
DEBUG: Iter: 6, Files: 2
DEBUG: Iter: 7, Files: 1

基本上,s-0-*是第一遍的输出,s-1-*是第二遍的输出......

如果您希望运行命令parallel,而不运行任何命令,请使用:

parallel --dry-run ...

答案 3 :(得分:0)

使用join

$ join f1.txt <(join f2.txt f3.txt)
B

join期望对文件进行排序。这似乎也有效:

$ join <(sort f1.txt) <(join <(sort f2.txt) <(sort f3.txt))
B

答案 4 :(得分:0)

如果(但仅限于)所有文件都有唯一条目,这也应该有效:

sort f*.txt | uniq -c \ | grep "^\s*$(ls f*.txt | wc -w)\s" \ | while read n content; do echo $content; done