¿Cómo hago para que find fall si -exec falla?

29

Cuando ejecuto este comando en el shell (en un directorio no vacío):

find . -exec invalid_command_here {} \;

Entiendo esto:

find: invalid_command_here: No such file or directory
find: invalid_command_here: No such file or directory
find: invalid_command_here: No such file or directory

(y así sucesivamente para cada archivo)

Necesito findfallar después del primer error. ¿Hay alguna manera de hacer que esto funcione? No puedo usar xargs, ya que tengo espacios en mi camino, pero necesito el script que llama a esto para devolver un código de error.

Steven Fisher
fuente

Respuestas:

34

Esta es una limitación de find. El estándar POSIX especifica que el estado de retorno findes 0 a menos que ocurra un error al recorrer los directorios; el estado de retorno de los comandos ejecutados no entra en él.

Puede hacer que los comandos escriban su estado en un archivo o en un descriptor:

find_status_file=$(mktemp findstatus)
: >"$find_status_file"
find  -exec sh -c 'trap "echo \$?" EXIT; invalid_command "$0"' {} \;
if [ -s "$find_status_file" ]; then
  echo 1>&2 "An error occurred"
fi
rm -f "$find_status_file"

Otro método, como descubrió , es usar xargs. Los xargscomandos siempre procesan todos los archivos, pero devuelve el estado 1 si alguno de los comandos devuelve un estado distinto de cero.

find  -print0 | xargs -0 -n1 invalid_command

Sin embargo, otro método es evitar findy utilizar el engrosamiento recursivo en el shell: **/significa cualquier profundidad de subdirectorios. Esto requiere la versión 4 o superior de bash; macOS está atascado en la versión 3.x, por lo que tendría que instalarlo desde una colección de puertos. Use set -epara detener el script en el primer comando que devuelve un estado distinto de cero.

shopt -s globstar
set -e
for x in **/*.xml; do invalid_command "$x"; done

Tenga en cuenta que en bash 4.0 a 4.2, esto funciona pero atraviesa enlaces simbólicos a directorios, lo que generalmente no es deseable.

Si usa zsh en lugar de bash, el engrosamiento recursivo funciona de inmediato sin problemas. Zsh está disponible por defecto en OSX / macOS. En zsh, solo puedes escribir

set -e
for x in **/*.xml; do invalid_command "$x"; done
Gilles 'SO- deja de ser malvado'
fuente
El xargsenfoque funciona en general, pero de alguna manera se rompe en los bash -ccomandos. Por ejemplo: find . -name '*.xml' -print0 | xargs -0 -n 1 -I '{}' bash -c "foo {}". Esto se ejecuta varias veces, mientras que find . -name '2*.xml' -print0 | xargs -0 -n 1 -I '{}' foo {}se ejecuta una vez y falla. ¿Alguna idea de por qué?
DKroot
@DKroot Nunca lo use en el {}interior bash -c. Esto toma el nombre del archivo y lo inserta directamente dentro del comando de shell. Si el nombre del archivo contiene caracteres que tienen un significado especial en el shell, como espacios, el shell interpreta estos caracteres especiales como tales. Si necesita un shell, pase {}como un argumento separado, por ejemplo bash -c 'foo "$0"' {}(tenga en cuenta también las comillas dobles $0).
Gilles 'SO- deja de ser malvado'
OK, citando las preguntas a un lado, ¿por qué no se detiene lo siguiente en el primer error? find . -name '*' -print0 | xargs -0 -n 1 -I '{}' bash -c 'foo "$0"' {}
DKroot
@DKroot ¿Por qué se detendría ante un error? xargs siempre ejecuta el comando en todos los elementos.
Gilles 'SO- deja de ser malvado'
Estoy tratando de usar esta respuesta: el find . -print0 | xargs -0 -n1 invalid_commandenfoque xargs ( ). Esto se detiene en el primer error correctamente: find . -name '*' -print0 | xargs -0 -n 1 -I '{}' foo {}. ¡Excelente! Pero el mismo enfoque no funciona con bash -c(arriba). La única diferencia entre los dos es bash -c.
DKroot
18

Puedo usar esto en su lugar:

find . -name *.xml -print0 | xargs -n 1 -0 invalid_command
Steven Fisher
fuente
4

xargsEs una opción. Sin embargo, en realidad es trivialmente fácil hacer esto findtambién usando en +lugar de\;

-exec  utility_name  [argument ...]   {} +

De la documentación POSIX :

Si la expresión primaria se puntúa con un signo más, la primaria siempre se evaluará como verdadera, y los nombres de ruta para los que se evalúa la primaria se agregarán en conjuntos. La utilidad utility_name se invocará una vez para cada conjunto de rutas agregadas. Cada invocación comenzará después de que se agregue el último nombre de ruta en el conjunto, y se completará antes de que salga la utilidad find y antes de que se agregue el primer nombre de ruta en el siguiente conjunto (si lo hay) para este primario, pero no se especifica si la invocación ocurre antes, durante o después de las evaluaciones de otras primarias. Si alguna invocación devuelve un valor distinto de cero como estado de salida, la utilidad de búsqueda devolverá un estado de salida distinto de cero.Un argumento que contenga solo los dos caracteres "{}" se reemplazará por el conjunto de nombres de ruta agregados, con cada nombre de ruta pasado como un argumento separado a la utilidad invocada en el mismo orden en que se agregó. El tamaño de cualquier conjunto de dos o más nombres de ruta se limitará de modo que la ejecución de la utilidad no provoque que se supere el límite del sistema {ARG_MAX}. Si hay más de un argumento que contiene solo los dos caracteres "{}", el comportamiento no se especifica.

suizo
fuente