Ejecutar la función definida por el usuario en una llamada find -exec

25

Estoy en Solaris 10 y he probado lo siguiente con ksh (88), bash (3.00) y zsh (4.2.1).

El siguiente código no produce ningún resultado:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

El hallazgo coincide con varios archivos (como se muestra al reemplazar -exec ...con -print), y la función funciona perfectamente cuando se llama fuera de la findllamada.

Esto es lo que man finddice la página -exec:

 -exec comando True si el comando ejecutado devuelve un
                     valor cero como estado de salida. El fin de
                     el comando debe ser puntuado por un escape
                     punto y coma (;). Un argumento de comando {} es
                     reemplazado por la ruta actual. Si el
                     El último argumento para -exec es {} y usted
                     especifique + en lugar del punto y coma (;),
                     el comando se invoca menos veces, con
                     {} reemplazado por grupos de nombres de ruta. Si
                     cualquier invocación del comando devuelve un
                     valor distinto de cero como estado de salida, buscar
                     devuelve un estado de salida distinto de cero.

Probablemente podría escapar haciendo algo como esto:

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

Pero tengo miedo de lidiar con los problemas de separación de campos.

¿Es posible llamar a una función de shell (definida en el mismo script, no nos molestemos con problemas de alcance) desde una find ... -exec ...llamada?

Lo he probado con ambos /usr/bin/findy /bin/findy obtuve el mismo resultado.

rahmu
fuente
¿Intentaste exportar la función después de declararla? export -f foo
h3rrmiller
2
Deberá hacer que la función sea un script externo y ponerla PATH. Alternativamente, use sh -c '...'y ambos definan Y ejecuten la función en el ...bit. Puede ser útil comprender las diferencias entre funciones y scripts .
jw013

Respuestas:

27

A functiones local para un shell, por lo que necesitaría find -execgenerar un shell y tener esa función definida en ese shell antes de poder usarlo. Algo como:

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

bashpermite exportar funciones a través del entorno export -f, por lo que puede hacer (en bash):

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

ksh88tiene typeset -fxque exportar la función (no a través del entorno), pero eso solo puede ser usado por scripts de she-bang less ejecutados por ksh, así que no con ksh -c.

Otra opción es hacer:

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

Es decir, use typeset -fpara volcar la definición de la foofunción dentro del script en línea. Tenga en cuenta que si foousa otras funciones, también deberá volcarlas.

Stéphane Chazelas
fuente
2
¿Puede explicar por qué hay dos ocurrencias de ksho bashen el -execcomando? Entiendo la primera, pero no la segunda ocurrencia.
daniel kullmann
66
@danielkullmann In bash -c 'some-code' a b c, $0is a, $1is b..., así que si quieres $@serlo, a, b, cdebes insertar algo antes. Debido a $0que también se usa cuando se muestran mensajes de error, es una buena idea usar el nombre del shell, o algo que tenga sentido en ese contexto.
Stéphane Chazelas
@ StéphaneChazelas, gracias por tan buena respuesta.
Usuario9102d82
5

Esto no siempre es aplicable, pero cuando lo es, es una solución simple. Establezca la globstaropción ( set -o globstaren ksh93, shopt -s globstaren bash ≥4; está activada por defecto en zsh). Luego, use **/para hacer coincidir el directorio actual y sus subdirectorios de forma recursiva.

Por ejemplo, en lugar de find . -name '*.txt' -exec somecommand {} \;, puedes ejecutar

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

En lugar de find . -type d -exec somecommand {} \;, puedes correr

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

En lugar de find . -newer somefile -exec somecommand {} \;, puedes correr

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

Cuando **/no funcione para usted (porque su shell no lo tiene o porque necesita una findopción que no tenga un análogo de shell), defina la función en el find -execargumento .

Gilles 'SO- deja de ser malvado'
fuente
Parece que no puedo encontrar una globstaropción en la versión de ksh ( ksh88) que estoy usando.
rahmu
@rahmu De hecho, es nuevo en ksh93. ¿Solaris 10 no tiene un ksh93 en alguna parte?
Gilles 'SO- deja de ser malvado'
De acuerdo con esta entrada de blog ksh93 se ha introducido en Solaris 11 para reemplazar tanto el Shell Bourne y ksh88 ...
rahmu
@rahmu. Solaris ha tenido ksh93 durante un tiempo como dtksh(ksh93 con algunas extensiones X11), pero una versión anterior y posiblemente parte de un paquete opcional donde CDE es opcional.
Stéphane Chazelas
Cabe señalar que el globbing recursivo difiere de findque excluye los archivos de puntos y no desciende en dotdirs y que clasifica la lista de archivos (los cuales pueden ser direcciones en zsh a través de calificadores de globbing). También **/*en lugar de ./**/*, los nombres de archivo pueden comenzar con -.
Stéphane Chazelas
4

Use \ 0 como delimitador y lea los nombres de archivo en el proceso actual desde un comando generado, de esta manera:

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

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

Que está pasando aqui:

  • read -d '' lee hasta el siguiente \ 0 byte, por lo que no tiene que preocuparse por caracteres extraños en los nombres de archivo.
  • de manera similar, -print0usa \ 0 para terminar cada nombre de archivo generado en lugar de \ n.
  • cmd2 < <(cmd1)es lo mismo que cmd1 | cmd2excepto que cmd2 se ejecuta en el shell principal y no en un subshell.
  • la llamada a foo se redirige /dev/nullpara garantizar que no se lea accidentalmente desde la tubería.
  • $filename se cita para que el shell no intente dividir un nombre de archivo que contiene espacios en blanco.

Ahora, read -dy <(...)están en zsh, bash y ksh 93u, pero no estoy seguro de las versiones anteriores de ksh.

Aecolley
fuente
4

si desea un proceso secundario, generado a partir de su script, para usar una función de shell predefinida, debe exportarlo con export -f <function>

NOTA: export -fes bash específico

ya que solo un shell puede ejecutar funciones de shell :

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

EDITAR: esencialmente su script debería parecerse a esto:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;
h3rrmiller
fuente
1
export -fes un error de sintaxis kshe imprime la definición de la función en la pantalla zsh. En total ksh, zshy bash, no resuelve el problema.
rahmu
1
find / -exec /bin/bash -c 'function' \;
h3rrmiller
Ahora funciona, pero solo con bash. ¡Gracias!
rahmu
44
¡Nunca incrustar {}en el código de shell! Eso significa que el nombre del archivo se interpreta como código shell también lo es muy peligroso
Stéphane Chazelas