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

185

Есть ли способ получить findвыполнение функции, которую я определяю в оболочке? Например:

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

Результат этого:

find: dosomething: No such file or directory

Есть ли способ получить find, -execчтобы увидеть dosomething?

alxndr
источник

Ответы:

262

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

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;
Адам Розенфилд
источник
7
Ты ударил меня. Кстати, вы можете поставить фигурные скобки внутри кавычек вместо использования $0.
Приостановлено до дальнейшего уведомления.
20
Если вы используете find . -exec bash -c 'dosomething "$0"' {} \;его, он будет обрабатывать пробелы (и другие странные символы) в именах файлов ...
Гордон Дэвиссон
3
@alxndr: произойдет сбой в именах файлов с двойными кавычками, обратными кавычками, знаками доллара, некоторыми escape-комбо и т. д.
Гордон Дэвиссон
4
Также обратите внимание, что любые функции, которые может вызывать ваша функция , будут недоступны, если вы также не экспортируете их.
Храбан
3
export -fБудет работать только в некоторых версиях Баша. Это не posix, не кроссплатфорн, с ним /bin/shбудет ошибка
Роман Коптев
120
find . | while read file; do dosomething "$file"; done
Jac
источник
10
Хорошее решение. Не требует экспорта функции или работы с экранирующими аргументами и, предположительно, более эффективен, так как не запускает подоболочки для выполнения каждой функции.
Том
19
Имейте в виду, однако, что он будет ломаться на имена файлов, содержащие переводы строк.
Chepner
3
Это более "shell'ish", так как ваши глобальные переменные и функции будут доступны без создания совершенно новой оболочки / среды каждый раз. Научился этому нелегко, попробовав метод Адама и столкнувшись со всевозможными проблемами окружающей среды. Этот метод также не повреждает оболочку текущего пользователя при всех операциях экспорта и требует меньших затрат.
Рэй Фосс
НАМНОГО БЫСТРЕЕ, чем принятый ответ без субоболочки! ОТЛИЧНО! Спасибо!
Билл Хеллер
2
также я исправил свою проблему, изменив while readцикл for; for item in $(find . ); do some_function "${item}"; done
user5359531
22

Ответ Яка выше великолепен, но в нем есть несколько ловушек, которые легко преодолеть:

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

Это использует нуль как разделитель вместо перевода строки, так что имена файлов с переводом строки будут работать. Он также использует -rфлаг, который отключает обратную косую черту, без обратной косой черты в именах файлов не будет работать. Он также очищается, IFSтак что потенциальные конечные пробелы в именах не удаляются.

pajamian
источник
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имеет особое значение для оболочки.

Dominik
источник
9

Пусть скрипт вызовет сам себя, передавая каждый найденный элемент в качестве аргумента:

#!/bin/bash

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

find . -exec $0 {} \;

exit 0

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

Майк Мариди
источник
классная идея, но плохой стиль: использует один и тот же сценарий для двух целей. если вы хотите уменьшить количество файлов в вашей корзине /, то вы можете объединить все свои скрипты в один, в начале которого есть большое предложение case. очень чистое решение, не так ли?
user829755
не говоря уже о том, что это не удастся, find: ‘myscript.sh’: No such file or directoryесли начать как bash myscript.sh...
Camusensei
5

Для тех из вас, кто ищет функцию bash, которая будет выполнять данную команду для всех файлов в текущем каталоге, я скомпилировал один из приведенных выше ответов:

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

Обратите внимание, что он разрывается с именами файлов, содержащими пробелы (см. Ниже).

В качестве примера возьмем эту функцию:

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

Скажем, я хотел изменить все экземпляры hello to world во всех файлах в текущем каталоге. Я бы сделал:

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
edib
источник
3

Для справки, я избегаю этого сценария, используя:

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

Получите вывод команды find в текущем файле сценария и выполните итерацию по выводу, как вам нужно. Я согласен с принятым ответом, но я не хочу показывать функцию за пределами моего файла сценария.

Динеш Сайни
источник
у этого есть ограничения размера
Ричард
2

Невозможно выполнить функцию таким способом.

Чтобы преодолеть это, вы можете поместить свою функцию в скрипт оболочки и вызывать ее из find

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

Теперь используйте его в find как:

find . -exec dosomething.sh {} \;
codaddict
источник
Пытался избежать больше файлов в моем ~ / bin. Спасибо хоть!
alxndr
Я рассмотрел снижение голосов, но само по себе решение не является плохим. Пожалуйста, используйте правильное цитирование: dosomething $1=> dosomething "$1"и начните свой файл правильно сfind . -exec bash dosomething.sh {} \;
Camusensei
2

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

Функции оболочки являются внутренними по отношению к оболочке, в которой они определены; findникогда не сможет их увидеть.

Angus
источник
Попался; имеет смысл. Хотя пытался избежать больше файлов в моем ~ / 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, но опять же, вы должны иметь в виду, что $@это не сработает, как ожидалось.

firedrillsergeant
источник
0

Не напрямую, нет. Find выполняется в отдельном процессе, а не в вашей оболочке.

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

Лоуренс Гонсалвес
источник
Пытался избежать больше файлов в моем ~ / bin. Спасибо хоть!
alxndr
-2

Я бы не использовал -execвообще. Почему бы не использовать xargs?

find . -name <script/command you're searching for> | xargs bash -c
Барри
источник
В то время IIRC пытался сократить объем используемых ресурсов. Подумайте о том, чтобы найти миллионы пустых файлов и удалить их.
alxndr