Поскольку все входные файлы уже отсортированы, мы можем пропустить фактический этап сортировки и просто использовать sort -m
для объединения файлов вместе.
В некоторых системах Unix (насколько мне известно только в Linux) этого может быть достаточно
sort -m *.words | uniq -d >dupes.txt
чтобы получить дублированные строки, записанные в файл dupes.txt
.
Чтобы найти, из каких файлов были получены эти строки, вы можете сделать
grep -Fx -f dupes.txt *.words
Это будет указывать grep
обрабатывать строки в dupes.txt
( -f dupes.txt
) как фиксированные строковые шаблоны ( -F
). grep
также потребует, чтобы вся строка идеально подходила от начала до конца ( -x
). Он напечатает имя файла и строку к терминалу.
Unix Linux Unices (или даже больше файлов)
В некоторых системах Unix 30000 имен файлов будут расширяться до строки, которая слишком длинна, чтобы передать ее одной утилите (что означает sort -m *.words
сбой Argument list too long
, что происходит в моей системе OpenBSD). Даже Linux будет жаловаться на это, если количество файлов намного больше.
Нахождение обманщиков
Это означает, что в общем случае (это также будет работать со многими, более чем 30000 файлами), нужно «разделить» сортировку:
rm -f tmpfile
find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh
В качестве альтернативы, создание tmpfile
без xargs
:
rm -f tmpfile
find . -type f -name '*.words' -exec sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh {} +
Это найдет все файлы в текущем каталоге (или ниже), чьи имена совпадают *.words
. Для соответствующего размера фрагмента этих имен за раз, размер которого определяется xargs
/ find
, он объединяет их в отсортированный tmpfile
файл. Если он tmpfile
уже существует (для всех, кроме первого чанка), этот файл также объединяется с другими файлами в текущем чанке. В зависимости от длины ваших имен файлов и максимально допустимой длины командной строки, для этого может потребоваться более или намного более 10 отдельных запусков внутреннего скрипта ( find
/ xargs
сделает это автоматически).
«Внутренний» sh
скрипт,
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi
используется sort -o tmpfile
для вывода в tmpfile
(это не будет перезаписывать, tmpfile
даже если это также вход sort
) и -m
для слияния. В обеих ветвях "$@"
развернется список имен файлов, указанных в кавычках, которые передаются в скрипт из find
или xargs
.
Затем, просто запустите uniq -d
на , tmpfile
чтобы получить все строки, которые дублировали:
uniq -d tmpfile >dupes.txt
Если вам нравится принцип «СУХОЙ» («Не повторяйте себя»), вы можете написать внутренний скрипт как
if [ -f tmpfile ]; then
t=tmpfile
else
t=/dev/null
fi
sort -o tmpfile -m "$t" "$@"
или
t=tmpfile
[ ! -f "$t" ] && t=/dev/null
sort -o tmpfile -m "$t" "$@"
Откуда они пришли?
По тем же причинам, что и выше, мы не можем grep -Fx -f dupes.txt *.words
определить, откуда появились эти дубликаты, поэтому вместо этого мы используем find
снова:
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt {} +
Поскольку «сложной» обработки не требуется, мы можем вызывать grep
непосредственно из -exec
. -exec
Опция принимает команду утилиты и поместить найденные имена {}
. С +
в конце, find
разместит столько аргументов, {}
сколько поддерживает текущая оболочка при каждом вызове утилиты.
Чтобы быть полностью правильным, можно использовать либо
find . -type f -name '*.words' \
-exec grep -H -Fx -f dupes.txt {} +
или
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt /dev/null {} +
чтобы быть уверенным, что имена файлов всегда включены в вывод из grep
.
Первый вариант используется grep -H
для всегда вывода совпадающих имен файлов. Последний вариант использует тот факт, что grep
будет включать имя соответствующего файла, если в командной строке указано более одного файла .
Это имеет значение, поскольку последний кусок имен файлов, отправленных grep
с, find
может фактически содержать только одно имя файла, и в этом случае grep
он не будет упоминаться в его результатах.
Бонусный материал:
Рассекая команду find
+ xargs
+ sh
:
find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh
find . -type f -name '*.words'
просто сгенерирует список имен путей из текущего каталога (или ниже), где каждое из них совпадает с именем обычного файла ( -type f
) и имеет в конце соответствующий компонент имени файла *.words
. Если нужно искать только текущий каталог, можно добавить -maxdepth 1
после .
, перед -type f
.
-print0
обеспечит вывод всех найденных путей с символом \0
( nul
) в качестве разделителя. Это символ, который недопустим в пути Unix, и он позволяет нам обрабатывать имена путей, даже если они содержат символы новой строки (или другие странные вещи).
find
трубы его выход на xargs
.
xargs -0
прочтет \0
-delimited список имен путей и будет выполнять данную утилиту несколько раз с кусками этих, гарантируя , что утилита выполняется с достаточно просто аргументы , чтобы не вызвать оболочку жаловаться на слишком длинный список аргументов, пока больше нет ввода от find
.
Утилита вызывается xargs
это sh
с помощью сценария , заданного в командной строке в виде строки , используя свой -c
флаг.
При вызове sh -c '...some script...'
с последующими аргументами аргументы будут доступны скрипту $@
, за исключением первого аргумента , в который будет помещен $0
(это «имя команды», которое вы можете заметить, например, top
если вы достаточно быстры). Вот почему мы вставляем строку sh
в качестве первого аргумента после конца реального скрипта. Строка sh
является фиктивным аргументом и может быть любым отдельным словом (некоторые предпочитают _
или sh-find
).
fi' sh
?fi
конецif
оператора во "внутреннем"sh
сценарии оболочки. В'
торцах , что сценарий оболочки (весь сценарий является одиночно строкой в кавычках).sh
Будет передан на внутренний скрипт в$0
(не часть$@
, которая будет содержать имена файлов). В этом случае этаsh
строка может быть любым словом. Если пропуститьsh
в конце, первое имя файла будет передано$0
и не будет частью обработки, выполняемой внутренним сценарием оболочки.Это означает, что вы, вероятно, можете найти какое-то применение для
sort -m
:Другой очевидной альтернативой для этого было бы просто
awk
собрать строки в массив и сосчитать их. Но, как прокомментировал @ dave_thompson_085 , эти 3 000 миллионов строк (или сколько угодно уникальных), вероятно, потребовали бы некоторый значительный объем памяти для хранения, так что это может работать не очень хорошо.источник
С помощью awk вы можете получить все повторяющиеся строки во всех файлах одной короткой командой:
Но он будет повторять строки, если линия существует 3 или более раз.
Есть решение получить только первый дубликат:
Это должно быть довольно быстро (если повторов мало), но потребляет много памяти, чтобы сохранить все строки в памяти. Возможно, в зависимости от ваших реальных файлов и повторов, попробуйте сначала с 3 или 4 файлами.
В противном случае вы можете сделать:
Который будет печатать уникальные повторяющиеся строки.
источник
sort -m * | uniq -d
'x[$0]++==1'
но на самом деле потребуется много памяти; если линии 3G имеют, скажем, отличные значения 1G, и если вашему awk нужно, скажем, 50 байтов для записи хеш-массива, отображающей (предположительно короткую) строку в значение неинициализированного значения, это 50 ГБ. Для сортированного ввода вы можете сделать этоuniq -d
вручную,awk '$0==p&&n++==1;$0!=p{p=$0;n=1}'
но зачем?==1
, отличная идея.awk
хранить 2,4E11 байт (223 ГиБ).sort -m *.words | uniq -d
работает отлично! После процесса я запускаю,grep
чтобы найти файлы, которые содержат повторяющиеся записи. Вы видите способ напечатать хотя бы одно имя файла, содержащее дублированную запись?Оптимизированное
sort
+uniq
решение:--parallel=N
- изменить количество одновременных сортировок наN
-d, --repeated
- печатать только дубликаты строк, по одной для каждой группыисточник