Traitement des flux et des chaînes
De la chaîne au flux
Les commandes dans un environnement UNIX ont toutes un fonctionnement de base similaire : elles prennent un flux de données en entrée sur stdin, émettent un flux de données en sortie sur stdout et d’éventuelles erreurs sur stderr.
1. Utilisation d’une variable dans un flux
Beaucoup de commandes acceptent en argument un nom de fichier, qu’elles vont lire à la place de stdin, certaines permettent également de donner un nom de fichier de sortie, où elles écriront à la place de stdout. Cependant, la puissance des scripts provient bel et bien du chaînage que l’on peut effectuer d’une commande à l’autre, notamment grâce au pipe (ou tube, écrit « | »), tel qu’abordé au chapitre Les bases de l’écriture d’un script - Enchaînements et redirections.
Différentes variables sont initialisées lors du lancement du script (variables d’environnement, arguments donnés au script, etc.), d’autres peuvent être initialisées en cours d’exécution du script (avec la commande read par exemple). Dans tous ces cas, si on veut traiter le contenu de ces variables, il faut le transformer en flux, car peu de commandes acceptent de recevoir ces données sous forme d’arguments et peu de commandes sont étudiées pour directement lire une variable.
C’est pourquoi on utilise la commande echo (dont le but est de retourner le contenu de ses arguments sur la sortie standard) suivi d’un pipe dès que l’on veut traiter le contenu d’une variable :
echo "$var" | <commande>
Ensuite, si l’on veut récupérer le résultat de la commande (ou de l’enchaînement de commandes) dans une variable, on utilise la syntaxe d’assignation à une variable vue au chapitre Les bases de l’écriture d’un script - Enchaînements et redirections, avec la syntaxe $(...). On obtient alors la ligne suivante :
nouvelle_var="$(echo "$var" | <commande>)"
Cela fonctionne bien sûr avec un enchaînement de commandes :
nouvelle_var="$(echo "$var" | <commande> | <autre_commande>)"
2. Limite des arguments
Cette approche connaît...
Découpage
Il est souvent nécessaire d’extraire des données de certains fichiers, ces données étant généralement formatées d’une manière claire et uniforme. On souhaite alors découper les lignes selon certains séparateurs. Dans le même esprit, on peut également souhaiter découper un fichier en plusieurs fichiers plus petits.
La plupart des commandes sont étudiées pour traiter les flux de données ligne par ligne. De manière générale, que ce soit à partir d’une variable ou à partir d’un fichier, le retour à la ligne est un séparateur d’enregistrements : les commandes qui vont être abordées traitent pour la plupart les données reçues sur stdin ligne par ligne.
1. Couper les lignes : cut
La commande cut permet de découper une ligne selon un séparateur et d’en extraire un ou plusieurs champs ; quand elle reçoit plusieurs lignes en entrée, elle répète cette opération sur chacune des lignes. Elle peut également extraire une suite de caractères identifiée par les positions du caractère de début et du caractère de fin.
On doit lui fournir au moins une option, lui précisant quelle suite de caractères (ou quel champ) extraire. On peut lui donner en argument le nom du fichier à lire ; sans nom de fichier, elle lit ses données sur son entrée standard stdin.
Son usage le plus courant se fait avec les options -d (pour définir le séparateur entre champs, aussi appelé délimiteur) -f (pour définir le champ à extraire).
La commande suivante permet par exemple d’extraire la liste des répertoires personnels de tous les utilisateurs du système :
cut -d: -f6 /etc/passwd
… on demande ici à la commande cut d’extraire le sixième champ de chaque ligne du fichier /etc/passwd en délimitant ces champs avec le caractère « : ».
Lorsque l’option -d n’est pas utilisée, le délimiteur est égal à la tabulation (code ASCII 9, souvent notée « \t » sous UNIX) ; ce séparateur n’est en pratique plus réellement...
Filtrage
Une autre opération très souvent nécessaire est le filtrage des lignes, afin de ne conserver que les lignes intéressantes. Dans ce cadre, plusieurs approches peuvent convenir : filtrage selon le numéro des lignes à conserver, conservation du début ou de la fin du fichier, conservation de lignes selon leur contenu... En réalité, plusieurs outils peuvent répondre à chacun des besoins, cette section aborde les commandes simples, les outils plus évolués sont abordés dans la section Outils évolués.
1. Conserver les premières lignes : head
Lorsque seules les premières lignes d’un fichier sont nécessaires, la commande head (« tête ») est appropriée. Celle-ci retourne sur sa sortie standard stdout les premières lignes du fichier qui lui est donné en argument, ou de son entrée standard stdin si aucun nom de fichier n’est donné en argument ou si l’argument est égal à « - ». Par défaut, cette commande extrait les 10 premières lignes.
Parmi les options de cette commande, les deux suivantes sont les plus significatives :
Option |
Objectif |
-c <nombre> ou --bytes=<nombre> |
Retourner cette quantité de premiers octets. |
-n <nombre> ou --lignes=<nombre> |
Retourner cette quantité de premières lignes. |
Le nombre peut être fourni avec un suffixe : K pour 1024, kB pour 1000, M pour 1024×1024, MB pour 1000×1000, etc. La liste complète des suffixes peut être trouvée dans la page de manuel de cette commande.
De plus, lorsque ce nombre est négatif (préfixé par « - »), il signifie « toutes les lignes sauf les X dernières » au lieu de « les X premières lignes ».
Par exemple :
-
La commande suivante retourne les 20 premières lignes du fichier fichier.txt :
head -n 20 fichier.txt
-
La commande suivante retourne le contenu du fichier autre.txt, sans ses 38 dernières lignes :
head -n -38 autre.txt
2. Conserver les dernières lignes : tail
De la même manière qu’il est parfois nécessaire d’extraire uniquement les premières lignes, la commande tail permet...
Modification
Au-delà du découpage et du filtrage de données existantes, il y a souvent un besoin de modification des données. Pour cela, plusieurs commandes coexistent, chacune ayant ses avantages et ses inconvénients.
1. Modifier des caractères : tr
La commande tr (pour translate, « traduire ») permet de reproduire les flux reçus sur l’entrée standard stdin, en modifier ou supprimer certains caractères et retourner le résultat sur la sortie standard stdout.
Cette commande agit sur l’ensemble du flux : contrairement à la plupart des commandes abordées, elle n’analyse pas les données ligne par ligne. Elle reste cependant très basique : elle ne remplace pas des suites de caractères mais uniquement des caractères isolés.
Par défaut, elle prend deux arguments : d’une part la liste des caractères à remplacer, d’autre part les caractères de remplacement dans le même ordre.
Son utilisation peut être illustrée par l’exemple suivant :
$ echo "Hallo!" | tr 'a!Hv' 'ewyk'
yellow
… ici, le caractère « a » est remplacé par « e », « ! » est remplacé par « w » et « H » est remplacé par « y ». Le caractère « v » serait remplacé par « k », mais il n’y en a aucun dans le flux d’entrée.
Cette commande peut par exemple être utilisée pour passer une chaîne en majuscules ou en minuscules :
$ echo "Bonjour Le Monde !" | tr 'a-z' 'A-Z'
BONJOUR LE MONDE !
$ echo "Bonjour Le Monde !" | tr 'A-Z' 'a-z'
bonjour le monde !
En plus du remplacement de caractères, tr est capable d’en supprimer, avec l’option -d :
$ echo "Bonjour Le Monde !" | tr -d '! '
BonjourLeMonde
… ici, le point d’exclamation et les espaces sont supprimés.
Par ailleurs, on peut inverser la compréhension du premier argument (utiliser son complément), afin de ne garder que certains caractères :
$ echo...
Outils évolués
Au-delà des outils simples qui répondent au principe KISS évoqué en introduction, plusieurs outils plus évolués permettent d’effectuer des modifications de manière parfois bien plus puissante. Il s’agit notamment de :
-
sed (Stream Editor, éditeur de flux) qui permet de transformer des flux de données selon des règles pré-établies, même sur des flux ou fichiers de très grande taille.
-
awk (ce nom est tiré des initiales de ses concepteurs), langage de traitement de lignes, offrant des possibilités plus poussée, grâce à l’utilisation de variables par exemple.
-
ed (Editor), l’ancêtre de vi, qui a été le premier logiciel à inclure les expressions rationnelles.
1. sed, l’éditeur de flux
sed, dont le jeu de commandes est inspiré de celui d’ed (il partage donc de nombreux aspects avec vi), prend en argument une liste de commandes qui sont appliquées à chacune des lignes rencontrées dans le flux d’entrée. Le second argument est le nom de fichier à modifier : si aucun nom n’est précisé ou si le nom « - » est donné, alors la commande travaille sur ce qu’elle reçoit sur son entrée standard stdin. Par défaut, les données modifiées sont retournées sur la sortie standard stdout, on peut alors utiliser l’opérateur de redirection « > » pour stocker ces données dans un fichier.
Étant donné que cet outil travaille ligne par ligne, il ne charge qu’une seule ligne en mémoire à la fois. Par conséquent, il est très performant sur de très grands fichiers car il n’a pas besoin de les charger en mémoire, contrairement aux éditeurs de texte habituels.
On pourra par exemple rencontrer la commande suivante, pour corriger la faute de frappe « dangeruex » en « dangereux » :
sed 's/dangeruex/dangereux/g' texte.txt > texte_corrige.txt
On retrouve ici la commande de remplacement qui existe également dans vi, lui-même basé sur ed (cf. chapitre Éditeurs de texte - Vim : performant, léger et universel...