Понимание опции -exec `find`

53

Я постоянно вижу синтаксис

find . -name "FILENAME"  -exec rm {} \;

главным образом потому, что я не понимаю, как именно эта -execчасть работает. Что означает скобки, обратный слеш и точка с запятой? Существуют ли другие варианты использования этого синтаксиса?

Жолт Силаги
источник
11
@Philippos: я понимаю вашу точку зрения. Помните, что справочные страницы являются справочными, т. Е. Полезными для тех, кто понимает вопрос, для поиска синтаксиса. Для кого-то новичка в теме, они часто бывают загадочными и формальными, чтобы быть полезными. Вы обнаружите, что принятый ответ примерно в 10 раз длиннее записи справочной страницы, и это не просто так.
Жолт Силаги
6
Даже на старой manстранице POSIX читается имя утилиты или аргумент, содержащий только два символа «{}», которые должны быть заменены текущим путем , что мне кажется достаточным. Кроме того, у него есть пример -exec rm {} \;, как в вашем вопросе. В мои дни почти не было других ресурсов, кроме «большой серой стены», книг печатных manстраниц (бумага была дешевле, чем хранилище). Так что я знаю, что этого достаточно для кого-то нового в теме. Ваш последний вопрос, хотя справедливо задать здесь. К сожалению, ни у @Kusalananda, ни у меня нет ответа на это.
Филиппос
1
Comeon @Philippos. Вы действительно говорите Кусалананде, что он не улучшил справочную страницу? :-)
Жолт Силаги
1
@allo Хотя xargsиногда это удобно, findможно передать несколько аргументов пути команде без него. -exec command... {} ++вместо \;) пропускает столько путей после того, command...как будет соответствовать (каждая ОС имеет свой собственный предел длины командной строки). И как xargs, то +-завершённый форма find«S -execдействий также будет работать command...несколько раз в редких случаях, когда есть слишком много путей , чтобы поместиться в пределах.
Элия ​​Каган
2
@ ZsoltSzilagy Я не сказал ни того, ни другого. Он очень хорошо тебя кормил, я просто думаю, что ты достаточно взрослый, чтобы есть самому. (-;
Филиппос

Ответы:

90

Этот ответ приходит в следующих частях:

  • Основное использование -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:

  1. В качестве теста для дальнейшего ограничения поиска.
  2. Выполнить какое-то действие с найденным путем (обычно, но не обязательно, в конце 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"

Это передает два аргумента дочернему сценарию оболочки:

  1. Строка sh. Это будет доступно как $0внутри скрипта, и если внутренняя оболочка выводит сообщение об ошибке, она будет префиксом этой строки.

  2. Аргумент 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 {} +
Кусалананда
источник
7
-execберет программу и аргументы и запускает ее; некоторые команды оболочки состоят только из программы и аргументов, но многие этого не делают. Команда оболочки может включать перенаправление и конвейерную передачу; -execне может (хотя все findможет быть перенаправлено). Команда оболочки может использовать и ; && ifт.д .; -execне могу, хотя -a -oмогу сделать некоторые. Команда оболочки может быть псевдонимом или функцией оболочки, или встроенной; -execне можешь. Команда оболочки может раскрыть переменные; -execне может (хотя внешняя оболочка, которая запускает findбанку). Команда оболочки может заменять $(command)каждый раз по-разному; -execне можешь. ...
dave_thompson_085
... Команда оболочки может глобализироваться, -execне может - хотя findможет перебирать файлы так же, как это делает большинство глобусов, так что это редко требуется.
dave_thompson_085
@ dave_thompson_085 Конечно, команда оболочки может быть shсамой собой, которая вполне способна на все эти вещи
Тавиан Барнс,
2
Сказать, что это команда оболочки, здесь неправильно, find -exec cmd arg \;не вызывать оболочку для интерпретации командной строки оболочки, она запускается execlp("cmd", "arg")напрямую, а не execlp("sh", "-c", "cmd arg")(для которой оболочка в конечном итоге сделает эквивалент, execlp("cmd", "arg")если cmdне была встроена).
Стефан
2
Вы могли бы уточнить, что все findаргументы после -execи до ;или +составляют команду для выполнения вместе с ее аргументами, причем каждый экземпляр {}аргумента заменяется текущим файлом (с ;), {}а последний аргумент перед +заменой - списком файлов. в качестве отдельных аргументов (по {} +делу). IOW -execпринимает несколько аргументов, оканчивающихся на ;или {} +.
Стефан