Les commandes sed et awk

linux, commande  |  , ,  |  Grégory Roche  | 
Les commandes sed et awk dans un émulateur de terminal

Cet article propose une découverte des processeurs de fichiers sed et awk.

sed est spécialise dans le traitement de données non organisées; awk dans le traitement des données formatées en lignes et colonnes.

Les fichiers utilisés dans cet article sont disponibles dans l’archive sedawk.tar.bz2.

L’utilisateur familié de ces commandes préférera sans doute la documentation de référence, plus approfondie.

Sommaire

La commande sed

Sed (Stream EDitor, éditeur de flux) effectue des transformations basiques d’un texte.

À ses débuts, l’éditeur ed est doté d’une commande travaillant sur son flux d’entrée standard. Elle permet d’afficher toutes les lignes correspondant à une expression régulière.

Cette commande, dont la syntaxe s’écrit sous la forme g/re/p (global/regular expression/print), donne naissance à l’utilitaire grep.

La commande Sed est créée à partir de l’implémentation suivante de ed.

Sed travaille uniquement sur le flux d’entrée standard tout en tirant ses instructions d’un fichier de scripts.

La version de sed utilisée :

$ sed --version
GNU sed version 4.2.1
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE,
to the extent permitted by law.

GNU sed home page: <http://www.gnu.org/software/sed/>.
General help using GNU software: <http://www.gnu.org/gethelp/>.
E-mail bug reports to: <bug-gnu-utils@gnu.org>.
Be sure to include the word ``sed'' somewhere in the ``Subject:'' field.

La ligne de commande de sed

Le fonctionnement global de sed :

  • un texte est, lu à partir d’un fichier ou d’un tube, ligne après ligne;
  • une ligne sur le flux d’entrée est lue;
  • les diverses commandes traitent cette ligne;
  • optionnellement, le résultat du traitement est envoyé sur la sortie standard.
sed [-n] [-e commande] [-f fichier de commandes] [fichier]

où les options ont la signification suivante :

  • -n : écrit seulement les lignes spécifiées (par l’option /p) sur la sortie standard.
  • -e : permet de specifier les commandes à appliquer sur le fichier. Cette option est utile lorque vous appliquez plusieurs commandes. Afin d’éviter que le shell n’interprète certains caractères, il est préférable d’insérer la commande entre deux caractères simple quote ’, ou entre deux caractères double quotes ".
  • -f : les commandes sont lues à partir d’un fichier.

L’affichage de lignes contenant un motif

L’équivalent de la commande cat :

$ sed '' exemple.txt
La première ligne d'un exemple de texte.
C'est un texte avec des ereurs.
Beaucoups d'ereurs.
Une ligne ne contenant pas d'erreurs.
La dernière ligne de l'exemple de texte.

Pour afficher les lignes contenant un motif sur la sortie standard :

$ sed -n '/ereurs/p' exemple.txt
C'est un texte avec des ereurs.
Beaucoups d'ereurs.

Sans l’option -n, sed affiche chaque ligne lue, ainsi que chaque ligne contenant le motif recherché :

$ sed '/ereurs/p' exemple.txt
La première ligne d'un exemple de texte.
C'est un texte avec des ereurs.
C'est un texte avec des ereurs.
Beaucoups d'ereurs.
Beaucoups d'ereurs.
Une ligne ne contenant pas d'erreurs.
La dernière ligne de l'exemple de texte.

La commande suivante affiche les lignes d’un fichier qui contiennent un espace blanc :

$ sed -n '/[[:space:]]/p' exemple.txt
La première ligne d'un exemple de texte.
C'est un texte avec des ereurs.
Beaucoups d'ereurs.
Une ligne ne contenant pas d'erreurs.
La dernière ligne de l'exemple de texte.

Rechercher les lignes commençant par un premier motif et se terminant par un second motif :

$ sed -n '/^La.*texte.$/p' exemple.txt
La première ligne d'un exemple de texte.
La dernière ligne de l'exemple de texte.

On notera que le dernier point de l’expression régulière ^La.*texte.$ est nécessaire, puisqu’elle correspond à tout caractère, y compris le caractère fin de ligne.

La suppression

Supprimer les lignes contenant un motif :

$ sed '/ereurs/d' exemple.txt
La première ligne d'un exemple de texte.
Une ligne ne contenant pas d'erreurs.
La dernière ligne de l'exemple de texte.

Supprimer toutes les lignes vides d’un fichier :

$ sed /^$/d exemple.txt

Supprimer les lignes commençant par la chaîne L :

$ sed /^L/d exemple.txt
C'est un texte avec des ereurs.
Beaucoups d'ereurs.
Une ligne ne contenant pas d'erreurs.

Supprimer les lignes numéro 2 et 4 :

$ sed '2d; 4d' exemple.txt
La première ligne d'un exemple de texte.
Beaucoups d'ereurs.
La dernière ligne de l'exemple de texte.

Supprimer les lignes numéro 2 à 3 :

$ sed '2,4d' exemple.txt
La première ligne d'un exemple de texte.
La dernière ligne de l'exemple de texte.

Supprimer les deux premières lignes :

