16.2. Commandes complexes

Commandes pour utilisateurs plus expérimentés

find

-exec COMMANDE \;

Exécute 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 littérale à find, sans l'interpréter comme un caractère spécial).

bash$ find ~/ -name '*.txt'
/home/bozo/.kde/share/apps/karm/karmdata.txt
/home/bozo/misc/irmeyc.txt
/home/bozo/test-scripts/1.txt
              

Si COMMAND contient {}, alors find substitue le chemin complet du fichier sélectionné à « {} ».

find ~/ -name 'core*' -exec rm {} \;
# Supprime tous les fichiers core à partir du répertoire de l'utilisateur.
find /home/bozo/projects -mtime -1
#                                               ^   Attention au signe
#+ "moins"
#  Liste tous les fichiers situés dans le répertoire /home/bozo/projects
#+ qui ont été modifiés durant les dernières 24 heures
#+ (jour_d_aujourdhui - 1).
#
find /home/bozo/projects -mtime 1
#  Comme ci-dessus, but mais modifés il y a *exactement* 24 heures.
#
#  mtime = date de dernière modification du fichier cible
#  ctime = date de dernier changement d'état (via 'chmod' ou autre)
#  atime = date du dernier accès

REP=/home/bozo/fichiers_bidons
find "$REP" -type f -atime +5 -exec rm {} \;
#                          ^           ^^
#  Les accolades sont un indicateur pour le chemin trouvé par "find."
#
#  Efface tous les fichiers contenus dans "/home/bozo/fichiers_bidons"
#+ qui n'ont pas été accédés depuis *au moins* 5 jours (signe "plus"... +5).
#
#  "-type typefichier", où
#  f = fichier classique
#  d = répertoire
#  l = lien symbolique, etc.
# 
#  (La page de manuel et la page info de 'find' ont 
#  (La liste complète des options se trouve dans la page de manuel et
#  dans la page info de 'find'.)
find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;

#  Trouve toutes les adresses IP (xxx.xxx.xxx.xxx) contenues dans les fichiers
#+ situés dans le répertoire /etc .
#  Quelques correspondances n'auront rien à voir - 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.

# Merci, Stéphane Chazelas.
[Note]

Note

L'option -exec de find ne doit pas être confondue avec la commande intégrée du shell exec.

Exemple 16.3. Badname élimine dans le répertoire courant les fichiers dont le nom contient des caractères incorrects et des espaces blancs.

#!/bin/bash
# badname.sh

# Efface les fichiers du répertoire courant contenant des mauvais caractères.

