Выполнение пользовательской функции в вызове find -exec


25

Я использую Solaris 10, и я протестировал следующее с помощью ksh (88), bash (3.00) и zsh (4.2.1).

Следующий код не дает никакого результата:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

Поиск действительно соответствует нескольким файлам (как показано заменой -exec ...на -print), и функция отлично работает при вызове вне findвызова.

Вот что man findговорится на странице -exec:

 -exec команда Истина, если выполненная команда возвращает
                     нулевое значение в качестве состояния выхода. Конец
                     Команда должна быть акцентирована сбежавшим
                     точка с запятой (;). Аргумент команды {}
                     заменяется текущим путем. Если
                     последний аргумент для -exec это {} и вы
                     укажите + вместо точки с запятой (;),
                     команда вызывается меньше раз, с
                     {} заменяется группами путей. Если
                     любой вызов команды возвращает
                     ненулевое значение в качестве статуса выхода, найти
                     возвращает ненулевой статус выхода.

Я мог бы, вероятно, уйти, делая что-то вроде этого:

for f in $(find somedir); do
    foo
done

Но я боюсь иметь дело с проблемами разделителя полей.

Можно ли вызвать функцию оболочки (определенную в том же сценарии, давайте не будем беспокоиться о проблемах с областями видимости) из find ... -exec ...вызова?

Я попробовал это с обоими /usr/bin/findи /bin/findи получил тот же результат.


Вы пытались экспортировать функцию после ее объявления? export -f foo
h3rrmiller

2
Вам нужно будет сделать функцию внешним скриптом и вставить его PATH. В качестве альтернативы используйте sh -c '...'и оба определите И запустите функцию в ...бите. Это может помочь понять различия между функциями и сценариями .
jw013

Ответы:


27

A functionявляется локальным по отношению к оболочке, поэтому вам нужно find -execбудет создать оболочку и определить эту функцию в этой оболочке, прежде чем использовать ее. Что-то типа:

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bashпозволяет экспортировать функции через окружение export -f, поэтому вы можете сделать (в bash):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88должен typeset -fxэкспортировать функцию (не через окружение), но она может использоваться только сценариями, выполняемыми she-bang less ksh, но не с помощью ksh -c.

Другой вариант заключается в следующем:

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

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


2
Можете ли вы объяснить, почему есть два вхождения kshили bashв -execкоманде? Я понимаю первое, но не второе происшествие.
Даниэль Куллманн

6
@danielkullmann In bash -c 'some-code' a b c, $0is a, $1is b..., поэтому, если вы хотите $@быть, a, b, cвам нужно вставить что-то раньше. Поскольку $0также используется при отображении сообщений об ошибках, рекомендуется использовать имя оболочки или что-то, что имеет смысл в этом контексте.
Стефан Шазелас

@ StéphaneChazelas, спасибо за такой хороший ответ.
User9102d82

5

Это не всегда применимо, но когда это так, это простое решение. Установите globstarопцию ( set -o globstarв ksh93, shopt -s globstarв bash ≥4; по умолчанию она включена в zsh). Затем используйте **/для рекурсивного сопоставления текущего каталога и его подкаталогов.

Например, вместо find . -name '*.txt' -exec somecommand {} \;, вы можете запустить

for x in **/*.txt; do somecommand "$x"; done

Вместо этого find . -type d -exec somecommand {} \;вы можете запустить

for d in **/*/; do somecommand "$d"; done

Вместо этого find . -newer somefile -exec somecommand {} \;вы можете запустить

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

Если у **/вас не работает (потому что ваша оболочка его не имеет или вам нужна findопция, у которой нет аналога оболочки), определите функцию в find -execаргументе .


Я не могу найти globstarвариант для версии ksh ( ksh88), которую я использую.
Рахму

@rahmu Действительно, это ново в ksh93. У Solaris 10 где-нибудь есть ksh93?
Жиль "ТАК ... перестать быть злым"

Согласно этому сообщению в блоге в Solaris 11 был представлен ksh93 для замены оболочки Bourne Shell и ksh88 ...
rahmu

@rahmu. В Solaris некоторое время dtkshсуществовала версия ksh93 (ksh93 с некоторыми расширениями X11), но старая версия и, возможно, часть дополнительного пакета, где CDE не является обязательным.
Стефан Шазелас

Следует отметить, что рекурсивное глобирование отличается от того, findчто оно исключает точечные файлы и не спускается в точечные каталоги, а также сортирует список файлов (оба из которых могут быть адресами в zsh через классификаторы глобинга). Также **/*в отличие от ./**/*, имена файлов могут начинаться с -.
Стефан Шазелас

4

Используйте \ 0 в качестве разделителя и считывайте имена файлов в текущий процесс из порожденной команды, например так:

foo() {
  printf "Hello {%s}\n" "$1"
}

while read -d '' filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

Что тут происходит:

  • read -d '' читает до следующего байта \ 0, поэтому вам не нужно беспокоиться о странных символах в именах файлов.
  • аналогично, -print0использует \ 0 для завершения каждого сгенерированного имени файла вместо \ n.
  • cmd2 < <(cmd1)аналогично тому, cmd1 | cmd2что cmd2 запускается в основной оболочке, а не в подоболочке.
  • вызов foo перенаправлен с /dev/nullтого, чтобы он случайно не читал из канала.
  • $filename в кавычках, поэтому оболочка не пытается разделить имя файла, содержащее пробел.

Теперь read -dи <(...)в Zsh, Баш и КШ 93u, но я не уверен , о ранее КШ версии.


4

если вы хотите, чтобы дочерний процесс, порожденный из вашего скрипта, использовал предопределенную функцию оболочки, вам нужно экспортировать его с export -f <function>

ПРИМЕЧАНИЕ: export -fэто специфично для bash

поскольку только оболочка может выполнять функции оболочки :

find / -exec /bin/bash -c 'function "$1"' bash {} \;

РЕДАКТИРОВАТЬ: по сути, ваш сценарий должен выглядеть следующим образом:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;

1
export -fявляется синтаксической ошибкой в ksh, и выводит определение функции на экран в zsh. В целом ksh, zshи bashэто не решает проблему.
Рахму

1
find / -exec /bin/bash -c 'function' \;
h3rrmiller

Теперь это работает, но только с bash. Спасибо!
Рахму

4
Никогда не вставляйте {}в код оболочки! Это означает, что имя файла интерпретируется как шелл-код, что очень опасно
Стефан Шазелас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.