Этот ответ приходит в следующих частях:
- Основное использование
-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 {} +