знайти -exec функцію оболонки в Linux?


185

Чи є спосіб отримати findфункцію, яку я визначаю в оболонці? Наприклад:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;

Результатом цього є:

find: dosomething: No such file or directory

Чи є спосіб , щоб отримати find«S -execбачити dosomething?

Відповіді:


262

Оскільки тільки оболонка знає, як запускати функції оболонки, вам потрібно запустити оболонку для запуску функції. Вам також потрібно позначити свою функцію для експорту export -f, інакше підзарядка не успадкує їх:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;

7
Ти побив мене. До речі, ви можете поставити дужки всередині лапок, а не використовувати $0.
Призупинено до подальшого повідомлення.

20
Якщо ви використовуєте find . -exec bash -c 'dosomething "$0"' {} \;це, ви обробляєте пробіли (та інші дивні символи) у іменах ...
Gordon Davisson

3
@alxndr: це не вдасться назвати файли з подвійними котируваннями, зворотними котируваннями, знаками долара, деякими комбінаціями для втечі тощо ...
Гордон Девіссон

4
Зауважте також, що будь-які функції, до яких може зателефонувати ваша функція, будуть недоступні, якщо ви також не експортуєте їх.
hraban

3
export -fПрацюватиме тільки в деяких версіях Баша. Це не posix, не crossplatforn, /bin/shбуде з ним помилка
Роман Коптев

120
find . | while read file; do dosomething "$file"; done

10
Приємне рішення. Не вимагає експорту функції або заплутування навколо аргументів, що втікають, і, мабуть, більш ефективний, оскільки це не нерестування підрозділів для виконання кожної функції.
Том

19
Майте на увазі, що він порушить назви файлів, що містять нові рядки.
чепнер

3
Це більше "shell'ish", оскільки ваші глобальні змінні та функції будуть доступні, не створюючи абсолютно нової оболонки / середовища щоразу. Навчився цьому важким шляхом, випробувавши метод Адама і зіткнувшись з усілякими проблемами довкілля. Цей метод також не пошкоджує оболонку поточного користувача всім експортом і вимагає меншої кількості очікувань.
Рей Фосс

ТАКОЖ Швидше прийнятої відповіді без додаткової оболонки! НІШЕ! Дякую!
Білл Хеллер

2
також я вирішив свою проблему, змінивши while readна цикл for; for item in $(find . ); do some_function "${item}"; done
користувач5359531

22

Відповідь Джека вище є чудовою, але має кілька підводних каменів, які легко подолати:

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

Для цього використовується null як роздільник, а не передача рядків, тому назви файлів із стрічковими каналами працюватимуть. Він також використовує -rпрапор, який відключає нахил зворотної косої риси, без того, щоб рисові коси в імені файлів не працювали. Він також очищається, IFSщоб потенційні відсталі пробіли в іменах не були відкинуті.


1
Це добре, /bin/bashале не працюватиме /bin/sh. Шкода.
Роман Коптев

@ РоманКоптев Як пощастило, що принаймні це працює в / bin / bash.
sdenham

21

Додайте цитати, {}як показано нижче:

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

Це виправляє будь-яку помилку через спеціальні символи, повернуті find, наприклад файли з дужками в їх імені.


1
Це не правильний спосіб використання {}. Це порушить назву файлу, що містить подвійні лапки. touch '"; rm -rf .; echo "I deleted all you files, haha. На жаль
gniourf_gniourf

Так, це дуже погано. Це можна експлуатувати за допомогою ін'єкцій. Дуже небезпечно. Не використовуйте це!
Домінік

1
@kdubs: Використовуйте $ 0 (без котирування) у командному рядку та передайте ім'я файлу як перший аргумент: -exec bash -c 'echo $0' '{}' \;Зауважте, що при використанні bash -c$ 0 - це перший аргумент, а не ім'я сценарію.
sdenham

10

Обробка результатів оптом

Для підвищення ефективності багато людей використовують xargsобробку результатів масою, але це дуже небезпечно. Через це був введений альтернативний метод, який вводив findрезультати масово.

Зауважте, що цей метод може містити деякі застереження, наприклад, наприклад, вимога в POSIX find- мати {}в кінці команди.

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +

findпередасть багато результатів як аргументів на один виклик bashі for-loop повторюється через ці аргументи, виконуючи функцію dosomethingна кожному з них.

Вищевказане рішення починається з аргументів $1, через що існує _(що представляє $0).

Обробка результатів по черзі

Таким же чином, я вважаю, що прийняту верхню відповідь слід виправити

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;

Це не тільки більш розумно, тому що аргументи завжди слід починати $1, але також використання $0може призвести до несподіваної поведінки, якщо ім'я файлу, яке повертається, findмає спеціальне значення для оболонки.


9

Запропонуйте собі скрипт, передаючи кожен елемент, знайдений як аргумент:

#!/bin/bash

