Les outils GNU

Présentation de ce cours
Rubrique : Utilisation de GNU/Linux
Niveau : **
Audience : Utilisateur de GNU/Linux.
Lectures préalables :
But : Indiquer quel outil GNU peut correspondre à un besoin.

Introduction | (Haut de page) |

Cet article ne donnera pas toutes les options possibles des outils présentés. Il s'agit ici de montrer leurs cas d'utilisation les plus courants pour vos besoins quotidiens. C'est pour cela que l'organisation se fait par besoin et non par programme. Pour obtenir plus d'informations sur toutes les options d'un de ces programmes, consultez sa page de manuel.

Ces programmes peuvent soit travailler sur un fichier passé en paramètre, soit traiter leur entrée standard. On peut ainsi les chaîner les uns aux autres pour réaliser des tâches complexes à l'aide de tubes.

Les logiciels présentés ici existent en d'autres versions sur d'autres Unix. Les versions abordées ici sont celles GNU, mais la plupart des commandes s'utilisent avec les autres.

Rechercher un motif | (Haut de page) |

Pour simplement rechercher une expression, on peut utiliser grep. Cet outil affiche sur sa sortie standard les lignes contenant le motif spécifié. Ce motif est une expression régulière. La version GNU de grep ne fait pas la distinction entre les expressions simples ou étendues comme le font d'autres versions.

