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
.....
$
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.
En lecture
exec desc <fichier
En écriture
exec desc>fichier
read variable1 variable2 ..... variablen <&desc
ou
read -udesc variable1 variable2 ..... variablen
echo variable1 variable2 ..... variablen >&desc
ou
print -udesc variable1 variable2 ..... variablen
Syntaxe :
Ouvert en lecture
exec desc<&-
Ouvert en écriture
exec desc>&-
$ 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
.....
$
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
.....
$
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
.....
$
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
$
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
$