Je me retrouve constamment à chercher la syntaxe de
find . -name "FILENAME" -exec rm {} \;
principalement parce que je ne vois pas exactement comment le -exec
la pièce fonctionne. Quelle est la signification des accolades, de la barre oblique inverse et du point-virgule? Existe-t-il d'autres cas d'utilisation pour cette syntaxe?
Cette réponse se présente dans les parties suivantes:
-exec
-exec
en combinaison avec sh -c
-exec ... {} +
-execdir
-exec
Le -exec
option prend un utilitaire externe avec des arguments optionnels comme argument et l'exécute.
Si la chaîne {}
est présent n'importe où dans la commande donnée, chaque instance de celle-ci sera remplacée par le chemin d'accès en cours de traitement (par exemple ./some/path/FILENAME
). Dans la plupart des shells, les deux caractères {}
n'a pas besoin d'être cité.
La commande doit se terminer par un ;
pour find
pour savoir où cela se termine (car il peut y avoir d'autres options par la suite). Pour protéger le ;
du Shell, il doit être cité comme \;
ou ';'
, sinon le Shell la verra comme la fin de la commande find
.
Exemple (le \
à la fin des deux premières lignes sont juste pour les continuations de ligne):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
Cela trouvera tous les fichiers réguliers (-type f
) dont les noms correspondent au modèle *.txt
dans ou sous le répertoire actuel. Il testera ensuite si la chaîne hello
apparaît dans l'un des fichiers trouvés à l'aide de grep -q
(qui ne produit aucune sortie, juste un état de sortie). Pour les fichiers contenant la chaîne, cat
sera exécuté pour sortir le contenu du fichier vers le terminal.
Chaque -exec
agit également comme un "test" sur les chemins d'accès trouvés par find
, tout comme -type
et -name
Est-ce que. Si la commande renvoie un état de sortie nul (signifiant "succès"), la partie suivante de la commande find
est prise en compte, sinon la commande find
continue avec le nom de chemin suivant. Ceci est utilisé dans l'exemple ci-dessus pour rechercher des 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
:
find
).-exec
en combinaison avec sh -c
La commande qui -exec
peut s'exécuter est limité à un utilitaire externe avec des arguments facultatifs. Pour utiliser directement les fonctions intégrées, les fonctions, les conditions, les pipelines, les redirections, etc. de Shell avec -exec
n'est pas possible, sauf s'il est enveloppé dans quelque chose comme un sh -c
enfant Shell.
Si les fonctionnalités bash
sont requises, utilisez bash -c
au lieu de sh -c
.
sh -c
s'exécute /bin/sh
avec un script donné sur la ligne de commande, suivi d'arguments de ligne de commande facultatifs pour ce script.
Un exemple simple d'utilisation de sh -c
seul, sans find
:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
Cela transmet deux arguments au script enfant Shell. Ceux-ci seront placés dans $0
et $1
pour le script à utiliser.
La chaîne sh
. Ce sera disponible en tant que $0
à l'intérieur du script, et si le shell interne génère un message d'erreur, il le préfixe avec cette chaîne.
L'argument apples
est disponible sous la forme $1
dans le script, et s'il y avait eu plus d'arguments, ceux-ci auraient été disponibles en tant que $2
, $3
etc. Ils seraient également disponibles dans la liste "$@"
(à l'exception de $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 d'accès trouvés par find
.
Exemple: recherchez tous les fichiers normaux qui ont un certain suffixe de nom de fichier et remplacez ce suffixe de nom de fichier par 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" {} ';'
A l'intérieur du script interne, $1
serait la chaîne text
, $2
serait la chaîne txt
et $3
serait le nom de chemin que find
a trouvé pour nous. L'expansion des paramètres ${3%.$1}
prendrait le chemin d'accès et supprimerait le suffixe .text
à partir de cela.
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 les mêmes noms dans le script externe.
Ce qui précède est la bonne façon d'appeler 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 fractionne les noms de fichiers sur les espaces, invoque la globalisation des noms de fichiers et force également le shell à développer le résultat complet de find
avant même d'exécuter la première itération de la boucle.
Voir également:
-exec ... {} +
Le ;
à la fin peut être remplacé par +
. Cela provoque find
pour exécuter la commande donnée avec autant d'arguments (chemins d'accès trouvés) que possible plutôt qu'une fois pour chaque chemin d'accès trouvé. La chaîne {}
doit se produire juste avant le +
pour que cela fonctionne .
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} +
Ici, find
collectera les chemins d'accès résultants et exécutera cat
sur autant d'entre 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é aussi peu de fois que possible. Ce dernier exemple nécessite GNU mv
de coreutils (qui prend en charge le -t
option).
En utilisant -exec sh -c ... {} +
est également un moyen efficace de parcourir un ensemble de noms de chemin avec un script arbitrairement complexe.
Les bases sont les mêmes que lorsque vous utilisez -exec sh -c ... {} ';'
, mais le script prend maintenant une liste d'arguments beaucoup plus longue. Ceux-ci peuvent être bouclés en bouclant sur "$@"
à l'intérieur du script.
Notre exemple de la dernière section qui modifie les suffixes des noms de fichiers:
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" {} +
-execdir
Il y a aussi -execdir
(implémenté par la plupart des variantes de find
, mais pas une option standard).
Cela fonctionne comme -exec
avec 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 ./
, tandis que BSD find
ne le fera pas).
Exemple:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Cela déplacera chacun des *.txt
- fichier dans un fichier done-texts
sous-répertoire dans le même répertoire que celui où le fichier a été trouvé . Le fichier sera également renommé en ajoutant le suffixe .done
à elle.
Ce serait un peu plus délicat à faire avec -exec
car il nous faudrait extraire le nom de base du fichier trouvé de {}
pour former le nouveau nom du fichier. Nous avons également besoin du nom du 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 -exec
au lieu de -execdir
devrait employer un enfant Shell:
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 {} +