Вопрос или проблема
Как я могу find
файлы, содержащие конкретный контент (grep
able), и отсортировать найденные файлы по времени последнего изменения? (Я хочу выбрать только один самый новый файл)
Эта команда находит файлы:
find subfolder/ -maxdepth 1 -type f -exec grep -l 'blue_wizards' {} \;`
Эта команда находит файлы и сортирует их:
find subfolder/ -maxdepth 1 -type f -printf "%T+ %p\n" | sort
Но если я предоставлю -printf
, ввод для -exec grep
будет не таким, каким я хочу.
Было бы проще использовать zsh
и его квалификаторы глобов:
grep -le blue_wizards -- subfolder/*(D.Om)
Где:
D
также включает скрытые файлы, какfind
по умолчанию.
ограничивает до обычных файлов (как-type f
)Om
сортирует в обратном порядке по возрасту, какls -rt
с самым старым файлом первым
Вы можете сделать то же самое с использованием GNU find
, cut
и xargs
с любой оболочкой, похожей на Bourne, используя:
find subfolder/ -maxdepth 1 -type f -printf '%T@\t%p\0' |
LC_ALL=C sort -zn |
cut -zf2- |
xargs -r0 grep -le blue_wizards --
С более старыми версиями GNU cut
, возможно, вам потребуется использовать tr '\0\n' '\n\0' | cut -f2- | tr '\0\n' '\n\0'
или обратиться к GNU sed
, который, как и GNU sort
, поддерживает -z
гораздо дольше: LC_ALL=C sed -z 's/^[^:]*://'
(и изменить \t
на :
в аргументе -printf
).
Обратите внимание, что в любом случае -printf
специфичен для GNU (-maxdepth
также является расширением GNU, хотя теперь поддерживается большим количеством реализаций find
).
%T@
лучше, чем %T+
, так как это избегает вычисления календарной даты для соответствующего времени модификации (mtime), но также более важно, потому что это работает правильно при изменениях по зимнему времени (DST). Если использовать %T+
для сортировки, вы будете хотеть зафиксировать TZ
на UTC0, например, для получения времен UTC, не затронутых DST.
Сравните:
$ ls --full-time -ldrt a b
-rw-r--r-- 1 stephane stephane 13 2020-10-25 01:43:28.989189400 +0100 b
-rw-r--r-- 1 stephane stephane 13 2020-10-25 01:31:20.112312300 +0000 a
$ find a b -printf '%T+ %T@ %p\n' | sort
2020-10-25+01:31:20.1123123000 1603589480.1123123000 a
2020-10-25+01:43:28.9891894000 1603586608.9891894000 b
$ TZ=UTC0 find a b -printf '%T+ %T@ %p\n' | sort
2020-10-25+00:43:28.9891894000 1603586608.9891894000 b
2020-10-25+01:31:20.1123123000 1603589480.1123123000 a
a
новее b
, хотя он кажется старше с %T+
, так как он был изменен после перехода на зимнее время в моем часовом поясе (Европа/Лондон здесь).
Чтобы получить новейший файл, вы, вероятно, захотите инвертировать этот порядок (измените Om
на om
и sort
на sort -r
) и заставить grep
выводить список файлов с разделителем NUL, а не с разделителем новой строки, чтобы его можно было надежно обработать (так как новая строка является таким же допустимым символом в имени файла, как и любой другой), для чего вам понадобится GNU grep
или совместимый и принять первый.
Например, чтобы сохранить путь к новейшему файлу в переменной $newest
(и вернуть статус выхода с ошибкой, если нет соответствующего файла):
zsh + GNU grep:
grep --null -le blue_wizards -- subfolder/*(D.om) |
IFS= read -rd '' newest
bash + GNU find + GNU sort + GNU cut + GNU xargs + GNU grep:
IFS= read -rd '' newest < <(
find subfolder/ -maxdepth 1 -type f -printf '%T@\t%p\0' |
LC_ALL=C sort -rzn |
cut -zf2- |
xargs -r0 grep --null -le blue_wizards --
)
Чтобы получить все из них (новейшие первыми) в массив:
zsh + GNU grep:
files=(${(0)"$(grep --null -le blue_wizards -- subfolder/*(D.om))"})
print -r newest: $files[1]
bash 4.4+ + GNU find + GNU sort + GNU cut + GNU xargs + GNU grep:
readarray -td '' files < <(
find subfolder/ -maxdepth 1 -type f -printf '%T@\t%p\0' |
LC_ALL=C sort -rzn |
cut -zf2- |
xargs -r0 grep --null -le blue_wizards --
)
printf '%s\n' "newest: ${files[0]}"
Если вы хотите сделать это по POSIX, то есть, используя только стандартный синтаксис sh и утилит, это будет невозможно сделать без некоторых предположений о том, какие байтовые значения могут содержать имена файлов. Если вы можете гарантировать, что имена файлов не содержат символы новой строки и являются достаточно короткими, вы сможете сделать следующее:
dir=subfolder
file=$(
export LC_ALL=C
CDPATH= cd -P -- "$dir" &&
ls -At |
sed 's/"/"\\""/g; s/.*/"&"/; s|^|./|' |
xargs -E '' sh -c '
[ "$#" -eq 0 ] ||
find "$@" -type f -exec grep -l blue_wizards {} +' sh |
head -n 1
)
[ -n "$file" ] && newest="$dir/$file"
Если вы можете сделать еще больше предположений, например, имена файлов также содержат только буквенно-цифровые символы, .
, -
из переносимого набора символов, не начинаются с -
, все являются обычными файлами, $CDPATH
не установлен, $dir
не содержит компонентов ..
, не начинается с -
или +
:
dir=subfolder
file=$(
cd "$dir" &&
ls -At |
xargs grep -l blue_wizards |
head -n 1
)
[ -n "$file" ] && newest="$dir/$file"
Самым простым может быть объединение обеих команд find
следующим образом:
find subfolder/ -maxdepth 1 -type f \
-exec grep -q 'blue_wizards' {} \; \
-printf "%T+ %p\n" | sort
Дело в том, что -exec ;
может использоваться как условие, оно истинно или ложно, в зависимости от статуса выхода команды. Также grep -q
для получения только статуса выхода, и никакого вывода.
Это требует создания копии grep
для каждого файла, так что это не совсем оптимально в этом плане. -exec {} +
не работает таким же образом: вы не можете получить статус выхода для отдельных файлов таким образом, так что это всегда оценивается как истинное. Также с -printf "%T+ %p\n"
у вас будут проблемы, если какие-либо имена файлов содержат символы новой строки.
что если вы просто передадите в grep?
find subfolder/ -maxdepth 1 -type f -printf “%T+ %p\n” | grep -iE ‘blue_wizards’ (или ‘.blue_wizards.‘ для полного выделения строки)
Ответ или решение
Для решения задачи поиска файлов, содержащих определенный текст, с последующей сортировкой по времени последнего изменения и выбором самого нового файла, мы можем комбинировать команды find
, grep
и sort
. В контексте запроса, давайте рассмотрим, как можно реализовать это на практике, избегая стандартных ошибок и учитывая нюансы Unix-систем.
Решение задачи с использованием стандартных инструментов Unix
Чтобы найти файлы, которые содержат строку 'blue_wizards'
, и отсортировать их по времени последнего изменения, мы используем GNU find
, sort
и xargs
. Вот пошаговое объяснение:
-
Команда поиска файлов с использованием
find
иgrep
:find subfolder/ -maxdepth 1 -type f -exec grep -l 'blue_wizards' {} \;
Этот код находит файлы в указанной папке, содержащие текст
'blue_wizards'
. Опция-exec
позволяет выполнить командуgrep
для каждого найденного файла, где-l
указывает выводить только имена файлов. -
Сортировка файлов по времени последнего изменения:
find subfolder/ -maxdepth 1 -type f -printf '%T@ %p\n' | sort -r
Здесь мы используем опцию
-printf
сfind
для вывода времени последнего изменения файла как числа секунд с момента эпохи UNIX. Это позволяет корректно обрабатывать изменения времени и сортировать черезsort -r
(обратная сортировка). -
Комбинация двух подходов для оптимизации:
Вместо выполнения двух отдельных команд, мы можем совместить их таким образом:
find subfolder/ -maxdepth 1 -type f \ -exec grep -q 'blue_wizards' {} \; \ -printf '%T@ %p\n' | sort -r | head -n 1 | cut -d ' ' -f2-
-exec grep -q
используется для проверки наличия текста в файле, не выводя результат.- Результаты отфильтровываются и сортируются по времени изменения.
head -n 1
берет только первый (самый новый) файл в списке, аcut -d ' ' -f2-
отделяет имя файла от временной метки.
Альтернативный подход с Zsh
Если у вас есть возможность использовать Zsh, можно воспользоваться его мощной системой глобов:
newest_file=$(grep -le blue_wizards -- subfolder/*(D.om[1]))
(D.om[1])
— эти квалификаторы заставляют Zsh искать файлы, включая скрытые (D
), отобрать только обычные файлы (.
), и сортировать их в порядке изменения (om
).
Заключение
Представленные подходы позволяют эффективно и быстро находить файлы и сортировать их. Каждый из них имеет свои особенности и требует определенных настроек системы, будь то современный bash с поддержкой GNU-утилит или zsh. Важно также помнить, что для лучшей совместимости с различными Unix-системами необходимо учитывать переносимость кода, в зависимости от доступных утилит и используемой файловой системы.