Для любых двух файлов, скажем 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