Этот ответ содержится в следующих частях:
-exec
-exec
в сочетании сsh -c
-exec... {} +
-execdir
-exec
Опция -exec
принимает внешнюю утилиту с необязательными аргументами в качестве аргумента и выполняет ее.
Если строка {}
присутствует где-либо в заданной команде, каждый ее экземпляр будет заменен текущим обрабатываемым путем (, например../some/path/FILENAME
). В большинстве оболочек два символа {}
не нужно заключать в кавычки.
Команду необходимо завершить с помощью ;
, чтобы 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
, ограничена внешней утилитой с необязательными аргументами. Использовать встроенные в оболочку -ins, функции, условные операторы, конвейеры, перенаправления и т. д. напрямую с -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"
Это передает два аргумента дочернему сценарию оболочки. Они будут помещены в $0
и $1
для использования сценарием.
Строка 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
или sfind
не будет ).
Пример:
find. -type f -name '*.txt' \
-execdir mv -- {} 'done-texts/{}.done' \;
При этом каждый найденный*.txt
-файл будет перемещен в ранее -существующий done-texts
подкаталог в том же каталоге, где был найден файл . Файл также будет переименован путем добавления к нему суффикса .done
. --
, чтобы отметить конец параметров, необходимо здесь в тех реализациях find
, которые не имеют префикса базового имени ./
.Кавычки вокруг аргумента, содержащего {}
not в целом, необходимы, если ваша оболочка (t)csh
. Также обратите внимание, что не все find
реализации будут расширяться, {}
там(sfind
не будут ).
Это было бы немного сложнее сделать с -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 {} +
В вашем случае гораздо проще использовать grep
. Например. таким образом:
grep -o '<a href=[^<]*<\/a>'
Если между <a href>..</a>
есть <
, вы можете попробовать вместо этого:
grep -o '<a href=.*<\/a>'
Однако обратите внимание, что он может возвращать данные, которые вы не ожидаете для строк, в которых есть несколько <a href=...</a>
вхождений в одной и той же строке.
sed
не работает для вас, потому что /pattern1/,/pattern2/p
указывает sed
печатать все строки между строкой с pattern1
и строкой с pattern2
(, включая строки с шаблонами ).
Проблема также может быть решена с помощью sed
, но это зависит от содержимого somestupidstuff
(, например. одинаково во всех случаях или нет и т. д.)