【问题标题】:Shell: Find Matching Lines Across Many FilesShell:在许多文件中查找匹配行
【发布时间】:2012-09-03 11:26:05
【问题描述】:

我正在尝试使用 shell 脚本(以及“单线”)来查找大约 50 个文件之间的任何公共行。 编辑:注意我正在寻找出现在所有文件中的一行(行)

到目前为止,我已经尝试过 grep grep -v -x -f file1.sp *,它只匹配所有其他文件中的文件内容。

我也尝试过grep -v -x -f file1.sp file2.sp | grep -v -x -f - file3.sp | grep -v -x -f - file4.sp | grep -v -x -f - file5.sp 等...但我相信使用要作为 STD 搜索的文件而不是要匹配的模式进行搜索。

有谁知道如何使用 grep 或其他工具来做到这一点?

我不介意它是否需要一段时间才能运行,我必须在大约 500 个文件中添加几行代码,并希望在每个文件中找到一个共同的行,以便它插入“之后”(它们最初只是来自一个文件的 c&p,所以希望有一些共同点!)

感谢您的宝贵时间,

【问题讨论】:

标签: grep


【解决方案1】:

当我第一次读到这篇文章时,我以为您正在尝试找到“任何共同点”。我认为这是“查找重复行”的意思。如果是这种情况,以下就足够了:

sort *.sp | uniq -d

重新阅读您的问题后,您似乎实际上是在尝试查找“出现在所有文件中”的行。如果是这种情况,您需要知道目录中的文件数:

find . -type f -name "*.sp" | wc -l

如果返回数字 50,您可以像这样使用awk

WHINY_USERS=1 awk '{ array[$0]++ } END { for (i in array) if (array[i] == 50) print i }' *.sp

你可以合并这个过程,写一个这样的单行:

WHINY_USERS=1 awk -v find=$(find . -type f -name "*.sp" | wc -l) '{ array[$0]++ } END { for (i in array) if (array[i] == find) print i }' *.sp

【讨论】:

  • 原样,重复行将失败。想象一个有 50 行相同的文件
  • @bernardpaulus:OP 说的是“共同路线”,所以重复了。
【解决方案2】:

旧的 bash 答案(O(n);打开 2 * n 文件)

从@mjgpy3 的回答中,您只需创建一个 for 循环并使用 comm,如下所示:

#!/bin/bash

tmp1="/tmp/tmp1$RANDOM"
tmp2="/tmp/tmp2$RANDOM"

cp "$1" "$tmp1"
shift
for file in "$@"
do
    comm -1 -2 "$tmp1" "$file" > "$tmp2"
    mv "$tmp2" "$tmp1"
done
cat "$tmp1"
rm "$tmp1"

保存在comm.sh 中,使其可执行,然后调用

./comm.sh *.sp 

假设你所有的文件名都以.sp结尾。

更新的答案,python,每个文件只打开一次

查看其他答案,我想给出一个在不使用任何临时文件的情况下打开每个文件一次,并支持重复行的答案。另外,让我们并行处理这些文件。

给你(在 python3 中):

#!/bin/env python
import argparse
import sys
import multiprocessing
import os

EOLS = {'native': os.linesep.encode('ascii'), 'unix': b'\n', 'windows': b'\r\n'}

def extract_set(filename):
    with open(filename, 'rb') as f:
        return set(line.rstrip(b'\r\n') for line in f)

def find_common_lines(filenames):
    pool = multiprocessing.Pool()
    line_sets = pool.map(extract_set, filenames)
    return set.intersection(*line_sets)

if __name__ == '__main__':
    # usage info and argument parsing
    parser = argparse.ArgumentParser()
    parser.add_argument("in_files", nargs='+', 
            help="find common lines in these files")
    parser.add_argument('--out', type=argparse.FileType('wb'),
            help="the output file (default stdout)")
    parser.add_argument('--eol-style', choices=EOLS.keys(), default='native',
            help="(default: native)")
    args = parser.parse_args()

    # actual stuff
    common_lines = find_common_lines(args.in_files)

    # write results to output
    to_print = EOLS[args.eol_style].join(common_lines)
    if args.out is None:
        # find out stdout's encoding, utf-8 if absent
        encoding = sys.stdout.encoding or 'utf-8'
        sys.stdout.write(to_print.decode(encoding))
    else:
        args.out.write(to_print)

将其保存到find_common_lines.py,然后调用

python ./find_common_lines.py *.sp

更多使用信息请使用--help 选项。

【讨论】:

    【解决方案3】:

    结合这两个答案(ans1ans2)我认为您无需对文件进行排序即可获得所需的结果:

    #!/bin/bash
    ans="matching_lines"
    
    for file1 in *
    do 
        for file2 in *
            do 
                if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                    echo "Comparing: $file1 $file2 ..." >> $ans
                    perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
                fi
             done 
    done
    

    只需保存它,赋予它执行权限 (chmod +x compareFiles.sh) 并运行它。它将获取当前工作目录中存在的所有文件,并将进行全部比较,并将结果留在“matching_lines”文件中。

    需要改进的地方:

    • 跳过目录
    • 避免将所有文件比较两次(file1 vs file2 和 file2 vs file1)。
    • 可以在匹配字符串旁边添加行号

    希望这会有所帮助。

    最好的,

    艾伦·卡尔波夫斯基

    【讨论】:

    • 小心:您的算法运行在O(n**2) 复杂度
    【解决方案4】:

    this answer。我最初虽然 diff 听起来像你要求的,但这个答案似乎更合适。

    【讨论】:

    • 那个答案似乎不支持很多文件?这只是在两个文件中查找行的信息,我正在尝试在许多文件中查找行
    猜你喜欢
    • 2014-05-08
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 2012-11-25
    • 2020-02-25
    • 1970-01-01
    • 1970-01-01
    • 2022-01-07
    相关资源
    最近更新 更多