Gestion des entrées / sorties d'un script

Redirection des entrées/sorties standard

La commande interne exec permet de manipuler les descripteurs de fichier du shell courant.
Utilisée à l'intérieur d'un script, elle permet de rediriger de manière globale les entrées/sorties de celui-ci.

Rediriger l'entrée standard d'un script :

exec 0< fichier1

Toutes les commandes du script placées après cette directive et qui lisent leur entrée standard vont extraire leurs données à partir de fichier1.
Il n'y aura donc pas d'interaction avec le clavier.

Rediriger la sortie standard et la sortie d'erreur standard d'un script :

exec 1> fichier1 2> fichier2

Toutes les commandes du script placées après cette directive et qui écrivent sur leur sortie standard enverront leurs résultat dans fichier1.
Celles qui écrivent sur leur sortie d'erreur standard enverront leurs erreurs dans fichier2.

Rediriger la sortie standard et la sortie d'erreur standard d'un script dans un même fichier :

exec 1> fichier1 2>&1

Toutes les commandes du script placées après cette directive enverront leurs résultats et leurs erreurs dans fichier1

Premier exemple :

Redirection de la sortie standard vers /tmp/fichier1.log et redirection de la sortie d'erreur standard vers /tmp/fichier2.log.

$ nl test.sh
     1  #!/bin/bash
     2  exec 1> /tmp/fichier1.log 2> /tmp/fichier2.log
     3  echo "Début du traitement : $(date)"
     4  ls
     5  cp *.zzz /tmp
     6  rm *.zzz
     7  sleep 5
     8  echo "Fin du traitement : $(date)"
     9  exit 0
$

Exécution du script.

$ ./test.sh
$

Affichage du fichier /tmp/fichier1.log

$ nl /tmp/fichier1.log
     1  Début du traitement : lundi 12 décembre 2011, 08:23:33 (UTC+0100)
     2  1coucou
     3  24902
     4  25013
     5  25031
     6  25043
     7  boucleFor01.sh
     8  boucleFor02.sh
     9  boucleUntil01.sh
     .....
    87  Fin du traitement : lundi 12 décembre 2011, 08:23:38 (UTC+0100)
$

