Shell: найдите соответствующие линии по многим файлам

Я пытаюсь использовать оболочку script (ну и "один лайнер" ), чтобы найти общие строки между примерно 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 файлам и захотеть найти общую строку в каждом из них, чтобы она вставляла "after" ( они изначально были только c & p из одного файла, поэтому, надеюсь, есть некоторые общие строки!)

Спасибо за ваше время,

Ответ 1

old, bash answer (O (n); открывает файлы 2 * n)

Из ответа @mjgpy3 вам просто нужно создать цикл for и использовать comm, например:

#!/bin/bash

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

cp "$1" "$tmp1"
shift
for file in "[email protected]"
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 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.

Ответ 2

Когда я впервые прочитал это, я подумал, что вы пытаетесь найти "любые общие линии". Я воспринял это как значение "найти повторяющиеся строки". Если это так, должно быть достаточно:

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

Ответ 3

Объединив эти два ответа (ans1 и ans2), я думаю, вы можете получить результат, который вам нужен без сортировки файлов:

#!/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) и запустите его. Он примет все файлы, присутствующие в текущем рабочем каталоге, и сделает сравнение all-vs-all, оставив в файле match_lines результат.

Что нужно улучшить:

  • Пропустить каталоги
  • Избегайте сравнения всех файлов два раза (file1 vs file2 и file2 vs file1).
  • Возможно, добавьте номер строки рядом с соответствующей строкой

Надеюсь, что это поможет.

Бест,

Алан Карповский

Ответ 4

См. этот ответ. Я изначально, хотя diff звучал так, как вы просили, но этот ответ кажется более подходящим.