La commande find
Parmi
toutes les commandes Unix, find est sans aucun doute l'une des
plus puissantes, des moins comprises et des plus effrayantes.
Pourtant, une fois maitrisée, elle se révèle
extrèmement utile et c'est pourquoi nous allons nous pencher
sur elle présentement. Le but de cet article n'est pas de
paraphaser le man, mais d'illustrer a travers quelques exemples le
fonctionnement et les possibilités de find.
Le find
dont vous disposez sous Linux est celui de GNU qui diffère des
autres versions sur plusieurs points. Nous allons parler ici de find
en général et les exemples marchent sur tous les find
(en principe). Parfois, certaines options seront inutiles avec le
find de GNU, mais autant les apprendre, ca vous servira le
jour ou vous vous retrouverez avec une autre version (genre Unix
propriétaire, pourquoi pas?)
Ce que fait find, c'est
en réalite de parcourir une arborescence et appliquer un
traitement quelconque a certains fichiers. Il peut donc aussi bien
imprimer votre arborescence que rechercher un fichier particulier ou
compiler tous les *.c sur votre disque qui appartiennent a
l'utilisateur pixi, dont la taille fait 342K et dont le chemin
d'accès ne contient pas la lettre p.
find
/ -name "*.c" -user pixi -size 342k -regex "[^p]+"
-exec gcc -c {} \;
Les options de la ligne de
commande de find forment un véritable langage de
programmation qui permet de contrôler tout ceci. Elles
peuvent être classées dans trois ensembles:
Les Actions: ces options déclanchent une opération qui s'applique sur le nom de fichier que find est actuellement en train de lire.
Les Conditions(Tests): elles définissent des clauses logiques sur les noms des fichiers. Lorsque ces conditions sont évaluées a vrai, les actions sont appliquees.
Les Options: elles modifient le fonctionnement de find.
Le man de find contient une liste exhaustive de toutes les commandes, je vous invite donc a le lire. On en verra également quelques unes dans nos exemples.
Quelques Conditions :
|
Tests |
Explication |
|
-atime n |
Recherche les fichiers accédés depuis les n derniers jours. |
|
-ctime n |
Recherche les fichiers dont l'état (permissions, nom, groupe, etc.) a été modifié depuis les n derniers jours. |
|
-mtime n |
Recherche les fichiers modifiés depuis les n derniers jours. |
|
-name n |
Recherche les fichiers portant le nom n. |
|
-size n |
Recherche les fichiers dont la taille est de n. La valeur n est normalement exprimée en terme de blocs de 512 octets. Si n est suivi de c, la valeur est exprimée en terme d'octets. |
|
-user u |
Recherche tous les fichiers appartenant à l'utilisateur u. |
|
-empty |
Recherche tous les fichiers vides (de longueur zéro) et tous les répertoires vides (qui ne contiennent aucun fichier). |
Les valeurs de -atime, -ctime, -mtime et -size peuvent être signées.
Par exemple, la commande suivante recherche tous les fichiers modifiés depuis moins de 2 jours :
pixi@etnotropie:~/find . -mtime -2 -print
Les critères peuvent être combinés par l'un ou l'autre des opérateurs -a (ET) et -o (OU inclusif).
L'exemple suivant cherche tous les fichiers dont la taille est supérieure à 100000 octets modifiés depuis moins de 30 jours :
pixi@etnotropie:~/find . -size +100000c -a -mtime -30 -print
Quelques Actions :
|
Action |
Explication |
|
-exec c ; |
Exécute la commande c en substituant le nom du fichier en traitement à {} dans l'énoncé de la commande. |
|
-fprint f |
Enregistre le nom du fichier avec chemin complet dans le fichier f. |
|
-ls |
Affiche le nom du fichier sous la forme de ls -dils. |
|
|
Affiche les noms des fichiers. |
Par exemple, l'action -ls donne plus d'informations sur le fichier que l'action -print. Dans l'exemple qui suit, find cherche tous les fichiers vides (-empty) et les enumère (-ls):
pixi@etnotropie:~/find . -empty -ls
800619 29 -rw------- 1 lavc01 etudiant 0 Dec 1 1998 ./projet/journal
975298 26 -rw------- 1 lavc01 etudiant 0 Mar 26 1999 ./w3/cgi-bin/reponses
Commencons
par le plus simple. Lorsqu'on appelle find, le premier
paramètre doit être un nom de répertoire, les
paramètres suivants sont les commandes sus-citées. Par
exemple, la commande -print a pour effet d'afficher le nom du
fichier en cours de traitement. Tapez donc :
find .
-print
Le répertoire . est le répertoire
courant, cette commande a donc pour effet de lister toute
l'arborescence depuis le point ou vous vous trouvez. Essayez
également :
find / -print
et tout
le contenu de vos périphériques montés défile
devant vos yeux!
Maintenant,
prenons un exemple pratique. Supposons que vous voulez chercher le
fichier XF86Config. Nous allons utiliser pour cela la
condition -name suivie d'une expression réguliere qui
ordonne a find de ne prendre en compte que les fichiers dont
le nom est reconnu par cette expression. Dans notre cas, cela
donne:
find / -name "XF86Config"
-print
Observez les doubles côtes autour de
l'expression réguliere : elles sont la pour empécher le
shell d'évaluer cette expression (il faut toujours se souvenir
que sous Unix, les expressions régulieres sont parsées
par le shell).
Avec cet ordre, find cherchera le fichier
dans toute l'arborescence depuis la racine. Il est donc utile de
connaitre l'option -xdev qui empeche find de chercher
ailleurs que dans la partition contenant le repertoire de depart.
Il est possible de spécifier au critère -name des méta-caractères, mais il faut les mettre entre apostrophes pour éviter que le shell lui-même les interprète avant qu'ils ne parviennent à la commande find  :
pixi@etnotropie:~/find . -name '*.Z' -print
./logiciels/emacs-19.28.tar.Z
./logiciels/gcc-2.8.1.tar.Z
-exec COMMANDE ;
Toutefois,
il y a une commande particulière qui donne toute sa force à
find et dont nous n'avons pas encore parler : il s'agit
de -exec. Cette commande permet de lancer pour chaque fichier
un autre programme. C'est très puissant, mais il faut bien
comprendre comment ça marche.
Elle effectue COMMANDE sur chaque fichier trouvé par find. La séquence de commandes se termine par un \; (le << ; >> est échappé pour être certain que le shell le passe de façon litéralle à find).
Si
COMMANDE
contient {}, alors find substitue le chemin complet du fichier
en cours à << {} >>.
Lorsque find
rencontre un -exec, il considère tout ce qui suit comme
une ligne de commandes, jusqu'au caractère ;.
Exemple 1: Affiche « j'ai trouvé » quand il rencontre l'expression régulière entre cotes.
root@etnotropie:~# find / -name ".*rc" -exec echo "J'ai trouvé" \;
Deux
remarques a propos de cette commande: premièrement, j'attire
votre attention sur le \ qui précède le ;. En effet, le
shell considère le point-virgule comme un séparateur de
commandes et sans le \, il tenterait d'executer find / -name
".*rc" -exec echo "J'ai trouvé" (donc
sans le ; fermant -exec) suivi d'une commande vide (celle qui suit le
;).
Deuxiemement, cette commande ne fait qu'afficher "J'ai
trouvé" un certain nombre de fois: en effet, a chaque
fois qu'il rencontre un fichier .*rc, find appelle la commande
echo "J'ai trouvé".
Pour que tout ceci ait
un intérêt, il faut pouvoir transmettre a la commande
lancée par -exec
le nom du fichier en cours d'evaluation. find le fait en
remplacant dans la commande appelée la chaine de caracteres {}
par le nom du fichier.
Exemple 2: Affiche aussi le fichier concerné.
root@etnotropie:~# find / -name ".*rc" -exec echo "J'ai trouvé" {} \;
J'ai trouvé /etc/skel/.acrorc
J'ai trouvé /etc/skel/.bashrc
J'ai trouvé /etc/skel/.kderc
J'ai trouvé /etc/skel/.larswmrc
J'ai trouvé /etc/skel/.nessusrc
J'ai trouvé /etc/skel/.xfdeskmenurc
J'ai trouvé /etc/skel/.xfwm4rc
J'ai trouvé /home/pixi/.kderc
J'ai trouvé /home/pixi/.wmrc
J'ai trouvé /home/pixi/.nessusrc
J'ai trouvé /home/pixi/.bashrc
J'ai trouvé /home/pixi/.acrorc
J'ai trouvé /home/pixi/.dmrc
J'ai trouvé /home/pixi/.fbrc
J'ai trouvé /home/pixi/.sversionrc
J'ai trouvé /home/pixi/.glamerc
Exemple
3:
Find éxecute -print comme la prochaine commande à
appliquer au fichier « .*rc » après un
saut de ligne. Elle applique par défault l'opérateur
booléen -a et le fichier {}.
root@etnotropie:~#
find / -name ".*rc" -exec echo "J'ai trouvé"
\; -print
J'ai trouvé
/etc/skel/.acrorc
J'ai trouvé
/etc/skel/.bashrc
J'ai trouvé
/etc/skel/.kderc
J'ai trouvé
/etc/skel/.larswmrc
J'ai trouvé
/etc/skel/.nessusrc
J'ai trouvé
/etc/skel/.xfdeskmenurc
J'ai trouvé
/etc/skel/.xfwm4rc
J'ai trouvé
/home/pixi/.kderc
Exemple 4: Recherche un répertoire et change le group et le user.
pixi@etnotropie:~/Cours$ ll
total 32
drwxr-xr-x 6 root root 4096 2004-11-18 17:47 BALMAS
pixi@etnotropie:~/find ~pixi -type d -name BALMAS -exec chown pixi:pixi {} \;
drwxr-xr-x 6 pixi pixi 4096 2004-11-18 17:47 BALMAS
L'exemple qui suit combine des critères et utilise l'action -exec avec la commande rm. Elle efface tous les fichiers a.out et tous ceux portant l'extension .o qui n'ont pas été accédés depuis plus de 7 jours .
Exemple 5:
find . (-name a.out -o -name ´*.o´) -atime +7 -exec rm {} \;
Exemple
6: Copier
tous les fichiers de votre disque commencant par a sous /tmp.
find
/ -name "a*" -exec cp -f -r {} /tmp \; -print
Exemple 7: Efface tous les fichiers de sauvegarde (ceux qui se terminent par ~ ou %)
find / -name "*[~%]" -exec rm -f {} \;
Exemple 8: Transformer plein d'images JPEG en un seul fichier PostScript
find *.jpeg -exec jpeg2ps {} \> {}.eps \;
Voici un exemple dans lequel find trouve un fichier et l'éxecute en paramètre de deux commandes. Le fichier est afficher avant d'être renommé.
Exemple 9:
pixi@etnotropie:~$ find CPROGS/X11/ -type f -name toto.c~ -exec ls -l {} \; -a -exec mv {} CPROGS/X11/toto.sav \;
-rw-r--r-- 1 pixi pixi 1296 2004-11-16 23:26 CPROGS/X11/toto.c~
pixi@etnotropie:~$
pixi@etnotropie:~/CPROGS/X11$ ll
-rw-r--r-- 1 pixi pixi 1296 2004-11-16 23:26 toto.c~
pixi@etnotropie:~/CPROGS/X11$ ll
-rw-r--r-- 1 pixi pixi 1296 2004-11-16 23:26 toto.sav
Exemple 10: Trouve toutes les adresses IP (xxx.xxx.xxx.xxx) contenues dans les fichiers situés dans le répertoire /etc.
find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;
Exemple 11: Quelques correspondances n'auront rien à voir, comment peuvent-elles être éliminées ? Peut-être en faisant:
find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \ | grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'
# [:digit:] est un ensemble de caractères POSIX 1003.2
# introduit avec le standard POSIX 1003.2.
L'option -exec de find ne doit pas être confondue avec la commande intégrée du shell exec.
Encore des Exemples : Find est très utile dans les scripts.
Exemple 12: Le script suivant élimine dans le répertoire courant les fichiers dont le nom contient des caractères incorrects et des espaces blancs.
#!/bin/bash
# Efface les fichiers du répertoire courant contenant des mauvais caractères.
for nomfichier in *
do
mauvaisnom=`echo "$nomfichier" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
# Les fichiers contenant ces méchants: + { ; " \ = ? ~ ( ) < > & * | $
rm $mauvaisnom 2>/dev/null # Les erreurs sont effacées.
done
# Maintenant, faire attention aux noms de fichiers contenant des espaces blancs.
find . -name "* *" -exec rm -f {} \;
# Le chemin du fichier trouvé par "find" remplace "{}".
# Le '\' nous assure que le ';' est interprété littéralement, en tant que fin
#+ de commande.
Exit 0
Exemple 13: Et pour vous montrer toute la puissance de find, voici en une ligne une alternative au script ci-dessus:
find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \;
Exemple 14: Ce script efface un fichier par son numéro d'inode.
#!/bin/bash
# idelete.sh: Effacer un fichier grâce à son inode.
# C'est très utile quand un nom de fichier commence avec un caractère illégal,
#+ comme un ? Ou -.
NBARGS=1 # L'argument du nom de fichier doit être passé au script.
E_MAUVAISARGS=70
E_FICHIER_INEXISTANT=71
E_CHANGE_D_ESPRIT=72
if [ $# -ne "$NBARGS" ]
then
echo "Usage: `basename $0` nomfichier"
exit $E_MAUVAISARGS
fi
if [ ! -e "$1" ]
then
echo "Le fichier \""$1"\" n'existe pas."
exit $E_FICHIER_INEXISTANT
fi
inum=`ls -i | grep "$1" | awk '{print $1}'`
# inum = inode (NdT; index node) numéro de fichier
# Chaque fichier possède un inode, qui contient ses informations d'adresses
#+ physiques.
echo; echo -n "Effacer vraiment \"$1\" (o/n)? "
# L'option '-v' de 'rm' pose la même question.
read reponse
case "$reponse" in
[nN]) echo "Vous avez changé d'avis"
exit $E_CHANGE_D_ESPRIT
;;
*) echo "Effacement en cours du \"$1\".";;
esac
find . -inum $inum -exec rm {} \;
echo "Fichier "\"$1"\" effacé!"
exit 0