$ sed '3,$d' exemple.txt
La première ligne d'un exemple de texte.
C'est un texte avec des ereurs.

Équivalent à :

$ head -n 2 exemple.txt
La première ligne d'un exemple de texte.
C'est un texte avec des ereurs.

La substitution

La commande de substitution s permet de substituer le résultat d’une expression régulière à un texte donné.

La substitution d’une chaîne par la chaîne vide est une suppression. La substitution d’une chaîne vide par une chaîne est une insertion.

Sa syntaxe est :

sed [adresse]s/expression/remplacement/options

Quelques options :

  • 0..9 : indique que la substitution ne doit être effectuée que pour la n-ième occurence du motif;
  • g : (global) effectue les modifications globalement sur toutes les occurences;
  • p : (print) affiche le contenu du tampon de travail (utilisé pour le déboguage);
  • w file : (write) écrit le résultat de la substitution dans le fichier file.

La substitution du premier motif d’une ligne

Substituer la première chaîne ereurs par la chaîne erreurs :

$ echo "ereurs ereurs" | sed 's/ereurs/erreurs/' 
erreurs ereurs

Le même traitement sur chaque ligne du fichier exemple.txt :

$ sed 's/ereurs/erreurs/' exemple.txt
La première ligne d'un exemple de texte.
C'est un texte avec des erreurs.

Supposons que l’on déplace le répertoire racine d’utilisateurs, de /krakatoa vers /fujiyama :

$ sed s!/krakatoa!/fujiyama!g /etc/passwd > /tmp/passwd.new

Attention car avec certains interpréteurs de commandes, comme bash, le caractère ! est vu comme un historique. Dans ce cas là, il suffit d’en utiliser un autre :

$ sed s#/krakatoa#/fujiyama#g /etc/passwd > /tmp/passwd.new

Supprimer les 3 derniers chiffres d’une chaîne numérique :

$ echo "12345" | sed -e 's/[0-9]\{3\}$//'
12

Supprimer les 3 derniers chiffres d’une chaîne numérique et afficher le résultat à l’aide d’une référence arrière (back-reference) :

$ echo "12345" | sed -e "s/\([0-9]\)\{3\}$/  résultat = \1/"
12  résultat = 5

Supprimer le premier caractère d’une chaîne alphabétique :

$ echo "abcde" | sed -e "s/^[a-zA-Z]\{1\}//"
bcde

Supprimer le premier caractère d’une chaîne alphabétique et transformer le reste en majuscules :

$ echo "abcde" | sed -e "s/^[a-zA-Z]\{1\}//" | tr '[:lower:]' '[:upper:]'
BCDE

Supprimer les 2 premiers caractères d’une chaîne alphabétique et transformer certains caratères en majuscules :

$ echo "abcde" | sed -e "s/^[a-zA-Z]\{2\}//" | tr 'abcde' 'aBcDe'
cDe

La substitution globale

Substituer les chaînes erors aux chaînes errors :

$ echo "ereurs ereurs" | sed 's/ereurs/erreurs/g' 
erreurs erreurs

Effectuer plusieurs substitutions :

$ sed -e 's/ereurs/erreurs/g' -e 's/dernière ligne/ligne finale/g' exemple.txt 
La première ligne d'un exemple de texte.
C'est un texte avec des erreurs.
Beaucoups d'erreurs.
Une ligne ne contenant pas d'erreurs.
La ligne finale de l'exemple de texte.

N’effectuer la substitution uniquement pour les lignes contenant le mot rendez-vous :

$ sed /rendez\-vous/s/soleil/lune/g fichier.in > fichier.out

Insérer une chaîne au début de chaque ligne :

$ sed 's/^/> /g' exemple.txt
> La première ligne d'un exemple de texte.
> C'est un texte avec des ereurs.

Insérer une chaîne à la fin de chaque ligne :

$ sed 's/$/EOL/g' exemple.txt
La première ligne d'un exemple de texte.EOL
C'est un texte avec des ereurs.EOL

Lister les fichiers du répertoire /usr/bin dont la deuxième lettre est un a :

$ ls /usr/bin | sed -n '/^.a/p' > filename.tmp.txt

Supprimer la troisième ligne du fichier temporaire :

$ head -n 3 filename.tmp.txt | tail -n 1
base64
$ sed '3,3d' filename.tmp.txt | grep base64
$ sed '3,3d' filename.tmp.txt >  falename.tmp.txt 

La commande awk

La commande awk (Alfred, Weinberger et Kernighan) est un processeur de fichier, spécialisé dans le traitement des données formatées en lignes et colonnes. Sa syntaxe est inspirée de celle du langage C.

L’installation

Plusieurs implémentations utilisent la syntaxe du programme awk :

  • nawk : (New AWK) étend les fonctionnalités de la version initiale;
  • mawk : (Mike’s AWK) une version connue pour sa rapidité dans certains cas;
  • gawk : (Gnu AWK) la version GNU, avec une version dérivée pour travailler sur le réseau TCP/IP;
  • jawk : (Java AWK) une version fonctionnant sur une JVM.

L’implémentation awk adoptée par la distribution Wheezy est gawk.

Installer les paquets nécessaires :

$ sudo apt-get install gawk gawk-doc

