Je suis constamment à la recherche de la syntaxe de
find . -name "FILENAME" -exec rm {} \;
principalement parce que je ne vois pas comment fonctionne exactement la partie -exec
. Quelle est la signification des accolades, de la barre oblique inverse et du point-virgule? Existe-t-il dautres cas dutilisation pour cette syntaxe?
Commentaires
Réponse
Cette réponse se décline dans les parties suivantes:
- Utilisation de base de
-exec
- Utilisation de
-exec
en combinaison avecsh -c
- Utilisation de
-exec ... {} +
- Utilisation de
-execdir
Utilisation basique de -exec
Loption -exec
prend un utilitaire externe avec des arguments facultatifs comme son argument et lexécute.
Si la chaîne {}
est présente nimporte où dans la commande donnée, chaque instance de celle-ci sera remplacée par le chemin en cours de traitement ( par exemple ./some/path/FILENAME
). Dans la plupart des shells, les deux caractères {}
nont pas besoin dêtre entre guillemets.
La commande doit être terminé par un ;
pour find
pour savoir où il se termine (car il peut y avoir dautres options par la suite s). Pour protéger le ;
du shell, il doit être cité comme \;
ou ";"
, sinon le shell le verra comme la fin de la commande find
.
Exemple (le \
au la fin des deux premières lignes est juste pour les continuations de ligne):
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 courant. Il testera ensuite si la chaîne hello
apparaît dans lun des fichiers trouvés en utilisant grep -q
(qui ne produit aucune sortie, juste une sortie statut). Pour les fichiers contenant la chaîne, cat
sera exécuté pour afficher le contenu du fichier vers le terminal.
Chaque -exec
agit également comme un « test » sur les chemins trouvés par find
, tout comme -type
et -name
fait. Si la commande renvoie un statut de sortie nul (signifiant « succès »), la partie suivante de la commande find
est prise en compte, sinon la find
La commande continue avec le chemin suivant. Ceci est utilisé dans lexemple ci-dessus pour rechercher les fichiers contenant la chaîne hello
, mais pour ignorer tous les autres fichiers.
Lexemple ci-dessus illustre les deux utilisations les plus courantes cas de -exec
:
- Comme test pour restreindre davantage la recherche.
- Pour effectuer une sorte daction sur le trouvé chemin daccès (généralement, mais pas nécessairement, à la fin de la commande
find
).
Utilisation de -exec
en combinaison avec sh -c
La commande que -exec
peut exécuter est limitée à un utilitaire externe avec des arguments optionnels.Utiliser directement les fonctions intégrées du shell, les fonctions, les conditions, les pipelines, les redirections, etc. avec -exec
, à moins dêtre enveloppé dans quelque chose comme un sh -c
shell enfant.
Si des fonctionnalités bash
sont requises, utilisez bash -c
à la place de sh -c
.
sh -c
exécute /bin/sh
avec un script donné sur la ligne de commande, suivi darguments de ligne de commande facultatifs pour ce script.
Un exemple simple dutilisation de sh -c
seul, sans find
:
sh -c "echo "You gave me $1, thanks!"" sh "apples"
Ceci passe deux arguments au script shell enfant. Ceux-ci seront placés dans $0
et $1
pour le script à utiliser.
-
Le chaîne
sh
. Ce sera disponible sous la forme$0
dans le script, et si le shell interne génère un message derreur, il le préfixera avec cette chaîne. -
Largument
apples
est disponible en tant que$1
dans le script, et sil y avait eu plus darguments, ils auraient été disponibles comme$2
,$3
etc. Ils seraient également disponibles dans la liste"$@"
(sauf pour$0
qui ne ferait pas partie de"$@"
).
Ceci est utile en combinaison avec -exec
car il nous permet de créer des scripts arbitrairement complexes qui agissent sur les chemins trouvés par find
.
Exemple: Trouvez tous les fichiers normaux qui ont un certain suffixe de nom de fichier, et changez ce suffixe de nom de fichier en un autre suffixe, où les suffixes sont 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" {} ";"
À lintérieur de lentier rnal script, $1
serait la chaîne text
, $2
serait la chaîne txt
et $3
serait le chemin que find
a trouvé pour nous. Lexpansion du paramètre ${3%.$1}
prendrait le chemin et en supprimerait 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 distincts des variables portant les mêmes noms dans le script externe.
Ce qui précède est la manière correcte dappeler un script complexe arbitraire à partir de -exec
avec find
. Utiliser find
dans une 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, invoque le globbing des noms de fichiers et force également le shell à étendre le résultat complet de find
avant même dexécuter la première itération de la boucle.
Voir aussi:
- Pourquoi une boucle sur la sortie de find ' est-elle une mauvaise pratique?
- Est-il possible dutiliser `find -exec sh -c` en toute sécurité?
Utiliser -exec ... {} +
Le ;
à la fin peut être remplacé par +
. Cela amène find
à exécuter la commande donnée avec autant darguments (chemins trouvés) que possible plutôt quune fois pour chaque chemin 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 chemins daccès résultants et exécutera cat
sur autant dentre eux que 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é comme quelques fois que possible. Ce dernier exemple nécessite GNU mv
de coreutils (qui prend en charge loption -t
).
Utilisation de -exec sh -c ... {} +
est également un moyen efficace de boucler sur un ensemble de chemins avec un script arbitrairement complexe.
Les bases sont les mêmes que lors de lutilisation de -exec sh -c ... {} ";"
, mais le script prend maintenant une liste darguments beaucoup plus longue. Ceux-ci peuvent être bouclés en boucle sur "$@"
à lintérieur du script.
Notre exemple de la dernière section qui change 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" {} +
Utilisation de -execdir
Il y a aussi -execdir
( implémentée par la plupart des variantes find
, mais pas une option standard).
Cela fonctionne comme -exec
à la différence que la commande shell donnée est exécutée avec le répertoire du chemin trouvé comme répertoire de travail actuel et que {}
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 le fera pas).
Exemple:
find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \;
Cela déplacera chaque *.txt
-fichier trouvé vers un sous-répertoire done-texts
dans le même répertoire que celui où se trouvait le fichier trouvé . Le fichier sera également renommé en y ajoutant le suffixe .done
.
Ce serait un peu plus délicat à faire avec -exec
car nous devrions obtenir le nom de base du fichier trouvé à partir de {}
pour former le nouveau nom du fichier. Nous avons également besoin du nom de répertoire de {}
pour localiser correctement le répertoire done-texts
.
Avec -execdir
, certaines choses comme celles-ci deviennent plus faciles.
Lopération correspondante en utilisant -exec
au lieu 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 {} +
Commentaires
-
-exec
prend un programme et des arguments et lexécute; certaines commandes shell consistent uniquement en un programme et des arguments, mais beaucoup ne le sont pas. Une commande shell peut inclure la redirection et la tuyauterie;-exec
ne peut pas (bien que lensemble defind
puisse être redirigé). Une commande shell peut utiliser; && if
etc;-exec
ne peut pas, bien que-a -o
puisse en faire. Une commande shell peut être un alias ou une fonction shell, ou intégrée;-exec
ne peut pas. Une commande shell peut développer vars;-exec
ne peut pas (bien que le shell externe qui exécute lefind
le puisse). Une commande shell peut remplacer$(command)
différemment à chaque fois;-exec
ne peut pas. … - Le dire ' sa commande shell est incorrect ici,
find -exec cmd arg \;
ne ' t invoquer un shell pour interpréter une ligne de commande shell, il exécuteexeclp("cmd", "arg")
directement, pasexeclp("sh", "-c", "cmd arg")
(pour lequel le shell finirait par faire léquivalent deexeclp("cmd", "arg")
sicmd
nétait pas intégré). - Vous pourriez clarifier que tout les arguments
find
après-exec
et jusquà;
ou+
composent la commande à exécuter avec ses arguments, chaque instance dun argument{}
remplacée par le fichier courant (par;
), et{}
comme dernier argument avant+
remplacé par une liste de fichiers comme arguments séparés (dans le{} +
cas). IOW-exec
prend plusieurs arguments, terminés par un;
ou{}
+
. - @Kusalananda wouldn ' t votre dernier exemple fonctionne également avec cette commande plus simple:
find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'
? - @Atralb Oui, cela aurait également fonctionné et aurait eu le même effet que le dernier morceau de code, mais au lieu dexécuter
mv
dans une boucle, une fois par fichier trouvé, vous exécutez à la foissh
etmv
pour chaque fichier trouvé, qui sera sensiblement plus lent pour de grandes quantités de fichiers.
man
lit Un nom_utilitaire ou un argument contenant uniquement les deux caractères " {} " doivent être remplacés d par le chemin daccès actuel , ce qui me semble suffisant. De plus, il a un exemple avec-exec rm {} \;
, comme dans votre question. À mon époque, il n’y avait guère d’autres ressources que le " grand mur gris ", des livres de pages (le papier était plus agréable que le stockage). Je sais donc que cela suffit pour quelquun de nouveau sur le sujet. Votre dernière question est cependant juste à poser ici. Malheureusement, ni @Kusalananda ni moi-même navons de réponse à cela.