Ця відповідь наводиться в наступних частинах:
- Основне використання
-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 {} +