Этот ответ приходит в следующих частях:
- Основное использование
-exec
- Использование
-execв сочетании сsh -c
- С помощью
-exec ... {} +
- С помощью
-execdir
Основное использование -exec
-execОпция имеет внешнюю утилиту с дополнительными аргументами в качестве аргумента и выполняет его.
Если строка {}присутствует где-либо в данной команде, каждый ее экземпляр будет заменен на путь, который обрабатывается в данный момент (например, ./some/path/FILENAME). В большинстве оболочек эти два символа {}не нужно заключать в кавычки.
Команду необходимо завершить с помощью ;for, findчтобы узнать, где она заканчивается (поскольку после этого могут быть дополнительные параметры). Чтобы защитить его ;от оболочки, его нужно заключить в кавычки как \;или ';', иначе оболочка увидит его как конец findкоманды.
Пример ( \в конце первых двух строк только для продолжения строки):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
Это найдет все обычные файлы ( -type f), имена которых соответствуют шаблону *.txtв текущем каталоге или ниже. Затем он проверит, helloвстречается ли строка в каком-либо из найденных файлов, используя grep -q(который не производит никакого вывода, только состояние выхода). Для тех файлов, которые содержат строку, catбудет выполнен вывод содержимого файла на терминал.
Каждый из них -execтакже действует как «тест» на пути, найденные find, как -typeи -nameделает. Если команда возвращает нулевой статус выхода (что означает «успех»), рассматривается следующая часть findкоманды, в противном случае findкоманда продолжается со следующим путем. Это используется в приведенном выше примере, чтобы найти файлы, содержащие строку hello, но игнорировать все остальные файлы.
Приведенный выше пример иллюстрирует два наиболее распространенных варианта использования -exec:
- В качестве теста для дальнейшего ограничения поиска.
- Выполнить какое-то действие с найденным путем (обычно, но не обязательно, в конце
findкоманды).
Использование -execв сочетании сsh -c
Команда, которая -execможет выполняться, ограничена внешней утилитой с необязательными аргументами. Использовать встроенные функции оболочки, функции, условные выражения, конвейеры, перенаправления и т. Д. Напрямую с помощью -execневозможно, если они не заключены в нечто вроде sh -cдочерней оболочки.
Если bashфункции требуются, то используйте bash -cвместо sh -c.
sh -cвыполняется /bin/shсо сценарием, заданным в командной строке, за которым следуют необязательные аргументы командной строки для этого сценария.
Простой пример использования sh -cсам по себе, без find:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
Это передает два аргумента дочернему сценарию оболочки:
Строка sh. Это будет доступно как $0внутри скрипта, и если внутренняя оболочка выводит сообщение об ошибке, она будет префиксом этой строки.
Аргумент applesдоступен как $1в скрипте, и если бы было больше аргументов, тогда они были бы доступны как $2и $3т. Д. Они также были бы доступны в списке "$@"(за исключением того, $0что не было бы частью "$@").
Это полезно в сочетании с тем, -execчто позволяет нам создавать произвольно сложные сценарии, которые воздействуют на найденные пути find.
Пример: Найти все обычные файлы, которые имеют определенный суффикс имени файла, и изменить этот суффикс имени файла на другой суффикс, где суффиксы хранятся в переменных:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'
Внутри внутреннего скрипта, $1будет строка text, $2будет строка txtи $3будет любым путем find, найденным для нас. Расширение параметра ${3%.$1}будет принимать путь и удалять .textиз него суффикс .
Или, используя dirname/ basename:
find . -type f -name "*.$from" -exec sh -c '
mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'
или с добавленными переменными во внутреннем скрипте:
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2; pathname=$3
mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'
Обратите внимание, что в этом последнем варианте переменные fromи toв дочерней оболочке отличаются от переменных с одинаковыми именами во внешнем скрипте.
Выше приведен правильный способ вызова произвольного сложного сценария с -execпомощью find. Использование findв цикле, как
for pathname in $( find ... ); do
склонен к ошибкам и не элегантен (личное мнение). Он разбивает имена файлов на пробелы, вызывает глобализацию имени файла, а также заставляет оболочку развернуть полный результат findперед тем, как даже выполнить первую итерацию цикла.
Смотрите также:
С помощью -exec ... {} +
В ;конце может быть заменен на +. Это заставляет findвыполнять данную команду с максимально возможным количеством аргументов (найденных путей), а не один раз для каждого найденного пути. Строка {} должна появиться перед тем, +как это сработает .
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} +
Здесь findмы соберем полученные имена путей и выполним catих на как можно большем количестве одновременно.
find . -type f -name "*.txt" \
-exec grep -q "hello" {} ';' \
-exec mv -t /tmp/files_with_hello/ {} +
Так же и здесь, mvбудет выполнено как можно меньше раз. Этот последний пример требует GNU mvот coreutils (который поддерживает эту -tопцию).
Использование -exec sh -c ... {} +также является эффективным способом зацикливания набора путей с произвольно сложным сценарием.
Основы те же, что и при использовании -exec sh -c ... {} ';', но теперь сценарий принимает гораздо более длинный список аргументов. Они могут быть зациклены, зацикливаясь "$@"внутри скрипта.
Наш пример из последнего раздела, который изменяет суффиксы имени файла:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2
shift 2 # remove the first two arguments from the list
# because in this case these are *not* pathnames
# given to us by find
for pathname do # or: for pathname in "$@"; do
mv "$pathname" "${pathname%.$from}.$to"
done' sh "$from" "$to" {} +
С помощью -execdir
Также есть -execdir(реализовано большинством findвариантов, но не стандартным вариантом).
Это работает как -execс той разницей, что данная команда оболочки выполняется с каталогом найденного пути в качестве текущего рабочего каталога и {}будет содержать базовое имя найденного пути без его пути (но GNU findвсе равно будет префиксом базового имени с ./, в то время как BSD findне буду этого делать).
Пример:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Это переместит каждый найденный *.txt-файл в ранее существовавший done-textsподкаталог в том же каталоге, где был найден файл . Файл также будет переименован, добавив .doneк нему суффикс .
Это было бы немного сложнее, -execпоскольку нам нужно было бы получить базовое имя найденного файла, {}чтобы сформировать новое имя файла. Нам также нужно имя каталога, {}чтобы done-textsправильно найти каталог.
С -execdirнекоторыми вещами, подобными этим, становится легче.
Соответствующая операция с использованием -execвместо -execdirдолжна будет использовать дочернюю оболочку:
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
done' sh {} +
или же,
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "${name%/*}/done-texts/${name##*/}.done"
done' sh {} +