Affichage du fichier /tmp/fichier2.log (les fichiers *.zzz n'exsistant pas, 2 erreurs sont générées)

$ nl /tmp/fichier2.log
     1  cp: impossible d'évaluer «*.zzz»: Aucun fichier ou dossier de ce type
     2  rm: impossible de supprimer «*.zzz»: Aucun fichier ou dossier de ce type
$

Deuxième exemple :

Redirection de la sortie standard et de la sortie d'erreur standard vers le fichier /tmp/fichier3.log

$ nl ./test2.sh
     1  #!/bin/bash
     2  exec 1> /tmp/fichier3.log 2>&1
     3  echo "Début du traitement : $(date)"
     4  ls
     5  cp *.zzz /tmp
     6  rm *.zzz
     7  sleep 5
     8  echo "Fin du traitement : $(date)"
     9  exit 0
$

Exécution du script

$ ./test2.sh
$

Affichage du fichier /tmp/fichier3.log

$ nl /tmp/fichier3.log
     1  Début du traitement : lundi 12 décembre 2011, 12:56:35 (UTC+0100)
     2  1coucou
     3  24902
     4  25013
     5  25031
     6  25043
     .....
    88  cp: impossible d'évaluer «*.zzz»: Aucun fichier ou dossier de ce type
    89  rm: impossible de supprimer «*.zzz»: Aucun fichier ou dossier de ce type
    90  Fin du traitement : lundi 12 décembre 2011, 12:56:40 (UTC+0100)
$

Troisième exemple :

Redirection de l'entrée standard

$ nl test3.sh
     1  #!/bin/bash
     2  exec 0< /etc/passwd
     3  cpt=1
     4  while read ligne
     5  do
     6          echo "Lecture de la ligne $cpt"
     7          echo $ligne
     8          ((cpt+=1))
     9  done
    10  exit 0
$

Exécution du script

$ ./test3.sh
Lecture de la ligne 1
root:x:0:0:root:/root:/bin/bash
Lecture de la ligne 2
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
Lecture de la ligne 3
bin:x:2:2:bin:/bin:/bin/sh
Lecture de la ligne 4
sys:x:3:3:sys:/dev:/bin/sh
Lecture de la ligne 5
sync:x:4:65534:sync:/bin:/bin/sync
Lecture de la ligne 6
games:x:5:60:games:/usr/games:/bin/sh
Lecture de la ligne 7
man:x:6:12:man:/var/cache/man:/bin/sh
.....
$

Gestion de fichiers

Les shells récents apportent une fonctionnalité supplémentaire.
La possibilité d'ouvrir et de manipuler des fichiers en utilisant des descripteurs compris entre 3 et 9 (en supplément de 0, 1 et 2).
L'avantage est de pouvoir manipuler des fichiers tout en conservant les descripteurs 0, 1 et 2 connectés sur le terminal.

Ouverture de fichier :

En lecture

exec  desc <fichier

En écriture

exec  desc >fichier

Lecture à partir d'un fichier :

read variable1 variable2 ..... variablen <&desc

ou

read -udesc variable1 variable2 ..... variablen

Ecriture dans un fichier :

echo variable1 variable2 ..... variablen >&desc

ou

print -udesc variable1 variable2 ..... variablen

Fermeture d'un fichier :

Syntaxe :

Ouvert en lecture

exec  desc <&-

Ouvert en écriture

exec  desc >&-

Exemple :

$ nl test4.sh
     1  #!/bin/bash
     2  # Ouverture du fichier /etc/passwd en lecture sous le descripteur 3
     3  # et du fichier /tmp/resultat.log en écriture sous le descripteur 4
     4  exec 3</etc/passwd 4>/tmp/resultat.log
     5  cpt=1
     6  # Lecture ligne par ligne du fichier /etc/passwd
     7  # correspondant au descripteur 3
     8  while read -u3 ligne
     9  do
    10          # Ecriture des données dans le fichier /tmp/resultat.log
    11          # correspondant au descripteur 4
    12          echo "Ecriture de la ligne $cpt" >&4
    13          echo $ligne >&4
    14          ((cpt+=1))
    15  done
    16  # Fermeture du fichier /etc/passwd correspondant au descripteur 3
    17  exec 3<&-
    18  # Fermeture du fichier /tmp/resultat.log correspondant au descripteur 4
    19  exec 4>&-
    20  exit 0
$

Résultat :

$ ./test4.sh
$ nl /tmp/resultat.log
     1  Ecriture de la ligne 1
     2  root:x:0:0:root:/root:/bin/bash
     3  Ecriture de la ligne 2
     4  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
     5  Ecriture de la ligne 3
     6  bin:x:2:2:bin:/bin:/bin/sh
     7  Ecriture de la ligne 4
     8  sys:x:3:3:sys:/dev:/bin/sh
     9  Ecriture de la ligne 5
    10  sync:x:4:65534:sync:/bin:/bin/sync
    11  Ecriture de la ligne 6
    12  games:x:5:60:games:/usr/games:/bin/sh
    13  Ecriture de la ligne 7
    14  man:x:6:12:man:/var/cache/man:/bin/sh
    .....
$

Traitement d'un fichier

Les diffétrentes façons d'exploiter un fichier

Rediriger l'exécution du script

Les redirections peuvent également être faites au moment de l'exécution du script.

Exemple :

Avec le script suivant

$ nl test5.sh
     1  #!/bin/bash
     2  cpt=1
     3  # Lecture ligne par ligne du fichier passé en paramètre
     4  # ou lecture de la saisie clavier si pas de fichier en paramètre
     5  while read ligne
     6  do
     7          # Ecriture des données dans le fichier passé en paramètre
     8          # ou affichage à l'écran si pas de fichier en paramètre
     9          echo "Ecriture de la ligne $cpt"
    10         echo $ligne
    11         ((cpt+=1))
    12  done
    13  exit 0
$

Exécution du script sans paramètre

$ ./test5.sh
saisie 1     # saisie
Ecriture de la ligne 1
saisie 1
saisie 2     # saisie
Ecriture de la ligne 2
saisie 2
saisie 3     # saisie
Ecriture de la ligne 3
saisie 3
^d
$

Exécution du script avec paramètre en entrée

$ ./test5.sh < /etc/passwd
Ecriture de la ligne 1
root:x:0:0:root:/root:/bin/bash
Ecriture de la ligne 2
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
Ecriture de la ligne 3
bin:x:2:2:bin:/bin:/bin/sh
Ecriture de la ligne 4
sys:x:3:3:sys:/dev:/bin/sh
Ecriture de la ligne 5
sync:x:4:65534:sync:/bin:/bin/sync
Ecriture de la ligne 6
games:x:5:60:games:/usr/games:/bin/sh
Ecriture de la ligne 7
man:x:6:12:man:/var/cache/man:/bin/sh
.....
$

Exécution du script avec paramètres en entrée et en sortie

$ ./test5.sh < /etc/passwd > /tmp/resultat.log
$ nl /tmp/resultat.log
     1  Ecriture de la ligne 1
     2  root:x:0:0:root:/root:/bin/bash
     3  Ecriture de la ligne 2
     4  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
     5  Ecriture de la ligne 3
     6  bin:x:2:2:bin:/bin:/bin/sh
     7  Ecriture de la ligne 4
     8  sys:x:3:3:sys:/dev:/bin/sh
     9  Ecriture de la ligne 5
    10  sync:x:4:65534:sync:/bin:/bin/sync
    11  Ecriture de la ligne 6
    12  games:x:5:60:games:/usr/games:/bin/sh
    13  Ecriture de la ligne 7
    14  man:x:6:12:man:/var/cache/man:/bin/sh
     .....
$

Redirections internes au script

Les redirections d'entrée (0) et de sortie (1) standards peuvent également être faite à l'intérieur du script.

Exemple :

Avec le script suivant

$ nl test6.sh
     1  #!/bin/bash
     2  exec </etc/passwd >/tmp/resultat.log
     3  cpt=1
     4  while read ligne
     5  do
     6          echo "Ecriture de la ligne $cpt"
     7          echo $ligne
     8          ((cpt+=1))
     9  done
    10  exit 0
$

Exécution du script et affichage du résultat

$ ./test6.sh
$ nl /tmp/resultat.log
     1  Ecriture de la ligne 1
     2  root:x:0:0:root:/root:/bin/bash
     3  Ecriture de la ligne 2
     4  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
     5  Ecriture de la ligne 3
     6  bin:x:2:2:bin:/bin:/bin/sh
     7  Ecriture de la ligne 4
     8  sys:x:3:3:sys:/dev:/bin/sh
     9  Ecriture de la ligne 5
    10  sync:x:4:65534:sync:/bin:/bin/sync
    11  Ecriture de la ligne 6
    12  games:x:5:60:games:/usr/games:/bin/sh
    13  Ecriture de la ligne 7
    14  man:x:6:12:man:/var/cache/man:/bin/sh
     .....
$

Redirection d'un bloc

Il est également possible de rediriger uniquement les commandes situées à l'intérieur d'une structure de contrôle.
Les redirections doivent être écrites derrière le mot clé qui ferme la structure de contrôle.
A l'exécution, elles sont mises en place avant le traitement de la structure de contrôle.*

Exemple :

Dans le script suivant, seules les commandes situées à l'intérieur de la boucle while seront redirigées.

$ nl test7.sh
     1  #!/bin/bash
     2  echo "Lancement du script"
     3  cpt=1
     4  while read ligne
     5  do
     6          echo "Ecriture de la ligne $cpt"
     7          echo $ligne
     8          ((cpt+=1))
     9  done </etc/passwd >/tmp/resultat.log
    10  exit 0
$ ./test7.sh
Lancement du script
$ nl /tmp/resultat.log
     1  Ecriture de la ligne 1
     2  root:x:0:0:root:/root:/bin/bash
     3  Ecriture de la ligne 2
     4  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
     5  Ecriture de la ligne 3
     6  bin:x:2:2:bin:/bin:/bin/sh
$

Rediriger un bloc avec des fichiers ouverts en amont

$ nl test8.sh
     1  #!/bin/bash
     2  exec 3</etc/passwd 4>/tmp/resultat.log
     3  echo "Lancement du script"
     4  cpt=1
     5  while read ligne
     6  do
     7          # Ecriture des données dans le fichier /tmp/resultat.log
     8          # correspondant au descripteur 4
     9          echo "Ecriture de la ligne $cpt"
    10          echo $ligne
    11          ((cpt+=1))
    12  done <&3 >&4
    13  echo "Fin du script"
    14  exec 3<&-
    15  exec 4>&-
    16  exit 0
$ ./test8.sh
Lancement du script
Fin du script
$ nl /tmp/resultat.log
     1  Ecriture de la ligne 1
     2  root:x:0:0:root:/root:/bin/bash
     3  Ecriture de la ligne 2
     4  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
     5  Ecriture de la ligne 3
     6  bin:x:2:2:bin:/bin:/bin/sh
$

Découper une ligne en champs

Si les lignes du fichier à traiter sont structurées en champs, il est très facile de récupérer chacun de ceux ci dans une variable.
Pour cela, il faut modifier la valeur de la variable IFS.

Exemple :

Le script suivant génère, à partir du fichier /etc/passwd, un affichage à l'écran du username suivi de son uid.
La variable IFS est sauvegardée (ligne 13) afin d'être restaurée (ligne 19) et son contenu est remplacé par ":" (ligne 14).
":" étant le caractère séparant les champs du fichier /etc/passwd.
La commande read reçoit 7 variables en argument (ligne 15). Autant de variables que de champs dans le fichier /etc/passwd.
Elle découpe donc la ligne lue (ligne 18) en champs en utilisant le caractère ":" comme séparateur.
La ligne est donc automatiquement découpée et les valeurs récupérables via les variables indiquées.

$ nl test9.sh
     1  #!/bin/bash
     2  if (( $# != 1 ))
     3  then
     4          echo "Mauvais nombre d'arguments"
     5          echo "Usage : $0 fichier"
     6          exit 1
     7  fi
     8  if [[ ( ! -f "$1" ) || ( ! -r "$1" ) ]]
     9  then
    10          echo "$1 n'est pas un fichier ordinaire ou n'est pas accessible en lecture"
    11          exit 2
    12  fi
    13  oldIFS="$IFS"
    14  IFS=":"
    15  while read username password uid gid nomComplet home shell
    16  do
    17          echo "$username ==> $uid"
    18  done < $1
    19  IFS="$oldIFS"
    20  exit 0
$ ./test9.sh /etc/passwd
root ==> 0
daemon ==> 1
bin ==> 2
sys ==> 3
$

Ce script peut très bien être dirigé dans un tube et exploité avec la commande grep

$ ./test9.sh /etc/passwd | grep "root"
root ==> 0
$