Connaître l’implémentation de awk utilisée :

$ type awk
awk est /usr/bin/awk

Lister les implémentations disponibles :

$ update-alternatives --list awk
/usr/bin/gawk
/usr/bin/original-awk

Obtenir plus d’informations sur les implémentations disponibles :

$ update-alternatives --display awk
awk - mode automatique
 le lien pointe actuellement sur /usr/bin/gawk
/usr/bin/gawk - priorité 10
 lien secondaire awk.1.gz : /usr/share/man/man1/gawk.1.gz
 lien secondaire nawk : /usr/bin/gawk
 lien secondaire nawk.1.gz : /usr/share/man/man1/gawk.1.gz
/usr/bin/original-awk - priorité 0
 lien secondaire awk.1.gz : /usr/share/man/man1/original-awk.1.gz
La « meilleure » version actuelle est « /usr/bin/gawk ».

Changer d’implémentation par défaut :

$ update-alternatives --config awk
Il existe 2 choix pour l'alternative awk (qui fournit /usr/bin/awk).

  Sélection   Chemin                 Priorité  État
------------------------------------------------------------
  0            /usr/bin/gawk           10        mode automatique
  1            /usr/bin/gawk           10        mode manuel
* 2            /usr/bin/original-awk   0         mode manuel

Appuyez sur <Entrée> pour conserver la valeur par défaut[*] ou choisissez le numéro sélectionné :0

Connaître la version de awk :

$ awk --version
awk version 20110810

Connaître la version de gawk :

$ awk -W version
GNU Awk 4.0.1

L’aide de awk :

$ awk --help

Le manuel et l’information sur awk :

$ man awk
$ info awk

Le guide de l’utilisateur The GNU Awk User's Guide :

$ elinks file:///usr/share/doc/gawk-doc/gawk-html/index.html

Les exemples du paquet gawk-doc :

$ ls /usr/share/doc/gawk/examples
data  misc  network  prog

La ligne de commande

La syntaxe de awk est la suivante :

awk [-F] [-v var=valeur] 'programme' fichier

ou

awk [-F] [-v var=valeur] -f fichier-programme fichier

Les arguments :

  • -F : doit être suivi du séparateur de champ (-F: pour un : comme séparateur de champ);
  • -v : définit une variable qui sera utilisée par la suite dans le programme;
  • -f : suivi du nom du fichier programme de awk.

La structure du programme est :

'motif1 { action1 } motif2 { action2 } …'

quand il n’y a pas de motif, l’action s’applique à toutes les lignes du fichier.

Le fonctionnement global de awk

Le fichier est lu séquentiellement ligne après ligne.

Chaque ligne du fichier est comparée successivement aux différents motifs et l’action du premier motif renvoyant la valeur vraie est exécutée.

Une fois la ligne sélectionnée, elle est découpée en champs selon le séparateur d’entrée FS, qui par défaut correspond au caractère espace ou au caractère tabulation.

L’utilisation de awk en ligne de commande pour émuler la commande cat :

$ echo coucou | awk '{ print }'
coucou

Placer les commandes dans un fichier :

$ cat > print-awk.cmd << EOF
{ print }
EOF
$ chmod 755 print-awk.cmd
$ echo coucou | awk -f print-awk.cmd
coucou

On utilise un Here Documents << de Bash pour créer puis écrire le contenu du fichier print-awk.cmd.

Les scripts awk utilisent le shebang #!/usr/bin/awk -f :

$ cat > print.awk << EOF
#!/usr/bin/awk -f
{ print }
EOF
$ chmod 755 print.awk 
$ echo coucou | ./print.awk 
coucou

Utilisation de awk dans un script Bash :

$ cat > print-awk.sh << EOF
awk '{ print }'
EOF
$ chmod 755 print-awk.sh
$ echo coucou | ./print-awk.sh
coucou

Les enregistrements, les champs et le séparateur

awk scinde les données d’entrée en enregistrements et les enregistrements en champ.

Un enregistrement est une chaîne d’entrée délimité par un retour chariot. Un enregistrement est référencé par la variable $0.

Un champ est une chaîne délimitée par un espace dans un enregistrement. Dans un enregistrement les champs sont référencés par les variables $1, $2, …, $NF, où $NF est le dernier champ.

La variable prédéfinie FS (Field Separator) FIXME valeur par défault.

Afficher les lignes du fichier /etc/passwd :

$ awk '{print $0}' /etc/passwd 

La commande équivalente avec cat :

$ cat /etc/passwd 

Afficher la liste des utilisateurs systèmes :

$ awk -F: '{print $1}' /etc/passwd 

La liste des utilisateurs système n’ayant pas de mot de passe :

$ awk -F: '{ if ($2 == "") print $1 ": pas de mot de passe !" }' < /etc/passwd

Chaque enregistrement du fichier /etc/passwd représente un utilisateur système. Les champs d’un enregistrement sont séparés par un caractère deux-points :.

$ cat > passwd.awk << 'EOF'

#!/usr/bin/awk -f

BEGIN { FS=":"}