Le premier argument est l'expression à rechercher, tous les autres arguments désignent le ou les fichiers dans lesquels effectuer la recherche. Si un seul argument est présent, la recherche se fait sur ce qui provient de l'entrée standard (généralement la sortie d'une commande précédente).

Le premier exemple permet d'afficher la ligne du fichier /etc/passwd correspondant à l'utilisateur Tian. Ce fichier contient les utilisateurs du système ainsi que diverses informations comme le shell à utiliser.

> grep "^Tian:" /etc/passwd
Tian:x:500:100:Tian:/home/Tian:/bin/bash

L'exemple suivant exécute tout d'abord ls -l qui liste les fichiers d'un répertoire en donnant notamment le propriétaire du fichier. Ensuite grep est utilisé comme filtre pour ne garder que les noms des fichiers appartenant à Tian :

> ls -l /tmp | grep Tian
drwx------ 2 Tian users 4096 jan 23 20:55 Trash
-rw-r--r-- 1 Tian users 31237 jan 23 20:54 chvar.1532
-rw-r--r-- 1 Tian users 27483 jan 23 20:51 gfind.found

En fait les fichiers dont le nom contient Tian dans /tmp seront également affichés. Mais c'est un moyen rapide pour faire un premier tri.

Une option utile de grep est -i qui permet de faire une recherche insensible à la casse. ps -e liste tous les programmes en cours d'exécution sur le système. Pour voir tous ceux dont le nom commence par x ou X on fera :

> ps -e | grep -i [[:blank:]]x
474 ? 00:00:00 xfs
1241 ? 00:01:09 X
1493 ? 00:00:02 xconsole

Lorsque plusieurs fichiers sont passés en paramètre, chaque ligne en sortie contiendra au début le nom du fichier. L'option -h permet de désactiver cela. -n est également d'usage courant. Avec cette option, l'affichage contiendra le numéro de la ligne où la correspondance a été trouvée. Pour trouver dans le répertoire courant tous les fichiers contenant la chaîne tmp et savoir à quelle ligne cela se trouve, on utilisera :

> grep -n tmp * 2>/dev/null
debit:3:DATA_FILE=/tmp/debit.data
gfind:3:FOUND=/tmp/gfind.found
migrate:3:TMP_SED=/tmp/tmp_sed_`basename $0`_$$
verifyPicture:5:export TEMPORARY_DIR=/tmp/

Le caractère * sera remplacé par le shell par la liste de tous les fichiers. Le 2>/dev/null à la fin de la ligne permet de ne pas afficher les messages d'erreurs pour si par exemple l'utilisateur n'a pas le droit de lire un fichier ou qu'un sous-répertoire est présent (car le grep essayera de s'exécuter sur celui-ci ce qui est impossible). Pour cela le flux d'erreur standard noté 2 est redirigé vers le périphérique spécial /dev/null qui ignore tout ce qu'on lui passe agissant comme une poubelle.

Modifier une chaîne | (Haut de page) |

Pour effectuer des subtitutions, sed est bien adapté. Il s'agit en fait d'un éditeur fonctionnant en mode flux et qui permet donc de faire beaucoup plus que cela sur ce qu'il reçoit en entrée.

Pour l'utiliser afin de modifier une chaîne, il faut utiliser sa commande s (pour substitute) sous la forme suivante :

> sed s/motif/remplacement/modificateurs

Le motif est une expression régulière. remplacement est une chaîne qui ne sera pas considéré comme une expression régulière donc aucun caractère spécial ne sera pris en compte. En revanche on peut y utiliser les paramètres positionnels (\1 \2 ...) qui correspondent aux différents sous-motifs trouvés dans la recherche.

Pour les modificateurs, il s'agit d'un ensemble de caractères indiquant comment doit être effectué le remplacement. Les plus utiles sont i et g. La lettre i permet de faire une recherche insensible à la casse (la différence majuscule/minuscule n'est pas prise en compte). Pour le g, il indique que le remplacement doit être global. Si plusieurs motifs correspondent dans la même ligne, tous seront remplacés. En l'absence de g, seul le premier le sera.

Traditionnellement, le / est utilisé pour séparer les différents paramètres de la commande de substitution. Mais un autre caractère quelconque peut être utilisé du moment qu'il n'apparaît ni dans le motif ni dans le remplacement.

L'exemple suivant (purement illustratif) remplace tous les o de la chaîne par un a :

> echo "toto" | sed s/o/a/g
tata

Voici comment procéder pour remplacer dans le fichier index.html toutes les occurences du mot tian (en majuscules ou minuscules) par le même mot encadré par <b> et </b> (ce qui permet en HTML de mettre en gras toutes les fois où le mot apparaît). Le résultat sera copié dans index2.html.

> sed "sA\(tian\)A<b>\1</b>Agi" index.html > index2.html

Le séparateur utilisé ici est la lettre A (choisi aléatoirement). Cela a été fait pour simplifier le remplacement qui contient un /

Les doubles guillemets autour de la commande passée à sed permettent que le shell n'interpréte pas < et > comme des redirections.

sed affiche le résultat sur sa sortie standard. Il est ici ensuite mis dans un fichier à l'aide de > index2.html qui redirige l'affichage. On ne peut pas écrire directement dans le même fichier. Il faut donc utiliser un autre fichier avec lequel on peut éventuellement écraser celui d'origine ensuite.

Traiter des colonnes | (Haut de page) |

Le programme cut permet de couper une partie de chaque ligne en entrée. On peut lui indiquer d'extraire par exemple du quatrième au huitième caractère. Mais nous nous intéresserons plus ici à sa possibilité de considérer des colonnes. On lui spécifie un séparateur de champ et les colonnes à afficher. Voici un exemple pour mieux expliquer cela :

> cut -d: -f1,6 /etc/passwd
root:/root
Tian:/home/Tian

L'option -d permet de spécifier le séparateur de champ qui est par défaut la tabulation. Ici on veut utiliser les deux points. Ensuite -f permet d'indiquer une liste de numéros de colonnes séparés par des virgules. On peut aussi indiquer une suite de colonne en séparant les extrémités par un tiret comme ceci :

> cut -d: -f1-6 /etc/passwd
root:x:0:0:root:/root
Tian:x:400:100:Tian:/home/Tian:/bin/bash

Pour de petites tâches comme vu précédemment, cut est idéal. Mais il existe un outil beaucoup plus puissant : gawk (version GNU de awk). Il s'agit d'un interpréteur complet qui permet de faire des programmes dont la syntaxe est proche du C. Détailler tout ce qu'il est possible de faire ne rentre pas dans le cadre de cet article, nous ne verrons donc qu'une petite partie de ses fonctionnalités.

Un programme gawk traite son entrée (passée par un tube ou par un nom de fichier) ligne par ligne. Le séparateur de colonne est défini par l'option -F ou par la variable FS. On définit une action (suite d'instructions) encadrée par des accolades. Chaque action est précédée d'une expression régulière. L'action ne sera exécutée que si l'expression est trouvée dans la ligne. A l'intérieur d'une action, les colonnes sont stockées dans les variables $1, $2, ... La variable $0 contient la ligne entière.

Voyons tout d'abord un exemple sans spécification de motif permettant de réaliser le même résultat que le premier exemple de cut :

> gawk -F: '{print $1 ":" $6}' /etc/passwd
root:/root
Tian:/home/Tian

On spécifie à l'aide de -F que le séparateur de colonnes est le caractère : (deux points). Par défaut, il s'agit de tout caractère d'espacement. Ensuite on a une seule action à être exécutée sur toutes les lignes. La commande print permet de réaliser un affichage. On affiche donc ici la première colonne, une chaîne de caractère fixe ":" (les doubles-guillemets ne sont pas affichés) puis la sixième colonne. Pour utiliser print, on lui spécifie la liste de tout ce qu'il doit afficher en séparant les éléments par des espaces. Ces éléments peuvent être soit des chaînes fixes encadrés par des doubles guillemets, soit des variables.

On peut faire précéder l'action par un motif qui est une expression régulière. Par exemple pour n'afficher ces informations que si l'utilisateur est Tian :

> gawk -F: '/^Tian:/ {print $1 ":" $6}' /etc/passwd
Tian:/home/Tian

Le motif à rechercher est placé entre / / puis entre { } suivent les instructions. Ici il n'y en a qu'une seule. On peut en indiquer plusieurs en les séparant par des ; (points-virgules) :

> gawk -F: '/^Tian:/ {print "Utilisateur : " $1; print "Répertoire personnel : " $6}' /etc/passwd
Utilisateur : Tian
Répertoire personnel : /home/Tian

Dans un script, on placera les différentes instructions sur des lignes différentes pour plus de lisibilité. L'exemple suivant montre cela. Il met également en évidence le fait que l'on peut spécifier plusieurs motifs avec chacun des actions différentes à exécuter :

gawk -F: '
/^Tian:/ {
   print "Utilisateur : " $1;
   print "Identifiant : " $3;
   print "Répertoire personnel : " $6;
}
/^root:/ {
   print "Administrateur";
   print "Répertoire personnel : " $6;
}
' /etc/passwd

Il faut prendre garde à placer l'accolade ouvrante sur la même ligne que le motif auquel correspondent les instructions suivantes.

Le message affiché est ici différent selon le nom de l'utilisateur. Le résultat de l'exécution de ce programme serait :

Administrateur
Répertoire personnel : /root
Utilisateur : Tian
Identifiant : 500
Répertoire personnel : /home/Tian

Il existe deux motifs spéciaux BEGIN et END. Ils correspondent à des actions devant être effectuées avant ou après la lecture des lignes. Comme indiqué précédemment, la variable FS permet aussi de spécifier le séparateur d'enregistrements. Les actions associées à BEGIN permettent d'initialiser cette variable au lieu d'utiliser l'option -F. La variable NR contient le nombre de lignes déjà lues, qu'elles aient correspondu ou non à un des motifs. En affichant sa valeur à la fin, on peut connaître le nombre d'enregistrements total. L'exemple suivant met en pratique ces notions :

gawk '
BEGIN {
   FS=":";
   print "Les utilisateurs :\n";
}
/^Tian:/ {
   print "Utilisateur : " $1;
   print "Identifiant : " $3;
   print "Répertoire personnel : " $6 "\n";
}
/^root:/ {
   print "Administrateur";
   print "Répertoire personnel : " $6 "\n";
}
END {
   print NR " enregistrements traité(s)"
}
' /etc/passwd

Avec pour résultat :

Les utilisateurs :

Administrateur
Répertoire personnel : /root

Utilisateur : tian
Identifiant : 500
Répertoire personnel : /home/tian

2 enregistrements traité(s)

Dans les affichages est utilisé \n qui permet de sauter une ligne. En plus d'initialiser le séparateur de champ, la partie BEGIN réalise un affichage. Cela afin de bien montrer que ces actions ne sont exécutées qu'une seule fois au début.

Parcourir des répertoires | (Haut de page) |

Il existe un outil permettant de lister tous les fichiers et sous-répertoires d'un répertoire et ce récursivement (pour chaque sous-répertoire, son contenu est également listé). Il s'agit de find. Contrairement aux autres programmes présentés jusque là, il ne lit ni sur son entrée standard, ni à partir d'un fichier. C'est donc pour cela qu'on le trouvera généralement en début d'une chaîne avec des tubes, sa sortie étant ensuite filtrée par les autres outils.

La manière la plus simple d'invoquer find est de lui donner un nom de répertoire :

> find /tmp
/tmp
/tmp/Trash
/tmp/Trash/core
/tmp/Trash/a.out
/tmp/chvar.1532
/tmp/chvar.1754
/tmp/gfind.found

Il existe ensuite de nombreuses possibilités de tests pour filtrer les résultats.

On peut demander à ce que ne soient affichés que certains types de résultats avec l'option -type suivie d'un paramètre. Le plus utile est pour ne garder que les fichiers, car on va généralement leur appliquer ensuite un traitement qui n'aurait pas de sens pour un répertoire. -type f (abréviation de file) est destiné à cela.

> find /tmp -type f
/tmp/Trash/core
/tmp/Trash/a.out
/tmp/chvar.1532
/tmp/chvar.1754
/tmp/gfind.found

Par rapport au résultat précédent, le contenu du répertoire /tmp/Trash est toujours affiché mais pas le répertoire seul. Les possiblités les plus courantes avec -type est de lui passer ensuite d pour ne garder que les répertoires et l pour les liens symboliques.

Un des exemples d'utilisation de grep présenté plus haut permettait de n'afficher que les fichiers appartenant à l'utilisateur Tian. Mais cela ne marchait pas forcément tout le temps. Avec find, on peut spécifier l'utilisateur à qui doivent appartenir les fichiers au moyen de l'option -user

> find /tmp -user Tian
/tmp/Trash
/tmp/Trash/core
/tmp/chvar.1532
/tmp/gfind.found

On peut aussi préciser quelle profondeur doit parcourir find. Cela permet par exemple de ne pas afficher le contenu des sous-répertoires, ou de le faire seulement jusqu'à un certain niveau. Avec -maxdepth on peut spécifier la valeur maximale de profondeur que find peut atteindre. Pour n'afficher que le contenu du répertoire courant sans lister ses sous-répertoires, on indiquera une profondeur de 1 :

> find /tmp -maxdepth 1
/tmp
/tmp/Trash
/tmp/chvar.1532
/tmp/chvar.1754
/tmp/gfind.found

Il est aussi possible de filtrer selon le nom du fichier. Avec soit -name qui permet de donner le nom du fichier uniquement, soit avec -regex qui comparera à toute la chaîne affichée (incluant le chemin).

-name fonctionne avec les caractères de substitution du shell. * correspond à une chaîne quelconque et ? à un caractère quelconque.

> find /tmp -name chvar*
/tmp/chvar.1532
/tmp/chvar.1754

Pour -regex, on trouve ensuite une expression régulière. Mais celle-ci sera comparée à toute la chaîne. Cela se passe un peu comme si automatiquement un ^ et un $ étaient ajoutés respectivement en début et en fin de l'expression régulière. L'exemple suivant ne trouvera donc rien :

> find /tmp -regex chvar.*

Il faut ajouter au début de l'expression de quoi englober le chemin complet :

> find /tmp -regex .*chvar.*
/tmp/chvar.1532
/tmp/chvar.1754

Cette expression régulière peut être affinée selon les besoins bien évidemment.

Toutes ces possibilités de filtrage peuvent être combinées en les indiquant les unes à la suite des autres. Il en existe bien plus que celles présentées ici, comme pour ne garder que les fichiers dont le dernier accès remonte à moins qu'une certaine durée (-atime), ceux appartenant à un groupe donné (-group), ceux avec certaines permissions (-perm), ...

Une fonctionnalité utile de find est sa possibilité d'exécuter pour chaque fichier trouvé (en prenant en compte les filtres) une commande. Cela se réalise avec l'option -exec suivie de la commande. Cette dernière doit être terminée par un ; (point-virgule). Et à l'intérieur de cette commande, on peut utiliser {} (deux accolades) qui seront remplacées par chaque élément trouvé par find.

> find /tmp -exec ls -ld {} \;
drwxrwxrwt 26 root root 4096 fév 4 20:30 /tmp
drwx------ 2 Tian users 4096 jan 23 20:55 /tmp/Trash
-rwxr--r-- 1 root root 85418 jan 22 14:38 /tmp/Trash/a.out
-rw-r--r-- 1 Tian users 85418 jan 22 13:27 /tmp/Trash/core
-rw-r--r-- 1 Tian users 31237 jan 23 20:54 /tmp/chvar.1532
-rw-r--r-- 1 root root 31237 jan 23 21:08 /tmp/chvar.1754
-rw-r--r-- 1 Tian users 27483 jan 23 20:51 /tmp/gfind.found

Cet exemple a parcouru tous les fichiers et répertoires et a exécuté sur chacun d'eux la commande ls -ld permettant d'obtenir des informations détaillées (l'option -d de ls permet que les répertoires soient affichés aussi et non pas leur contenu). Le point-virgule terminant l'instruction est précédé par un \ (backslash) pour ne pas que celui-ci soit interprété par le shell.

Passer des arguments | (Haut de page) |

A l'aide de xargs on peut passer des arguments à un programme. Une commande est spécifiée à xargs. Il se charge de l'exécuter avec comme arguments tout ce qu'il a reçu par son entrée standard.

> ls /tmp | xargs echo "Fichiers :"
Fichiers : Trash chvar.1532 chvar.1754 gfind.found

Cet exemple n'a aucun intérêt pratique. Il permet simplement de montrer que xargs passe en une seule fois tout ce qu'il a reçu car avec ls lancé seul, les résultats auraient été affichés sur plusieurs lignes. Mais on peut modifier ce comportement à l'aide de l'option -l suivie par un nombre. Ce dernier correspond au nombre de lignes à attendre avant de lancer la commande.

> ls /tmp | xargs -l1 echo "Fichier :"
Fichier : Trash
Fichier : chvar.1532
Fichier : chvar.1754
Fichier : gfind.found

La valeur était ici de 1. Donc xargs a exécuté une nouvelle fois la commande echo pour chaque ligne en entrée.

Si rien n'est précisé, les paramètres sont ajoutés à la fin de la commande. Mais on peut spécifier une chaîne de caractères qui devra être remplacée par l'entrée de xargs avant exécution. Cela se réalise avec l'option -i suivie par la chaîne en question.

> ls | xargs -l1 -i{} cp {} ~/Trash/

Cela va copier (programme cp) tous les fichiers du répertoire courant dans le répertoire ~/Trash. Les {} seront donc remplacées par chaque nom de fichier. En supposant que cette commande ait été lancée à partir du répertoire /tmp/Trash, tout se passe comme si on avait tapé :

> cp a.out ~/Trash/
> cp core ~/Trash/