if [ ! $1 == "" ] ; then
   echo "doing something with $1"
   exit 0
fi

find . -exec $0 {} \;

exit 0

Коли ви запускаєте сценарій самостійно, він знаходить те, що шукаєте, і називає себе, передаючи кожен результат пошуку як аргумент. Коли сценарій запускається з аргументом, він виконує команди на аргументі, а потім виходить.


крута ідея, але поганий стиль: використовує один і той же сценарій для двох цілей. якщо ви хочете зменшити кількість файлів у своєму біні /, то ви можете об'єднати всі свої сценарії в один, який має на початку великий заголовок. дуже чисте рішення, чи не так?
користувач829755

не кажучи вже про це, не вдасться, find: ‘myscript.sh’: No such file or directoryякщо почнеться як bash myscript.sh
Камюнсей,

5

Для тих, хто шукає функцію bash, яка буде виконувати задану команду для всіх файлів у поточному каталозі, я склав один із наведених відповідей:

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}

Зауважте, що він порушується з іменами файлів, що містять пробіли (див. Нижче).

Як приклад, візьміть цю функцію:

world(){
    sed -i 's_hello_world_g' "$1"
}

Скажіть, що я хотів змінити всі екземпляри привіт світу у всіх файлах у поточному каталозі. Я б робив:

toall world

Щоб захистити будь-які символи у файлах файлів, використовуйте:

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}

(але вам потрібна findця ручка, -print0наприклад, GNU find).


3

Я вважаю найпростішим способом наступний спосіб, повторюючи дві команди в одному виконанні

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done

3

Для довідки я уникаю цього сценарію, використовуючи:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

Отримайте результат пошуку в поточному файлі сценарію та перейдіть на вихід, як вам захочеться. Я згоден з прийнятою відповіддю, але не хочу виставляти функцію поза моїм файлом сценарію.


це обмеження розміру
Річард

2

Таким чином виконати функцію неможливо .

Щоб подолати це, ви можете розмістити свою функцію в сценарії оболонки і викликати її з find

# dosomething.sh
dosomething () {
  echo "doing something with $1"
}
dosomething $1

Тепер використовуйте його в пошуку як:

find . -exec dosomething.sh {} \;

Намагався уникати більше файлів у моєму ~ / bin. Дякую, хоча!
alxndr

Я вважав заборону, але рішення саме по собі непогане. Будь ласка, просто скористайтеся правильним цитуванням: dosomething $1=> dosomething "$1"і правильно find . -exec bash dosomething.sh {} \;
заведіть

2

Помістіть функцію в окремий файл і приступайте findдо виконання цього.

Функції оболонки є внутрішніми для оболонки, у якій вони визначені; findніколи не зможе їх побачити.


Готча; має сенс. Намагався уникати більше файлів у моєму ~ / bin.
alxndr

2

Для того, щоб забезпечити необхідні доповнення і уточнення до деяких інші відповіді, якщо ви використовуєте опцію насипну для execабо execdir( -exec command {} +), і ви хочете , щоб отримати всі позиційні аргументи, ви повинні розглянути звернення $0з bash -c. Більш конкретно, розглянемо команду нижче, яка використовує, bash -cяк було запропоновано вище, і просто повторює шляхи до файлів, що закінчуються на ".wav" з кожного знайденого каталогу:

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

Посібник з bash говорить:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

Тут 'check $@'знаходиться командний рядок та _ {}є аргументи після командного рядка. Зауважте, що $@це спеціальний позиційний параметр у bash, який розширюється на всі позиційні параметри починаючи з 1 . Також зауважте, що за допомогою -cпараметра перший аргумент присвоюється позиційному параметру $0. Це означає, що якщо ви спробуєте отримати доступ до всіх позиційних параметрів $@, ви отримаєте лише параметри починаючи з $1і вгору. Саме тому у відповіді Домініка є той _, який є фіктивним аргументом для заповнення параметра $0, тому всі потрібні аргументи будуть доступні пізніше, якщо ми будемо використовувати, $@наприклад, розширення параметра або цикл for, як у цій відповіді.

Звичайно, подібно до прийнятої відповіді, bash -c 'shell_function $0 $@'це також діятиме, чітко проходячи $0, але знову ж таки, вам доведеться пам’ятати, що $@не буде працювати, як очікувалося.


0

Не безпосередньо, ні. Знайти виконується в окремому процесі, а не в оболонці.

Створіть скрипт оболонки, який виконує ту саму роботу, що і ваша функція, і знайдіть, чи може -execце.


Намагався уникати більше файлів у моєму ~ / bin. Дякую, хоча!
alxndr

-2

Я б уникну -execзовсім використовувати . Чому б не використовувати xargs?

find . -name <script/command you're searching for> | xargs bash -c

У той час IIRC намагався зменшити кількість використаних ресурсів. Подумайте знайти мільйони порожніх файлів та видалити їх.
alxndr
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.