{ print "Enregistrement :" }
{ print $0 } 
{ print "Champs :" } 
{ print "identifiant  : " $1 }
{ print "mot de passe : " $2 }
{ print "UID          : " $3 }
{ print "GID          : " $4 }
{ print "commentaire  : " $5 }
{ print "répertoire   : " $6 }
{ print "shell        : " $7 }
EOF
$ chmod 755 passwd.awk
$ head -1 /etc/passwd | ./passwd.awk
Enregistrement :
root:$6$ZgID92lJ$JyxrYo:0:0:root:/root:/bin/bash
Champs :
identifiant  : root
mot de passe : $6$ZgID92lJ$JyxrYoB5j
UID          : 0
GID          : 0
commentaire  : root
répertoire   : /root
shell        : /bin/bash

Transformer des lignes de la forme Username:Firstname:Lastname:Telephone number sous la forme d’une entrée d’un annuaire LDAP :

dn: uid=Username, dc=example, dc=com
cn: Firstname Lastname
sn: Lastname
telephoneNumber: Telephone number
$ cat > data.txt << EOF
Alice:Merveille:Lastname1:0102030405
Bob:Sleigh:Lastname2:0506070809
Eve:Tentation:Lastname3:1011121314
EOF
$ cat > data2ldap.awk << 'EOF'
#!/usr/bin/awk -f

BEGIN { FS=":"; }
{ 
print "dn: uid="$1", dc=example, dc=com"
print "cn: " $2 " " $3
print "sn: " $3 
print "telephoneNumber: " $4 
}
EOF
$ chmod 755 data2ldap.awk
$ ./data2ldap.awk data.txt
dn: uid=Alice, dc=example, dc=com
cn: Merveille Lastname1
sn: Lastname1
telephoneNumber: 0102030405

Les blocs BEGIN et END

Deux blocs spéciaux permettent d’ajouter des instructions à exécuter avant la lecture du premier enregistrement, le bloc BEGIN et après la lecture du dernier enregistrement, le bloc END.

Le bloc BEGIN est utilisé pour :

  • afficher un en-tête avant l’exécution du bloc principal;
  • la déclaration de variables;
  • l’initialisation de variables pour les opérations d’incrémentation et de décrémentation dans le bloc principal.

Le bloc END est utilisé pour :

  • afficher un résultat final après l’exécution du bloc principal.

Afficher un en-tête, les deux dernières lignes du fichier /etc/passwd, suivi d’un pied de page sur la sortie courante :

$ cat > begin-end.awk << 'EOF'
#!/usr/bin/awk -f

BEGIN { print "*** begin ***" }
{ print $0 }
END { print "*** end ***" }
EOF
$ chmod 755 begin-end.awk 
$ tail -2 /etc/passwd | ./begin-end.awk 
*** begin ***
rtkit:x:123:129:RealtimeKit,,,:/proc:/bin/false
postgres:x:124:130:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
*** end ***

La liste des utilisateurs système :

$ awk 'BEGIN { FS = ":" } { print $1 }' /etc/passwd

Afficher les deux dernières lignes du fichier /etc/passwd, équivalent à la commande tail -2 :

$ awk '{ y=x "\n" $0; x=$0 }; END { print y }' /etc/passwd

Afficher la dernière ligne du fichier /etc/passwd, équivalent à la commande tail -1 :

$ awk '{ rec=$0 } END { print rec }' /etc/passwd

Afficher la suite des sommes cumulées des nombres de la troisième colonne du fichier numbers.txt :

$ cat > numbers.txt << EOF
1 2 78
2 3 56
3 4 21
EOF
$ awk 'BEGIN { s=0 } { s=s+$3; print s }' numbers.txt 
78
134
155

Afficher la somme des nombres de la première colonne du fichier numbers.txt :

$ awk '{ s=s+$1 } END { print s }' numbers.txt
6

Quelques variables

La variable FIELDWIDTHS

FIELDWIDTHS : liste des tailles de champs.

Formatter des données en colonnes :

$ cat > fieldwidths.txt << EOF
 10:06pm  up 21 days, 14:04,  23 users
User     tty       login     idle   JCPU  PCPU  what
hzuo     ttyV0     8:58pm    9      5     vi    p24.tex
hzang    ttyV3     6:37pm    50                 -csh
eklye    ttyV5     9:53pm    7      1     em    thes.tex
EOF
$ cat > fieldwidths.awk << 'EOF'
BEGIN { FIELDWIDTHS = "9 6 10 6 7 7 35" }
NR > 2 {
  idle = $4
  sub(/^  */, "", idle)   # strip leading spaces
  if (idle == "") { idle = 0 }
  if (idle ~ /:/) {
    split(idle, t, ":")
    idle = t[1] * 60 + t[2]
  }
  if (idle ~ /days/) { idle *= 24 * 60 * 60 }
  print $1, $2, idle
}
EOF
$ chmod 755 fieldwidths.awk
$ awk -f fieldwidths.awk fieldwidths.txt
hzuo      ttyV0  9
hzang     ttyV3  50
eklye     ttyV5  7

Les variables ORS et OFS

  • OFS : (Output Field Separator) séparateur de champs pour la sortie, valeur par défaut " ";
  • ORS : (Output Record Separator) séparateur d’enregistrement pour la sortie, valeur par défaut \n.

