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:


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.

-print

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&nbsp :


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