Выполнение пользовательской функции в вызове 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.