Trouver la ligne du fichier data.txt contenant le premier champs de plus grande taille :

$ awk '$1 > max { max=$1; maxline=$0 }; END { print max, maxline }' data.txt

Afficher la liste des tailles maximuns des colonnes du fichier data.txt :

$ cat > vars-ors-ofs-pre.awk << 'EOF'

#!/usr/bin/awk -f

BEGIN { FS=":" }
{
  for (i=1; i<=NF; i++) {
    l=length($i)
    if (l>max[i]) { max[i]=l }
  }
}
END {
  fieldwidths = max[1]
  for (i=2; i<=NF; i++) { fieldwidths = fieldwidths " " max[i] }
  print fieldwidths
}
EOF
$ chmod 755 fieldwidths.awk
$ ./fieldwidths.awk data.txt
5 9 8 10

Pour transformer le fichier data.txt :

$ awk -F: '{ printf "%-5s %9s %8s %10s\n", $1, $2, $3, $4 }' data.txt > data.out.txt
$ cat data.out.txt
Alice Merveille Lastname 0102030405
Bob      Sleigh Lastname 0506070809
Eve   Tentation Lastname 1011121314
$ cat > vars-ors-ofs.awk << 'EOF'
#!/usr/bin/awk -f

BEGIN {
  FIELDWIDTHS="6 10 9 11"
  OFS=" |"
  n=split(FIELDWIDTHS, array, " ")
  array[n+1]=length(OFS)*n # taille des n séparateurs des n+1 colonnes
  array[n+2]=3 # taille de première colonne
  for (i in array) { l+=array[i] }
  for (i=0; i<l; i++) { ORS = ORS "-" }
  ORS = ORS "\n"
  print $ORS
}
{
  print " "NR," "$1, $2, $3, $4
}
EOF
$ chmod 755 vars-ors-ofs.awk 
$ ./vars-ors-ofs.awk data.out.txt
----------------------------------------------
 1 | Alice | Merveille | Lastname | 0102030405
----------------------------------------------
 2 | Bob   |    Sleigh | Lastname | 0506070809
----------------------------------------------
 3 | Eve   | Tentation | Lastname | 1011121314
----------------------------------------------

La variable FILENAME

FILENAME : nom du fichier sur lequel on applique les commandes.

Utilisation basique de la variable FILENAME :

$ echo 'texte 01' > filename01.txt
$ echo 'texte 02' > filename02.txt
$ cat > filename.awk << 'EOF'
#!/usr/bin/awk -f
BEGIN { f=""; }
{
  if (f != FILENAME) {
    print "Lecture du fichier", FILENAME;
    f=FILENAME;
  }
  print;
}
EOF
$ chmod 755 filename.awk 
$ ./filename.awk filename01.txt filename02.txt
Lecture du fichier filename01.txt
texte 01
Lecture du fichier filename02.txt
texte 02

Les variables NF et NR

  • NF : (Number of Fields) nombre de champs de l’enregistrement courant;
  • $NF : valeur du dernier champs.

NR : (Number Records) nombre d’enregistrements déjà lu.

Sélectionner les premières et dernières colonnes de la sortie de la commande ls -l :

$ ls -l | awk '{ print $1, $9 }' 
total 
-rwxr-xr-x awk-print.cmd
-rwxrwxrwx awk-print.sh

Une généralisation de la commande précédante. Le nombre de champs de la commande ls -l est variable d’un système à l’autre; Berkeley a 8 champs et System V a 9 champs.

$ ls -l | awk '{ print $1, $NF }' 
total 124
-rwxr-xr-x awk-print.cmd
-rwxrwxrwx awk-print.sh

Supprimer les lignes blanches du fichier /etc/passwd :

$ awk NF /etc/passwd

Numéroter des lignes du fichier /etc/passwd :

$ awk '{ print NR, $0 }' /etc/passwd
1 root:$6$ZgID92lJ$JyxrYoB5jg:0:0:root:/root:/bin/bash
2 daemon:x:1:1:daemon:/usr/sbin:/bin/sh

Dénombrer les lignes du fichier /etc/passwd :

$ awk 'END { print NR }' /etc/passwd

Afficher la première ligne du fichier /etc/passwd, équivalent à la commande head -1 :

$ awk 'NR > 1 { exit }; 1' /etc/passwd

Afficher les 10 premières ligne du fichier /etc/passwd, équivalent à la commande head -10 :

$ awk 'NR < 11' /etc/passwd

Afficher la 12ème ligne du fichier /etc/passwd :

$ awk 'NR==12' /etc/passwd

Équivalent à :

$ head -12 /etc/passwd | tac | line

Afficher les lignes du fichier /etc/passwd dont le numéro est compris entre 8 et 12 :

$ awk 'NR==8,NR==12' /etc/passwd

Équivalent à :

$ head -12 /etc/passwd | tail -5

Dénombrer les lignes, les mots et les caractères du fichier /etc/passwd :


FIXME

Équivalent à :

$ wc /etc/passwd | awk '{ print $1, $2, $3 }'
52 80 2637

Les variables ARGV et ARGC

  • ARGV : tableau des arguments de la ligne de commande;
  • ARGC : nombres d’arguments de la ligne de commande.
