Bash. Получить перекресток из нескольких файлов
Так позвольте мне объяснить это немного больше:
У меня есть каталог, называемый тегами, у которого есть файл для каждого тега, что-то вроде:
tags/
t1
t2
t3
В каждом из файлов тегов есть такая структура, как:
<inode> <filename> <filepath>
Конечно, каждый файл тега будет иметь список многих файлов с этим тегом (но файл может появляться только в одном файле тега один раз). И файл может быть в нескольких файлах тегов.
То, что я хочу сделать, это вызвать команду вроде
tags <t1> <t2>
и попросите его перечислить файлы, которые имеют ОБОИХ теги t1 и t2 в хорошем смысле.
Мой план прямо сейчас заключается в создании временного файла. В основном выведите весь файл t1 в него. Затем пропустите каждую строку в t2 и выполните awk в файле. И продолжайте делать это.
Но мне интересно, есть ли у кого-нибудь другие способы. Я не слишком знаком с awk, grep и т.д.
Ответы
Ответ 1
Можете ли вы использовать
sort t1 t2 | uniq -d
Это объединит два файла, отсортирует их, а затем отобразит только строки, которые появляются более одного раза: то есть те, которые отображаются в обоих файлах.
Это предполагает, что каждый файл не содержит дубликатов внутри него и что inodes одинаковы во всех структурах для определенного файла.
Ответ 2
Вы можете попробовать с помощью утилиты comm
comm -12 <t1> <t2>
comm
с соответствующей комбинацией следующих параметров может быть полезна для различных заданий на содержимое файла.
-1 suppress column 1 (lines unique to FILE1)
-2 suppress column 2 (lines unique to FILE2)
-3 suppress column 3 (lines that appear in both files)
Это предполагает, что <t1>
и <t2>
отсортированы. Если нет, их следует сначала отсортировать с помощью sort
Ответ 3
Версия для нескольких файлов:
eval `perl -le 'print "cat ",join(" | grep -xF -f- ", @ARGV)' t*`
Расширяется до:
cat t1 | grep -xF -f- t2 | grep -xF -f- t3
Тестируемые файлы:
seq 0 20 | tee t1; seq 0 2 20 | tee t2; seq 0 3 20 | tee t3
Вывод:
0
6
12
18
Ответ 4
Здесь решение с одной командой, которое работает для произвольного числа несортированных файлов. Для больших файлов это может быть намного быстрее, чем использовать sort
и pipe, как я покажу ниже. Меняя $0
на $1
и т.д., Вы также можете найти пересечение определенных столбцов. Тем не менее, он предполагает, что строки не повторяются в файлах, а также предполагает версию awk
с переменной FNR
.
Решение:
awk ' { a[$0]++ }
FNR == 1 { b++ }
END { for (i in a) { if (a[i] == b) { print i } } } ' \
t1 t2 t3
Объяснение:
{ a[$0]++ } # on every line in every file, take the whole line ( $0 ),
# use it as a key in the array a, and increase the value
# of a[$0] by 1.
# this counts the number of observations of line $0 across
# all input files.
FNR == 1 { b++ } # when awk reads the first line of a new file, FNR resets
# to 1. every time FNR == 1, we increment a counter
# variable b.
# this counts the number of input files.
END { ... } # after reading the last line of the last file...
for (i in a) { ... } # ... loop over the keys of array a ...
if (a[i] == b) { ... } # ... and if the value at that key is equal to the number
# of input files...
print i # ... we print the key - i.e. the line.
Сравнительный анализ:
Примечание: улучшение во время выполнения становится более значительным, поскольку строки в файлах становятся длиннее.
### Create test data
mkdir test_dir; cd test_dir
for i in {1..30}; do shuf -i 1-540000 -n 500000 > test${i}.txt; done
### Method #1: based on sort and uniq
time sort test*.txt | uniq -c | sed -n 's/^ *30 //p' > intersect.txt
# real 0m23.921s
# user 1m14.956s
# sys 0m1.113s
wc -l < intersect.txt
# 53876
### Method #2: awk method in this answer
time \
awk ' { a[$0]++ }
FNR == 1 { b++ }
END { for (i in a) { if (a[i] == b) { print i } } } ' \
test*.txt \
> intersect.txt
# real 0m11.939s
# user 0m11.778s
# sys 0m0.109s
wc -l < intersect.txt
# 53876