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
звучал так, как вы просили, но этот ответ кажется более подходящим.