$ awk 'BEGIN { for (i=0; i<ARGC; i++) print "argv[" i "] = " ARGV[i] }' data=2 abc
argv[0] = awk
argv[1] = data=2
argv[2] = abc

L’option -v var=value définit la variable var avec la valeur value avant que l’exécution du programme commence. La valeur d’une telle variable est dispoble dans la règle BEGIN.

L’option -v ne peut définir qu’une seule variable, mais elle peut être utilisée plus d’une fois : awk -v foo=1 -v bar=2 …

Afficher les arguments de la commande awk :

$ cat > args-show.awk << 'EOF'
BEGIN {
  printf "A=%d, B=%d\n", A, B
  for (i=0; i<ARGC; i++) {
    printf "\tARGV[%d] = %s\n", i, ARGV[i]
  }
}
END { printf "A=%d, B=%d\n", A, B }
EOF
$ chmod 755 args-show.awk
$ awk -v A=1 -f args-show.awk B=2 /dev/null
A=1, B=0
    ARGV[0] = awk
    ARGV[1] = B=2
    ARGV[2] = /dev/null
A=1, B=2

Passer des arguments au programme :

$ cat > args.awk << 'EOF'
BEGIN {
  # Valeurs par défaut
  outfile = "x"
  count = 1000
 
  if (ARGC > 4) { usage() }

  i = 1
  if (ARGV[i] ~ /^-[[:digit:]]+$/) {
    count = -ARGV[i]
    ARGV[i] = ""
    i++
    printf "valeur de count: %d\n", count
  }

  # test argv in case reading from stdin instead of file
  if (i in ARGV) { i++ }    # passe le nom du fichier de données.

  if (i in ARGV) {
    outfile = ARGV[i]
    ARGV[i] = ""
    printf "fichier de sortie : %s\n", outfile
  }

  s1 = s2 = "a"
  out = (outfile s1 s2)
  printf "valeur de out : %s\n", out
}

function usage(e) {
  e = "utilisation : awk -f args.awk -- [-num] [file] [outname]"
  print e > "/dev/stderr"
  exit 1
}
EOF
$ chmod 755 args.awk
$ awk -f args.awk -- -3 file outname
valeur de count : 3
fichier de sortie : outname
valeur de out : outnameaa

La variable PROCINFO

PROCINFO : tableau d’informations à propos du programme awk courant.

$ cat > procinfo.awk << 'EOF'
#!/usr/bin/awk -f
BEGIN {
  print "prgpid: " PROCINFO["prgpid"]
  print "pid:    " PROCINFO["pid"]
  print "ppid:   " PROCINFO["ppid"]
  print "uid:    " PROCINFO["uid"]
  print "gid:    " PROCINFO["gid"]
  print "egid:   " PROCINFO["egid"]
  print "euid:   " PROCINFO["ugid"]
}
EOF
$ chmod 755 procinfo.awk
$ ./procinfo.awk
prgpid: 
pid:    29229
ppid:   531
uid:    1000
gid:    1000
egid:   1000
euid:   

Les expressions régulières

Afficher les lignes du fichier /etc/passwd contenant le caractère 2 :

$ awk '/2/ {print $0}' /etc/passwd
root:$6$ZgID92lJ$JyxrYoB5:0:0:root:/root:/bin/bash
bin:x:2:2:bin:/bin:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh

La commande équivalente avec grep :

$ grep '2' /etc/passwd

Afficher les lignes du fichier /etc/passwd ne contenant pas le caractère 2 :

$ awk '!/2/ {print $0}' /etc/passwd
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh

Afficher le répertoire racine de chaque partition et l’espace occupé :

$ df -h | awk '/dev/ { print $6, $5 }'
/dev 0%
/ 7%
/boot 6%
/home 74%

Afficher pour chaque ligne du fichier /etc/passwd vérifiant l’expression régulière /2/, la ligne précédante :

$ awk '/2/ { print (x=="" ? "match on line 1" : x) }; { x=$0 }' /etc/passwd
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
games:x:5:60:games:/usr/games:/bin/sh

Afficher pour chaque ligne du fichier /etc/passwd vérifiant l’expression régulière /2/, la ligne suivante :

$ awk '/2/ { getline; print }' /etc/passwd
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh

Afficher les lignes du fichier /etc/passwd contenues entre la ligne vérifiant l’expression régulière /root/ et la ligne vérifiant l’expression régulière /sys/ :

$ awk '/root/,/sys/' /etc/passwd

Afficher les lignes du fichier /etc/passwd dont le premier champs contient le caractère d :

$ awk '$1~/d/ {print $0}' /etc/passwd
root:$6$ZgID92lJ$JyxrYoB5jg/fg:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh

Afficher les fichiers de configuration du répertoire /etc commençant par a ou x :

$ ls /etc | awk '/^(a|x).*.conf$/ { print $0 }'
adduser.conf
apg.conf

Exemple :

$ echo abc | awk '{ gsub(//, "X") ; print }'
XaXbXcX

Les tableaux

Les tableaux sont associatifs : une clé peut être associée à une valeur unique. Il n’est pas nécessaire de déclarer un tableau. La valeur initiale des éléments est une chaîne vide ou zéro.

