我有一系列类型的文件 -
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.因此它出现在所有文件中。我仍然在寻找更好的解决方案。
答案 0 :(得分:1)
这个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 -Ff f1.txt f2.txt | grep -Ff f3.txt
B
这分两步进行:
grep -Ff f1.txt f2.txt
从f2.txt中选择也出现在f1.txt中的那些行。换句话说,此命令的输出由f1.txt和f2.txt共有的行组成。
grep -Ff f3.txt
从输入中选择同样位于f3.txt的所有行。
注意:
-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