for nomfichier in *
do
  mauvaisnom=`echo "$nomfichier" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
# mauvaisnom=`echo "$nomfichier" | sed -n '/[+{;"\=?~()&lt;&gt;&*|$]/p'`  fonctionne aussi.
#  Supprime les fichiers contenant les "mauvais" caractères :
#+   + { ; " \ = ? ~ ( ) < > & * | $
#
  rm $mauvaisnom 2>/dev/null
#                ^^^^^^^^^^^ Supression des messages d'erreur.
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, c'est-à-dire comme une fin
#+ de commande.

exit 0

#---------------------------------------------------------------------
# Les commandes ci-dessous ne seront pas exécutées à cause de la commande
# _exit_ au dessus.

# Voici une alternative au script ci-dessus:
find . -name '*[+{;"\\=?~()&lt;&gt;&*|$ ]*' -maxdepth 0 \
-exec rm -f '{}' \;
#  L'option "-maxdepth 0" nous assure que _find_ ne cherchera pas dans les
#+ sous-répertoires de $PWD.

# (Merci, S.C.)

Exemple 16.4. Effacer 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 contenant 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 fichier \"$1\".";;
esac

find . -inum $inum -exec rm {} \;
#                           ^^
#        Les accolades sont des emplacements réservés
#+       pour la sortie de texte par "find".
echo "Fichier "\"$1"\" effacé !"

exit 0

La commande find fonctionne aussi sans l'option -exec.

#!/bin/bash
#  Trouve les fichiers suid root.
#  Un fichier suid étrange pourrait indiquer une faille de sécurité
#+ voire même une intrusion dans le système.

repertoire="/usr/sbin"
# Essayer aussi /sbin, /bin, /usr/bin, /usr/local/bin, etc.
droits="+4000"  # suid root (dangerous!)


for fichier in $( find "$repertoire" -perm "$droits" )
do
  ls -ltF --author "$fichier"
done

Voir l'Exemple 16.30, « Utiliser cpio pour déplacer un répertoire complet », l'Exemple 3.4, « Sauvegarde de tous les fichiers modifiés depuis 24 heures » et l'Exemple 11.9, « Rechercher les auteurs de tous les binaires d'un répertoire » pour des exemples de scripts utilisant find. La page de manuel de cette commande, complexe et puissante, apporte des détails supplémentaires.

xargs

C'est un filtre pour passer les arguments à une commande, et aussi un outil pour assembler les commandes entre elles. xargs découpe un flux de données en morceaux suffisamment petits pour être traités par les filtres ou les commandes. On peut le considérer comme une alternative puissante aux apostrophes inverses. Dans les situations où la substitution de commande échoue avec l'erreur trop d'arguments, remplacer par xargs a de bonnes chances de régler le problème. [71] Habituellement, xargs lit depuis stdin ou depuis un tube mais il accepte aussi de lire dans la sortie d'un fichier.

La commande par défaut d'xargs est echo. Cela signifie que tout flux entrant transmis via un tube vers xargs peut voir ses sauts de ligne et caractères d'espacements supprimés.

bash$ ls -l
total 0
 -rw-rw-r--    1 bozo  bozo         0 Jan 29 23:58 fichier1
 -rw-rw-r--    1 bozo  bozo         0 Jan 29 23:58 fichier2


bash$ ls -l | xargs
total 0 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 fichier1 -rw-rw-r-- 1 bozo bozo 0 Jan...



bash$ find ~/mail -type f | xargs grep "Linux"
./misc:User-Agent: slrn/0.9.8.1 (Linux)
 ./sent-mail-jul-2005: hosted by the Linux Documentation Project.
 ./sent-mail-jul-2005: (Linux Documentation Project Site, rtf version)
 ./sent-mail-jul-2005: Subject: Criticism of Bozo's Windows/Linux article
 ./sent-mail-jul-2005: while mentioning that the Linux ext2/ext3 filesystem
 . . .
              

ls | xargs -p -l gzip : Compresse avec gzip tous les fichiers du répertoire courant, un à un, et demande confirmation avant chaque opération.

[Note]

Note

Notez que xargs traite les arguments qui lui sont passés séquentiellement, un à la fois.

bash$ find /usr/bin | xargs file
/usr/bin:          directory
 /usr/bin/foomatic-ppd-options:          perl script text executable
 . . .
              

[Astuce]

Astuce

Une option intéressante d'xargs est -n NN, qui limite à NN le nombre d'arguments passés.

ls | xargs -n 8 echo : Affiche le contenu du répertoire courant sur 8 colonnes.

[Astuce]

Astuce

Une autre option utile est -0, combinée avec find -print0 ou grep -lZ. Ceci permet de manipuler les arguments contenant des espaces ou des quotes.

find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f

grep -rliwZ GUI / | xargs -0 rm -f

N'importe laquelle des commande ci-dessus effacera tout fichier contenant « GUI ». (Merci, S.C.)

Or:

cat /proc/"$pid"/"$OPTION" | xargs -0 echo
#  Formate la sortie :     ^^^^^^^^^^^^^^^
#  À partir de la correction de Han Holl sur le script "get-commandline.sh"
#+ du chapitre "/dev et /proc".
[Astuce]

Astuce

L'option -P de xargs permet de faire tourner des processus en parallèle. C'est un moyen d'accélérer l'exécution sur une machine munie d'un processeur multic½ur.

#!/bin/bash

ls *gif | xargs -t -n1 -P2 gif2png
# Convertit toutes les images GIF du répertoire courant au format PNG.

# Options:
# =======
# -t    Affiche la commande dans stderr.
# -n1   Au plus 1 argument par ligne de commande.
# -P2   Fait tourner au plus 2 processus en même temps.

# Merci à Roberto Polli pour l'inspiration.

Exemple 16.5. Fichier de traces utilisant xargs pour surveiller les journaux système

#!/bin/bash

# Génère un journal de traces dans le répertoire courant à partir de la fin de
# /var/log/messages.

# Note : /var/log/messages doit être lisible par tout le monde si le script
# est appelé par un utilisateur simple.
#         #root chmod 644 /var/log/messages

LIGNES=5

( date; uname -a ) >>fichiertraces
# Date, heure et nom de l'ordinateur
echo --------------------------------------------------------------------- >>fichiertraces
tail -n LIGNES /var/log/messages | xargs |  fmt -s >>fichiertraces
echo >>fichiertraces
echo >>fichiertraces

exit 0

#  Note:
#  ----
#  Frank Wang précise que les guillemets qui ne se suivent pas (soit des
#+ simples soit des doubles) dans le fichier source pourraient donner
#+ une indigestion à xargs.
#
#  Il suggère d'utiliser la substitution suivante pour la ligne 15 :
#     tail -n $LIGNES /var/log/messages | tr -d "\"'" | xargs | fmt -s >>logfile



#  Exercice:
#  --------
#  Modifier ce script pour tracer les modifications dans /var/log/messages
#+ à des intervalles de 20 minutes.
#  Astuce : utilisez la commande "watch".

Comme avec find, une paire d'accolades sert à indiquer un texte à remplacer.

Exemple 16.6. Copier les fichiers du répertoire courant vers un autre répertoire en utilisant xargs

#!/bin/bash
# copydir.sh

#  Copie verbeusement tous les fichiers du répertoire courant ($PWD)
#+ dans le répertoire spécifié sur la ligne de commande

E_NOARGS=65

if [ -z "$1" ]   # Quitte si aucun paramètre n'est fourni.
then
  echo "Usage: `basename $0` rep-destination"
  exit $E_NOARGS
fi  

ls . | xargs -i -t cp ./{} $1
#            ^^ ^^      ^^
#  -t est l'option "verbeuse" (affiche la ligne de commande sur stderr).
#  -i est l'option de "remplacement des chaînes".
#  {} est un emplacement réservé pour le texte en sortie.
#  C'est similaire en utilisation à une paire d'accolades pour "find."
#
#  Liste les fichiers du répertoire courant (ls .),
#+ utilise la sortie de "ls" comme argument pour "xargs" (options -i -t),
#+ puis copie (cp) ces arguments ({}) vers le nouveau répertoire ($1).  
#
#  Le résultat net est l'équivalent exact de
#+   cp * $1
#+ sauf si un des noms de fichiers contient des espaces blancs.

exit 0

Exemple 16.7. Tuer des processus par leur nom

#!/bin/bash
# kill-byname.sh: Killing processes by name.
# Compare this script with kill-process.sh.

#  For instance,
#+ try "./kill-byname.sh xterm" --
#+ and watch all the xterms on your desktop disappear.

#  Warning:
#  -------
#  This is a fairly dangerous script.
#  Running it carelessly (especially as root)
#+ can cause data loss and other undesirable effects.

E_BADARGS=66

if test -z "$1"  # No command-line arg supplied?
then
  echo "Usage: `basename $0` Process(es)_to_kill"
  exit $E_BADARGS
fi


PROCESS_NAME="$1"
ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs -i kill {} 2&>/dev/null
#                                                       ^^      ^^

# ---------------------------------------------------------------
# Notes:
# -i is the "replace strings" option to xargs.
# The curly brackets are the placeholder for the replacement.
# 2&>/dev/null suppresses unwanted error messages.
#
# Can  grep "$PROCESS_NAME" be replaced by pidof "$PROCESS_NAME"?
# ---------------------------------------------------------------

exit $?

#  The "killall" command has the same effect as this script,
#+ but using it is not quite as educational.

Exemple 16.8. Analyse de la fréquence des mots en utilisant xargs

#!/bin/bash
# wf2.sh: Crude word frequency analysis on a text file.

# Uses 'xargs' to decompose lines of text into single words.
# Compare this example to the "wf.sh" script later on.


# Check for input file on command-line.
ARGS=1
E_BADARGS=85
E_NOFILE=86

if [ $# -ne "$ARGS" ]
# Correct number of arguments passed to script?
then
  echo "Usage: `basename $0` filename"
  exit $E_BADARGS
fi

if [ ! -f "$1" ]       # Check if file exists.
then
  echo "File \"$1\" does not exist."
  exit $E_NOFILE
fi



#####################################################
cat "$1" | xargs -n1 | \
#  List the file, one word per line. 
tr A-Z a-z | \
#  Shift characters to lowercase.
sed -e 's/\.//g'  -e 's/\,//g' -e 's/ /\
/g' | \
#  Filter out periods and commas, and
#+ change space between words to linefeed,
sort | uniq -c | sort -nr
#  Finally remove duplicates, prefix occurrence count
#+ and sort numerically.
#####################################################

#  This does the same job as the "wf.sh" example,
#+ but a bit more ponderously, and it runs more slowly (why?).

exit $?

expr

Évaluateur d'expression : Concatène et évalue les arguments suivant l'opération souhaitée (les arguments doivent être séparés par des espaces). Les opérations peuvent être arithmétiques, comparatives, chaînes de caractères ou logiques.

expr 3 + 5

renvoie 8

expr 5 % 3

renvoie 2

expr 1 / 0

renvoie le message d'erreur, expr: division by zero

Opérations arithmétiques illégales non autorisées.

expr 5 \* 3

renvoie 15

L'opérateur de multiplication doit être échappé lorsqu'il est utilisé dans une expression arithmétique avec expr.

y=`expr $y + 1`

Incrémente une variable, de la même manière que let y=y+1 et y=$(($y+1)). Ceci est un exemple d'expansion arithmétique.

z=`expr substr $chaine $position $longueur`

Extrait une sous-chaîne de caractères de $longueur caractères, en partant de $position.

Exemple 16.9. Utiliser expr

#!/bin/bash

# Démonstration des possibilités de 'expr'
# ========================================

echo

# Opérations arithmétiques
# ------------------------

echo "Opérations arithmétique"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"

a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incrémentation d'une variable)"

a=`expr 5 % 3`
# modulo
echo
echo "5 mod 3 = $a"

echo
echo

# Opérations logiques
# -------------------

#  Retourne 1 si vrai, 0 si faux,
#+ à l'opposé des conventions de Bash.

echo "Opérations logiques"
echo

x=24
y=25
b=`expr $x = $y`         # Test d'égalité.
echo "b = $b"            # 0  ( $x -ne $y )
echo

a=3
b=`expr $a \> 10`
echo 'b=`expr $a \> 10`, donc...'
echo "If a > 10, b = 0 (faux)"
echo "b = $b"            # 0  ( 3 ! -gt 10 )
echo

b=`expr $a \< 10`
echo "If a < 10, b = 1 (vrai)"
echo "b = $b"            # 1  ( 3 -lt 10 )
echo
# Notez l'échappement des opérations.

b=`expr $a \<= 3`
echo "If a <= 3, b = 1 (vrai)"
echo "b = $b"            # 1  ( 3 -le 3 )
# Il y a aussi l'opérande "\>=" (plus grand que ou égal à).


echo
echo



# Opérateur de chaine de caractères
# ---------------------------------

echo "Opérateur de chaînes de caractères"
echo

a=1234zipper43231
echo "Voici \"$a\"."

# length : longueur de la chaine
b=`expr length $a`
echo "La taille de \"$a\" est $b."

# index : position du premier caractère dans une sous-chaine
#         qui correspond à un caractère dans la chaine.
b=`expr index $a 23`
echo "La position du premier \"2\" dans \"$a\" est \"$b\"."

#  substr : extrait une sous-chaîne, en spécifiant la position de départ et la
#+          taille
b=`expr substr $a 2 6`
echo "La sous-chaîne de \"$a\", commençant à la position 2,\
et long de 6 caractère est \"$b\"."


#  L'attitude par défaut de l'opérateur 'match' est de
#+ chercher une occurence à partir ***du début*** de la chaîne.
#
#        utilisation des expressions régulières
b=`expr match "$a" '[0-9]*'`               #  Comptage numérique.
echo Le nombre de chiffres au début de \"$a\" est $b.
b=`expr match "$a" '\([0-9]*\)'`           #  Notez que les parenthèses échappées
#                   ==      ==              + déclenchent une reconnaissance de
#                                           + sous-chaîne.
echo "Les chiffres au début de \"$a\" sont \"$b\"."

echo

exit 0

[Important]

Important

L'opérateur : (null) équivaut à match. Par exemple, b=`expr $a : [0-9]*` est l'équivalent exact de b=`expr match $a [0-9]*` du listing précédent.

#!/bin/bash

echo
echo "Opérations avec des chaînes utilisant la construction \"expr \$string : \"
echo "======================================================================="
echo

a=1234zipper5FLIPPER43231

echo "La chaîne en cours est     \"`expr "$a" : '\(.*\)'`\"."
# Parenthèses échappées groupant l'opérateur.    ==  ==

#       *********************************
#+          Les parenthèses échappées
#+       correspondent à une sous-chaîne.
#       *********************************


#  Si les parenthèses ne sont pas échappées...
#+ alors 'expr' convertit la chaîne en un entier.

echo "La taille de \"$a\" est `expr "$a" : '.*'`."   # Taille de la chaîne

echo "Le nombre de chiffre au début de \"$a\" est `expr "$a" : '[0-9]*'`."

# ------------------------------------------------------------------------- #

echo

echo "Les chiffres au début de \"$a\" sont `expr "$a" : '\([0-9]*\)'`."
#                                                             ==      ==
echo "Les sept premières lettres de \"$a\" sont `expr "$a" : '\(.......\)'`."
#           =========                                      ==       ==
# Encore, les parenthèses échappées forcent une correspondance de sous-chaîne
#
echo "Les sept dernières lettres de \"$a\" sont `expr "$a" : '.*\(.......\)'`."
#           =========           fin de l'opérateur chaîne  ^^
#  (en fait, ça signifie qu'il saute un ou plus jusqu'à une sous-chaîne
#+  spécifiée)

echo

exit 0

Le script ci-dessus illustre comment expr utilise les opérateurs groupant appelés parenthèses echappées -- \( ... \) -- en tandem avec une analyse basée sur les expressions rationnelles pour faire coïncider une sous-chaîne de caractères. Voici un autre exemple, cette fois provenant de la « vie réelle. »

# Supprimer les espaces en début et en fin.
LRFDATE=`expr "$LRFDATE" : '[[:space:]]*\(.*\)[[:space:]]*$'`

#  Provient du script "booklistgen.sh" de Peter Knowles,
#+ script convertissant des fichiers au format Librie/PRS-50X de Sony.
#  (http://booklistgensh.peterknowles.com)

Perl, sed et awk ont des capacités d'analyse de chaînes de caractères très largement supérieures. Une petite sous-routine sed ou awk dans un script (voir la Section 36.2, « Scripts d'appel ») est aussi une bonne alternative à l'utilisation d'expr.

Voir la Section 10.1, « Manipulation de chaînes de caractères » pour en savoir plus sur l'utilisation d'expr dans les manipulations des chaînes de caractères.



[71] Et même quand xargs n'est pas strictement nécessaire, il peut accélérer l'exécution d'une commande impliquant le traitement en flot de plusieurs fichiers.