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