Сравните `n` текстовые файлы и напечатайте количество уникальных строк в файле - PullRequest
0 голосов
/ 30 марта 2019

У меня есть n количество текстовых файлов с текстовыми строками в них.
Некоторые строки дублируются между некоторыми файлами.
Есть ли в * 1004 метод, где я могу сравнивать файлы ираспечатать, сколько уникальных строк имеет каждый файл по сравнению с другими файлами?

Пример:

# file1
1
2
3
10

# file2
2
10
50
3

# file3
100
2
1
40
6

Я в основном ищу решение, которое скажет что-то похожее на:
$filename:$unique_lines

Ответы [ 2 ]

0 голосов
/ 30 марта 2019

Для любых двух файлов, скажем file1 и file2, вы можете вывести уникальные строки в file1 (т.е. строки в file1, которые не отображаются в file2), следующим образом:

> fgrep -vx -f file2 file1
1

Другие примеры использования ваших file1, file2 и file3:

> fgrep -vx -f file3 file1  # Show lines in file1 that do not appear in file3
3
10

> fgrep -vx -f file2 file3  # Show lines in file3 that do not appear in file2
100
1
40
6

Обратите внимание, что в большинстве, если не во всех системах, fgrep на самом деле является просто синонимом grep -F, где -F говорит grep сравнивать фиксированные строки вместо попытки сопоставления с регулярным выражением. Так что если у вас по какой-то причине нет fgrep, вы можете использовать grep -Fvx вместо fgrep -vx.

С несколькими файлами для сравнения становится сложнее, но для любого данного файла вы можете сохранить текущий список уникальных строк во временном файле, а затем уменьшить его, сравнивая временный файл с другим файлом по время:

# Show all lines in file3 that do not exist in file1 or file2
fgrep -vx -f file1 file3 > file3_unique
fgrep -vx -f file2 file3_unique
100
40
6

Поскольку все, что вам нужно, это подсчет количества уникальных строк, вы можете просто передать эту последнюю команду на wc -l:

> fgrep -vx -f file2 file3_unique | wc -l
3

Если вы сделаете это с более чем 3 файлами, вы обнаружите, что вам нужно использовать дополнительный временный файл. Предположим, у вас было file4:

> cat file4
1
3
40
6

Это означает, что вам понадобится третья команда fgrep, чтобы завершить сокращение списка уникальных строк. Если вы просто сделаете это, вы столкнетесь с проблемой:

# Show all lines in file3 that do not exist in file1, file2, or file4
> fgrep -vx -f file1 file3         > file3_unique
> fgrep -vx -f file2 file3_unique  > file3_unique
grep: input file 'file3_unique' is also the output 

Другими словами, вы не можете перенаправить результаты обратно в тот же файл, который grep -ed. Поэтому вам нужно каждый раз выводить в отдельный временный файл, а затем переименовывать его:

# Show all lines in file3 that do not exist in file1, file2, or file4
> fgrep -vx -f file1 file3         > temp
> mv temp file3_unique
> fgrep -vx -f file2 file3_unique  > temp
> mv temp file3_unique
> fgrep -vx -f file4 file3_unique
100

Обратите внимание, что я остановил | wc -l в последней строке, просто чтобы показать, что он работает как положено.

Конечно, если ваше число файлов произвольно, вы захотите сделать сравнение в цикле:

files=( file* )
for ((i=0; i<${#files[@]}; ++i)); do
  cp -f "${files[i]}" unique
  for ((j=0; j<${#files[@]}; ++j)); do
     if (( j != i )); then
       fgrep -vx -f "${files[j]}" unique > temp
       mv temp unique
     fi
  done
  echo "${files[i]}:$(wc -l <unique)"
  rm unique
done

Это приведет к выводу:

file1:0
file2:1
file3:1
file4:0

Если temp и unique - это существующие файлы или каталоги, вы можете использовать mktemp. Например:

unique=$(mktemp)
temp=$(mktemp)

fgrep -vx file2 file3 > "$temp"
mv "$temp" "$unique"

Таким образом, настоящие файлы будут выглядеть примерно так: /tmp/tmp.rFItj3sHVQ и т. Д., И вы не будете случайно перезаписывать что-либо с именем temp или unique в каталоге, где вы запускаете этот код.

Обновление : Просто ради ударов я решил немного уменьшить это. Во-первых, я не слишком люблю вложенный цикл или временные файлы. Вот версия, которая избавляет от обоих. Это улучшение основано на наблюдении, что сокращение, скажем, file1 путем сравнения с file2, file3 и file4 подряд - это то же самое, что и одно сравнение между file1 и конкатенацией file2 + file3 + file4. Хитрость в том, чтобы просто выяснить, как объединить все остальные файлы без циклов. Но оказывается, что вы можете сделать это довольно легко в bash с объединением массивов. Например:

files=( file1 file2 file3 file4 )

# Concatenate all files *except* ${files[2]}, i.e., file3
> cat "${files[@]:0:2}" "${files[@]:3}"
1
2
3
10
2
10
50
3
1
3
40
6

Объединяя это с предыдущим решением, мы можем заменить внутренний цикл и временные файлы одной строкой:

files=(file1 file2 file3 file4)
for ((i=0; i<${#files[@]}; ++i)); do
  echo "${files[i]}:$(fgrep -vxc -f <(cat "${files[@]:0:i}" "${files[@]:i+1}") <(sort -u "${files[i]}"))"
done
0 голосов
/ 30 марта 2019

Один использует grep, sort, tr и uniq, n > 1:

$ grep ^ file[123] | tr : ' ' | sort -k2 | uniq -f 1 -u
file3 100
file3 40
file2 50
file3 6

Другой использует GNU awk:

$ awk '{
    a[$0]++
    f[FILENAME][FNR]=$0
}
END {
    for(i in f)
        for(j in f[i])
            if(a[f[i][j]]==1)
                print i,f[i][j]
}' file[123]
file2 50
file3 100
file3 40
file3 6
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...