Cette réponse se présente dans les parties suivantes:
- Utilisation basique de
-exec
- Utilisation
-execen combinaison avecsh -c
- En utilisant
-exec ... {} +
- En utilisant
-execdir
Utilisation basique de -exec
L' -execoption 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 findpour 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 findcommande.
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 *.txtdans ou sous le répertoire actuel. Il vérifiera ensuite si la chaîne helloapparaî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, catsera exécuté pour afficher le contenu du fichier sur le terminal.
Chacun -execagit également comme un "test" sur les noms de chemins trouvés par find, tout comme -typeet le -namefait. Si la commande renvoie un état de sortie nul (signifiant "succès"), la partie suivante de la findcommande est considérée, sinon la findcommande 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
findcommande).
Utilisation -execen combinaison avecsh -c
La commande -execpouvant ê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. -execn'est pas possible, à moins d'être enveloppé dans un environnement similaire à un sh -cshell enfant.
Si des bashfonctionnalités sont requises, utilisez bash -cà la place de sh -c.
sh -cs’exécute /bin/shavec 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 -cpar 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 $0dans le script, et si le shell interne génère un message d'erreur, il le préfixera avec cette chaîne.
L'argument applesest disponible comme $1dans le script, et s'il y avait eu plus d'arguments, ceux-ci auraient été disponibles comme $2, $3etc. Ils seraient également disponibles dans la liste "$@"(sauf ceux $0qui ne feraient pas partie de "$@").
Ceci est utile en combinaison avec -execcar 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, $1serait la chaîne text, $2serait la chaîne txtet $3serait tout chemin que chemin finda 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 fromet todans 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 -execavec find. Utiliser finden 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 findavant 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 findpour 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, findcollectera les noms de chemin résultants et exécutera catle 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, mvsera exécuté aussi peu de fois que possible. Ce dernier exemple nécessite GNU mvde coreutils (qui supporte l’ -toption).
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 findvariantes, 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 findpréfixera toujours le nom de base avec ./, alors que BSD findne fera pas ça).
Exemple:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Cela déplacera chaque *.txtfichier- trouvé dans un done-textssous - 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 -execcar 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-textsrépertoire correctement.
Avec -execdir, certaines choses comme celles-ci deviennent plus faciles.
L'opération correspondante utilisant à la -execplace de -execdirdevrait 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 {} +