Le tableau unidimensionnel

Définir un élément du tableau tab :

tab[index]=variable

Pour supprimer l’élément d’index index du tableau tab :

delete tab[index]

Pour supprimer un tableau tab :

delete tab

Exemple d’un tableau associatif :

$ cat > array-create.awk << 'EOF'
#!/usr/bin/awk -f
BEGIN {
  tabname[1]="olive"
  tabname[5]="vert"  
  print tabname[1] ", " tabname[5] " et " tabname[3] "."
  delete tabname[5]
  tabname[3]="night."
  print tabname[1] ", " tabname[5] " et " tabname[3] "." 
  delete tabname
  print tabname[1] ", " tabname[5] " et " tabname[3] "."

  age["olivier"]=27
  age["veronique"]=25
  age["john"]=3
  for (name in age) { print name ": " age[name] }
}
EOF
$ chmod 755 array-create.awk 
$ ./array-create.awk 
olive, vert et .
olive,  et night..
,  et .
veronique: 25
john: 3
olivier: 27

Exemple : transformer une chaîne dans un tableau.

La fonction split(s, a, sep) transforme la chaîne s en un tableau a en utilisant le délimiteur sep.

set time = 12:34:56
set hr = `echo $time | awk '{split($0,a,":"); print a[1]}'` # = 12
set sec = `echo $time | awk '{split($0,a,":"); print a[3]}'` # = 56
# = 12 34 56
set hms = `echo $time | awk '{split($0,a,":"); print a[1], a[2], a[3]}'`
set hms = `echo $time | awk '{split($0,a,":"); for (i=1; i<=3; i++) print a[i]}'`
set hms = `echo $time | awk 'BEGIN{FS=":"}{for (i=1; i<=NF; i++) print $i}'`

Le parcours de tableau

$ cat > array.txt << EOF
5  I am the Five man
2  Who are you?  The new number two!
4  . . . And four on the floor
1  Who is number one?
3  I three you.
EOF
$ cat > array-scan.awk << 'EOF'
# Enregistre la valeur 1 pour chaque mot.
{ 
  for (i=1; i<=NF; i++) { used[$i]=1 } 
}
# Trouve le nombre de mots distincts de plus de 4 caractères.
END {
  for (x in used) {
    if (length(x) > 4) {
      ++num_long_words
      print x
    }
  }
  print num_long_words, " mots de plus de 4 caractères."
}
EOF
$ chmod 755 array-scan.awk
$ awk -f array-scan.awk array.txt
three
number
floor
3 mots de plus de 4 caractères.

Le tri de tableau

Exemple : ordre par défault.

$ awk 'BEGIN { a[4]=4; a[3]=3; for (i in a) print i, a[i] }'
4 4
3 3

Exemple : utiliser un ordre pré-définit.

$ awk 'BEGIN { PROCINFO["sorted_in"]="@ind_str_asc"; a[4]=4; a[3]=3; for (i in a) print i, a[i] }'
3 3
4 4

Exemple : créer un ordre.

$ cat > array-sort.awk << EOF
{
  if ($1 > max) { max = $1 }
  arr[$1] = $0
}
END {
  for (x = 1; x <= max; x++) { print arr[x] }
}
EOF
$ chmod 755 arraySort.awk 
$ awk -f array-sort.awk array.txt 
1  Who is number one?
2  Who are you?  The new number two!
3  I three you.
4  . . . And four on the floor
5  I am the Five man

Les tableaux multidimensionnels

On peut simuler un tableau à deux dimensions.

Le principe repose sur la création de deux indices (i, j) qu’on va concaténer avec SUBSEP (i:j).

SUBSEP=":"  
i="A",j="B"  
tab[i,j]="coucou"

L’élément coucou est indexé par la chaîne A:B.

Exemple : un tableau multidimensionnel.

$ cat > array-multidimensionnel.awk << 'EOF'
BEGIN {
  Colors[1,1] = "Red"
  Colors[1,2] = "Green"
  Colors[1,3] = "Blue"
  Colors[2,1] = "Yellow"
  Colors[2,2] = "Cyan"
  Colors[2,3] = "Purple"
}
END {
  for (i = 1; i <= 2; i++) {
    for (j = 1; j <= 3; j++) {
      printf "Colors[%d,%d] = %s\n", i, j, Colors[i,j];
    }
  }
}
EOF
$ chmod 755 array-multidimensionnel.awk
$ awk -f array-multidimensionnel.awk /dev/null
Colors[1,1] = Red
Colors[1,2] = Green
Colors[1,3] = Blue
Colors[2,1] = Yellow
Colors[2,2] = Cyan
Colors[2,3] = Purple

L’utilisation de la variable SUBSEP :

$ cat > array-subsep.awk << EOF
BEGIN {
  x=SUBSEP
  a="Red" x "Green" x "Blue"
  b="Yellow" x "Cyan" x "Purple"
  Colors[1][0] = ""
  Colors[2][0] = ""
  split(a, Colors[1], x)
  split(b, Colors[2], x)
  print Colors[2][3]
}
EOF
$ chmod 755 array-subsep.awk
$ awk -f array-subsep.awk 
Purple

