Comment puis-je faire échouer la recherche si -exec échoue?

29

Lorsque j'exécute cette commande dans le shell (dans un répertoire non vide):

find . -exec invalid_command_here {} \;

J'ai compris:

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

(et ainsi de suite pour chaque fichier)

J'ai besoin findd'échouer après la première erreur. Existe-t-il un moyen de faire fonctionner cela? Je ne peux pas utiliser xargs, car j'ai des espaces sur mon chemin, mais j'ai besoin du script appelant ceci pour renvoyer un code d'erreur.

Steven Fisher
la source

Réponses:

34

Il s'agit d'une limitation de find. La norme POSIX spécifie que l'état de retour findest 0 sauf si une erreur s'est produite lors de la traversée des répertoires; l'état de retour des commandes exécutées n'y entre pas.

Vous pouvez faire en sorte que les commandes écrivent leur état dans un fichier ou dans un descripteur:

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"

Comme vous l'avez découvert , une autre méthode consiste à utiliser des xargs. Les xargscommandes traitent toujours tous les fichiers, mais renvoie l'état 1 si l'une des commandes renvoie un état différent de zéro.

find  -print0 | xargs -0 -n1 invalid_command

Une autre méthode consiste à éviter findet à utiliser à la place le globbing récursif dans le shell: **/signifie n'importe quelle profondeur de sous-répertoires. Cela nécessite la version 4 ou supérieure de bash; macOS est bloqué à la version 3.x, vous devez donc l'installer à partir d'une collection de ports. Permet set -ed'arrêter le script sur la première commande renvoyant un état différent de zéro.

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

Attention, dans bash 4.0 à 4.2, cela fonctionne mais traverse des liens symboliques vers des répertoires, ce qui n'est généralement pas souhaitable.

Si vous utilisez zsh au lieu de bash, le globbing récursif fonctionne dès la sortie de la boîte sans gotchas. Zsh est disponible par défaut sur OSX / macOS. En zsh, vous pouvez simplement écrire

set -e
for x in **/*.xml; do invalid_command "$x"; done
Gilles 'SO- arrête d'être méchant'
la source
L' xargsapproche fonctionne en général mais casse en quelque sorte sur les bash -ccommandes. Par exemple: find . -name '*.xml' -print0 | xargs -0 -n 1 -I '{}' bash -c "foo {}". Ceci est exécuté plusieurs fois tandis qu'il find . -name '2*.xml' -print0 | xargs -0 -n 1 -I '{}' foo {}est exécuté une fois et échoue. Une idée pourquoi?
DKroot
@DKroot Ne jamais utiliser à l' {}intérieur bash -c. Cela prend le nom du fichier et l'insère directement dans la commande shell. Si le nom de fichier contient des caractères qui ont une signification spéciale dans le shell, tels que des espaces, le shell interprète ces caractères spéciaux comme tels. Si vous avez besoin d'un shell, passez {}comme un argument séparé, par exemple bash -c 'foo "$0"' {}(notez également les guillemets $0).
Gilles 'SO- arrête d'être méchant'
OK, en citant des questions de côté, pourquoi les éléments suivants ne s'arrêtent-ils pas à la première erreur ?? find . -name '*' -print0 | xargs -0 -n 1 -I '{}' bash -c 'foo "$0"' {}
DKroot
@DKroot Pourquoi s'arrêterait-il en cas d'erreur? xargs exécute toujours la commande sur tous les éléments.
Gilles 'SO- arrête d'être méchant'
J'essaie d'utiliser cette réponse: l' find . -print0 | xargs -0 -n1 invalid_commandapproche xargs ( ). Cela arrête le premier correctement erreur: find . -name '*' -print0 | xargs -0 -n 1 -I '{}' foo {}. Génial! Mais la même approche ne fonctionne pas avec bash -c(ci-dessus). La seule différence entre les deux est bash -c.
DKroot
18

Je peux l'utiliser à la place:

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

xargsest une option. Cependant, il est en fait trivialement facile de le faire findégalement en utilisant +au lieu de\;

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

De la documentation POSIX :

Si l'expression principale est ponctuée d'un signe plus, le primaire doit toujours être évalué comme vrai et les noms de chemin pour lesquels le primaire est évalué doivent être agrégés en ensembles. L'utilitaire nom_utilitaire doit être invoqué une fois pour chaque ensemble de noms de chemin agrégés. Chaque appel doit commencer après que le dernier nom de chemin dans l'ensemble est agrégé et doit être terminé avant que l'utilitaire de recherche ne se termine et avant que le premier nom de chemin dans l'ensemble suivant (le cas échéant) ne soit agrégé pour ce primaire, mais il n'est pas spécifié autrement si l'invocation se produit avant, pendant ou après les évaluations des autres primaires. Si une invocation renvoie une valeur non nulle comme état de sortie, l'utilitaire find doit retourner un état de sortie non nul.Un argument ne contenant que les deux caractères «{}» doit être remplacé par l'ensemble de noms de chemin agrégés, chaque nom de chemin étant transmis comme argument distinct à l'utilitaire appelé dans le même ordre qu'il a été agrégé. La taille de tout ensemble de deux ou plusieurs chemins d'accès doit être limitée de sorte que l'exécution de l'utilitaire ne provoque pas le dépassement de la limite {ARG_MAX} du système. Si plusieurs arguments contenant uniquement les deux caractères «{}» sont présents, le comportement n'est pas spécifié.

Suisse
la source