Виконання визначеної користувачем функції у виклику 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 True, якщо виконана команда повертає a
                     нульове значення як статус виходу. Кінець
                     команда повинна бути прописана втечею
                     крапка з комою (;). Аргумент команди {} є
                     замінено поточною назвою шляху. Якщо
                     Останній аргумент до -exec - це {} і ви
                     вказати +, а не крапку з комою (;),
                     команда викликається менше разів, з
                     {} замінено групами імен шляхів. Якщо
                     будь-яке виклик команди повертає a
                     ненульове значення як статус виходу, знайти
                     повертає ненульовий статус виходу.

Можливо, я міг би піти, роблячи щось подібне:

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 менших сценаріїв, виконаних ksh, так не з ksh -c.

Ще один варіант:

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

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


2
Чи можете ви пояснити, чому в команді є два входження kshабо bashв -execкоманді? Я розумію перше, але не друге явище.
Даніель Куллман

6
@danielkullmann В bash -c 'some-code' a b c, $0є a, $1є 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

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

Відповідно до цього повідомлення в блозі ksh93 було введено в Solaris 11, щоб замінити оболонку Борна і ksh88 ...
rahmu

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

Слід зазначити, що рекурсивне глобулювання відрізняється findтим, що воно виключає dotfiles і не спускається в dotdirs, і воно сортує список файлів (обидва з них можуть бути адресами в 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, bash та ksh 93u, але я не впевнений у попередніх версіях ksh.


4

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

ПРИМІТКА: export -fспецифічний баш

оскільки лише оболонка може виконувати функції оболонки :

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

EDIT: по суті ваш сценарій повинен нагадувати такий:

#!/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це не вирішує проблему.
rahmu

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

Зараз це працює, але тільки з bash. Дякую!
rahmu

4
Ніколи не вкладайте {}код оболонки! Це означає, що назва файлу інтерпретується як код оболонки, тому це дуже небезпечно
Stéphane Chazelas
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.