Ця відповідь наводиться в наступних частинах:
- Основне використання
-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
можна виконати, обмежується зовнішньою утилітою з необов'язковими аргументами. Використовувати вбудовані оболонки, функції, умовні умови, трубопроводи, перенаправлення тощо безпосередньо безпосередньо -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 {} +