Cette réponse se présente dans les parties suivantes:
- Utilisation basique de
-exec
- Utilisation
-exec
en combinaison avecsh -c
- En utilisant
-exec ... {} +
- En utilisant
-execdir
Utilisation basique de -exec
L' -exec
option prend un utilitaire externe avec des arguments facultatifs comme arguments et l'exécute.
Si la chaîne {}
est présente n'importe où dans la commande donnée, chaque instance sera remplacée par le chemin d'accès en cours de traitement (par exemple ./some/path/FILENAME
). Dans la plupart des coquillages, il {}
n'est pas nécessaire de citer les deux caractères .
La commande doit être terminée avec un ;
for find
pour savoir où elle se termine (car il peut y avoir d'autres options par la suite). Pour protéger le ;
shell, il doit être cité comme \;
ou ';'
, sinon le shell le verra comme la fin de la find
commande.
Exemple (la \
fin des deux premières lignes ne concerne que les continuations de lignes):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
Ceci trouvera tous les fichiers normaux ( -type f
) dont les noms correspondent au modèle *.txt
dans ou sous le répertoire actuel. Il vérifiera ensuite si la chaîne hello
apparaît dans l'un des fichiers trouvés à l'aide de grep -q
(ce qui ne produit aucune sortie, mais uniquement un état de sortie). Pour les fichiers contenant la chaîne, cat
sera exécuté pour afficher le contenu du fichier sur le terminal.
Chacun -exec
agit également comme un "test" sur les noms de chemins trouvés par find
, tout comme -type
et le -name
fait. Si la commande renvoie un état de sortie nul (signifiant "succès"), la partie suivante de la find
commande est considérée, sinon la find
commande continue avec le chemin suivant. Ceci est utilisé dans l'exemple ci-dessus pour rechercher les fichiers contenant la chaîne hello
, mais pour ignorer tous les autres fichiers.
L'exemple ci-dessus illustre les deux cas d'utilisation les plus courants de -exec
:
- Comme test pour restreindre davantage la recherche.
- Effectuer une sorte d'action sur le chemin trouvé (généralement, mais pas nécessairement, à la fin de la
find
commande).
Utilisation -exec
en combinaison avecsh -c
La commande -exec
pouvant être exécutée est limitée à un utilitaire externe avec des arguments facultatifs. Utiliser directement les fonctions intégrées au shell, les fonctions, les conditions, les pipelines, les redirections, etc. -exec
n'est pas possible, à moins d'être enveloppé dans un environnement similaire à un sh -c
shell enfant.
Si des bash
fonctionnalités sont requises, utilisez bash -c
à la place de sh -c
.
sh -c
s’exécute /bin/sh
avec un script donné sur la ligne de commande, suivi d’arguments facultatifs en ligne de commande pour ce script.
Un exemple simple d'utilisation sh -c
par lui-même, sans find
:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
Cela passe deux arguments au script shell enfant:
La ficelle sh
. Cela sera disponible comme $0
dans le script, et si le shell interne génère un message d'erreur, il le préfixera avec cette chaîne.
L'argument apples
est disponible comme $1
dans le script, et s'il y avait eu plus d'arguments, ceux-ci auraient été disponibles comme $2
, $3
etc. Ils seraient également disponibles dans la liste "$@"
(sauf ceux $0
qui ne feraient pas partie de "$@"
).
Ceci est utile en combinaison avec -exec
car cela nous permet de faire des scripts arbitrairement complexes qui agissent sur les noms de chemins trouvés par find
.
Exemple: Recherchez tous les fichiers normaux portant un suffixe de nom de fichier donné et remplacez ce suffixe par un autre suffixe, les suffixes étant conservés dans des variables:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'
Dans le script interne, $1
serait la chaîne text
, $2
serait la chaîne txt
et $3
serait tout chemin que chemin find
a trouvé pour nous. Le paramètre expansion ${3%.$1}
prendrait le chemin et en supprimait le suffixe .text
.
Ou en utilisant dirname
/ basename
:
find . -type f -name "*.$from" -exec sh -c '
mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'
ou, avec des variables ajoutées dans le script interne:
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2; pathname=$3
mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'
Notez que dans cette dernière variante, les variables from
et to
dans le shell enfant sont distinctes des variables portant le même nom dans le script externe.
Ce qui précède est la manière correcte d’appeler un script complexe arbitraire -exec
avec find
. Utiliser find
en boucle comme
for pathname in $( find ... ); do
est sujet aux erreurs et inélégant (opinion personnelle). Il divise les noms de fichiers sur des espaces, appelle la suppression du nom de fichier et oblige également le shell à développer le résultat complet find
avant même d'exécuter la première itération de la boucle.
Voir également:
En utilisant -exec ... {} +
Le ;
à la fin peut être remplacé par +
. Ceci a find
pour effet d'exécuter la commande donnée avec autant d'arguments (noms de chemins d'accès trouvés) que possible plutôt qu'une fois pour chaque chemin d'accès trouvé. La chaîne {}
doit apparaître juste avant le +
pour que cela fonctionne .
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} +
Ici, find
collectera les noms de chemin résultants et exécutera cat
le plus grand nombre possible à la fois.
find . -type f -name "*.txt" \
-exec grep -q "hello" {} ';' \
-exec mv -t /tmp/files_with_hello/ {} +
De même ici, mv
sera exécuté aussi peu de fois que possible. Ce dernier exemple nécessite GNU mv
de coreutils (qui supporte l’ -t
option).
Utiliser -exec sh -c ... {} +
est également un moyen efficace de parcourir un ensemble de chemins avec un script arbitrairement complexe.
Les bases sont les mêmes que lors de l'utilisation -exec sh -c ... {} ';'
, mais le script nécessite maintenant une liste d'arguments beaucoup plus longue. Ceux-ci peuvent être bouclés en boucle "$@"
dans le script.
Notre exemple de la dernière section qui modifie les suffixes de nom de fichier:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2
shift 2 # remove the first two arguments from the list
# because in this case these are *not* pathnames
# given to us by find
for pathname do # or: for pathname in "$@"; do
mv "$pathname" "${pathname%.$from}.$to"
done' sh "$from" "$to" {} +
En utilisant -execdir
Il existe également -execdir
(mis en œuvre par la plupart des find
variantes, mais pas une option standard).
Cela fonctionne -exec
à la différence que la commande shell donnée est exécutée avec le répertoire du chemin trouvé en tant que répertoire de travail actuel et qu'il {}
contiendra le nom de base du chemin trouvé sans son chemin (mais GNU find
préfixera toujours le nom de base avec ./
, alors que BSD find
ne fera pas ça).
Exemple:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Cela déplacera chaque *.txt
fichier- trouvé dans un done-texts
sous - répertoire préexistant dans le même répertoire que celui où le fichier a été trouvé . Le fichier sera également renommé en y ajoutant le suffixe .done
.
Ce serait un peu plus délicat à faire -exec
car il faudrait extraire le nom de base du fichier trouvé {}
pour former le nouveau nom du fichier. Nous avons également besoin du nom de répertoire de {}
pour localiser le done-texts
répertoire correctement.
Avec -execdir
, certaines choses comme celles-ci deviennent plus faciles.
L'opération correspondante utilisant à la -exec
place de -execdir
devrait utiliser un shell enfant:
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
done' sh {} +
ou,
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "${name%/*}/done-texts/${name##*/}.done"
done' sh {} +