Поскольку все входные файлы уже отсортированы, мы можем пропустить фактический этап сортировки и просто использовать 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?