Afficher la matrice transposée d’une matrice :

$ cat > array-transpose.txt << EOF
1 2 3 4 5 6
2 3 4 5 6 1
3 4 5 6 1 2
4 5 6 1 2 3
EOF
$ cat > array-transpose.awk << 'EOF'
{
  if (max_nf < NF) { max_nf = NF }
  max_nr = NR
  for (x = 1; x <= NF; x++) { vector[x, NR] = $x }
}
END {
  for (x = 1; x <= max_nf; x++) {
    for (y = max_nr; y >= 1; --y) { printf("%s ", vector[x, y]) }
    printf("\n")
  }
}
EOF
$ chmod 755 array-transpose.awk
$ awk -f array-transpose.awk array-transpose.txt
4 3 2 1 
5 4 3 2 
6 5 4 3 
1 6 5 4 
2 1 6 5 
3 2 1 6 

Annexe

Les structures de contrôles :

  • if (test) { actions } else { actions }
  • while (test) { actions }
  • do { actions } while (test)
  • for (expr1; expr2; expr3) { actions }
  • for (var in tableau) { actions }
  • next : passe à la ligne suivante;
  • continue : passe à l’élément suivant dans une boucle, hors d’une boucle, passe au motif suivant;
  • break : sort d’une boucle.

Les fonctions numériques :

  • int(x) : valeur entière de x;
  • sqrt(x) : racine carrée de x;
  • exp(x) : exponentielle de x;
  • log(x) : logarithme naturel de x;
  • sin(x) : sinus de x;
  • cos(x) : cosinus de x;
  • atan2(y,x) : arctangente de x/y dans l’intervalle [-pi, pi];
  • rand() : nombre aléatoire compris entre 0 et 1;
  • srand(x) : réinitialiser le générateur de nombre aléatoire.

Note : les angles sont exprimés en radian.

Les fonctions sur les chaînes de caractères

Les paramètres des fonctions :

  • s et t representent des chaînes de caractères;
  • r est une expression régulière;
  • i et n sont des nombres entiers.

Les fonctions :

  • getline() : lit l’entrée suivante d’une ligne, retourne 0 si fin de fichier (EOF), 1 sinon;
  • length(s) : retourne la longueur de la chaîne s;
  • index(s,t) : retourne l’index de la sous-chaîne t dans la chaine s, 0 si t n’est pas une sous-chaîne de s;
  • match(s,r) : retourne l’index ou s correspond à r et positionne RSTART et RLENTH;
  • sub(r,s,t) : comme gsub, mais remplace uniquement la première occurence;
  • substr(s,i,n) : retourne la sous-chaîne de s à partir de la position i et de taille n;
  • gsub(r,s,t) : substitue les occurences de r par s dans la chaîne t;
  • split(s,a,fs) : scinde s dans le tableau a sur fs, retourne le nombre de champs;
  • print : fonction d’affichage;
  • printf : fonction d’affichage formaté;
  • sprintf(fmt,liste expressions) : retourne la liste des expressions formatées suivant fmt.

Les variables prédéfinies

Les variables prédéfinies
Variable Rôle awk nawk gawk
FS (Field Separator) séparateur de champs en entrée, valeur par défaut : un ou plusieurs espaces blancs ou une tabulation. oui oui oui
NF (Number of Fields) nombre de champs de l’enregistrement courant. oui oui oui
RS (Record Separator) séparateur d’enregistrement en entrée, valeur par défaut \n. oui oui oui
NR (Number Records) nombre d’enregistrements déjà lu. oui oui oui
FILENAME Le nom du fichier sur lequel les commandes sont appliquées. oui oui oui
OFS (Output Field Separator) séparateur de champs pour la sortie, valeur par défaut “ ”. oui oui oui
ORS (Output Record Separator) séparateur d’enregistrement pour la sortie, valeur par défaut \n. oui oui oui
ARGC Nombre d’arguments de la ligne de commande. oui oui
ARGV Tableau des arguments de la ligne de commande. oui oui
ARGIND (ARGument INDex) index de ARGV pour le fichier courant. oui
FNR (File Number Records) nombre d’enregistrements du fichier. oui oui
OFMT (Ouput ForMaT) format de sortie des nombres, par défaut %.6g. oui oui
RSTART (Record START) index du premier caractère après match(). oui oui
RLENGTH (Record LENGTH) taille de la chaîne après match(). oui oui
SUBSEP (SUBscript SEPerator) séparateur de subscript, valeur par défaut \034. oui oui
ENVIRON (ENVIRONnement) Tableau des variables d’environnement. oui
IGNORECASE Ignore la casse d’une expression régulière. oui
CONVFMT (CONVersion ForMaT) valeur par défault : %.6g. oui
ERRNO Erreur courante suite à une défaillance de getline oui
FIELDWIDTHS Liste des tailles de champs. oui
BINMODE (BINary MODE) sous Windows. oui
LINT Bascule on/off pour le mode --lint. oui
PROCINFO Tableau d’informations à propos du programme awk courant. oui
RT (Record Terminator) oui
TEXTDOMAIN Localisation du programme awk courant. oui

Les sources