Shell avancé
Programmes et redirections
Les fichiers spéciaux
Il existe 3 fichiers spéciaux utilisés par de nombreux programmes et gérés par votre shell:
stdin
: entrée d'un programme, par exemple la saisie clavier dans le shellstdout
: sortie d'un programme, par exemple la zone d'affichage dans le shellstderr
: sortie d'erreur d'un programme, par exemple la zone d'affichage dans le shell
Un ou plusieurs programmes peuvent lire séparément stderr
et stdout
d'un autre programme. Il est donc important de ne pas mélanger les messages envoyés (on parlera de données écrites) sur stdout
(données de résultat) et stderr
(messages de déroulement)
//OK: les messages de déroulements sont bine séparés des données produites
fprintf(stdout, "Résultat ligne 1\n");
fprintf(stderr, "En cours");
fprintf(stdout, "Résultat ligne 2\n");
//MAUVAISE PRATIQUE: les messages de déroulements et les données produites sont mélangés
fprintf(stdout, "Résultat ligne 1\n");
fprintf(stdout, "En cours");
fprintf(stdout, "Résultat ligne 2\n");
Redirection des fichiers spéciaux
Le shell associe par défaut stdout
ou stderr
à la zone d'affichage de la fenêtre où il s'exécute, en d'autres termes toute donnée écrite par le programme dans ces deux fichiers s'affichera à l'écran sous forme de texte, pas forcément lisible si les données écrites ne sont pas du texte.
Mais il est possible de dire au shell que ces fichiers spéciaux sont à remplacer par d'autres fichiers. On parle alors de redirection des sorties d'un programme.
Le shell peut envoyer les données émises sur stdout
ou stderr
vers des fichiers avec le symbole >
:
-
redirection de sortie:
-
redirection de sortie et d'erreur:
Note
>
est équivalent à 1>
et signifie redirection de la sortie stdout
. Si le programme génère des erreurs, elles seront affichées dans le terminal si stderr
n'a pas été redirigé.
Bien évidement cela ne marche que si le programme est conçu pour écrire dans ces fichiers spéciaux.
Une redirection très utile consiste à envoyer les données "nulle part" donc à ne pas les transmettre du tout. Sous la plupart des systèmes, ceci se fait via le fichier spécial /dev/null.
Par exemple pour tester uniquement ce qu'un programme écrit sur stderr
sans s'occuper des données qu'il produit:
La redirection >
écrase le fichier de destination. Si vous voulez ajouter le contenu à la fin du fichier de destination, utilisez >>
(ou 2>>
pour stderr
):
Note
Prenez le temps de réfléchir à la gestion de ces fichiers spéciaux lorsque vous codez un programme, en particulier si votre programme produit des fichiers (autres que des logs) qui pourraient être utilisés par d'autres programmes.
Enchaîner des programmes
Si un programme sait lire depuis un fichier, on peut lui indiquer que le fichier à prendre en entrée est en fait le fichier stdout
d'un autre programme.
De cette manière, les fichiers spéciaux peuvent être utilisés comme canal unidirectionnel de communication entre programmes.
On utilise pour cela le symbole |
pour data pipe ou tube de données, qui redirige stdout
du premier programme vers l'entrée du suivant.
Si le programme de destination ne prend pas ses données d'entrée depuis stdin
mais depuis un nom de fichier donnée en argument, la convention est d'utiliser -
comme nom pour désigner stdin
:
On peut enchaîner ainsi plusieurs programmes
Cette technique est très utile pour échanger simplement des informations entre programmes. Il faut toutefois noter que c'est une technique dite bloquante: dans prog1 | prog2
, prog2
est mis en attente par le système tant que prog1
fonctionne et qu'aucune nouvelle donnée n'est écrite par prog1
sur son fichier stdout
.
Cas avancés de chaînage
Ces techniques ne marchent que si les programmes respectent les conventions des fichiers spéciaux. De même, si un programme produit plusieurs fichiers en une exécution, ces techniques ne marcheront pas.
Il est possible de s'en sortir via des noms explicites pour ces "tubes", on parlera de tubes nommés
ou named pipe
. La technique est nettement plus complexe à mettre en oeuvre dans un programme (en particulier sous windows), et ne sera pas couverte dans cette UE.
Pour aller plus loin
Ligne de commande avancée
Un shell ne se contente pas de traiter une ligne de commande simple comme nous l'avons vu jusqu'à présent. Il dispose aussi d'une grammaire permettant de construire des lignes de commandes avancés.
Il est possible de faire deux commandes en une, en séparant les commandes par ;
:
Il est possible de faire deux commandes, la seconde ne s'exécutant que si la première est valide, en séparant les commandes par &&
:
Il est possible de faire deux commandes, la seconde ne s'exécutant que si la première n'est pas valide, en séparant les commandes par ||
:
Attention
Ne confondez pas le symbole de tubes |
et le symbole OU ||
La logique de ces opérations repose sur le fait qu'un programme, lorsqu'il finit son exécution, retourne un code d'erreur (un entier) qui par convention est 0 en cas de succès et non-0 en cas d'erreur.
Note
Prenez le temps de réfléchir à la gestion de votre code de retour lorsque vous codez un programme, cela simplifiera l'utilisation de votre programme via le shell.
Vous pouvez par exemple tester le résultat de la dernière commande exécutée par le shell en utilisant le mot-clé $?
:
Chaque type de shell (bash, zsh, sh) a sa propre grammaire et ses outils pour faire des opérations très classiques comme des boucles for, while, des additions, etc...
Il n'est pas dans les objectifs de cette UE de couvrir tout cela, mais vous trouverez d'excellents tutoriels en ligne comme, par exemple, sur wikibooks.
Ligne de commande trop longue
Il est assez fréquent d'avoir des lignes de commandes complexes dont la longueur dépasse la zone d'affichage. Cela rend l'édition de la ligne de commande assez difficile.
Pour éviter ceci, le shell interprète le caractère \
comme une indication de continuation sur plusieurs lignes:
./mon_super_programe --option1 --option2=bla --option3=foo fichier_in1 fichier_in2 fichier_in3 -o fichier_out
peut donc s'écrire
./mon_super_programe --option1 --option2=bla --option3=foo \
fichier_in1 fichier_in2 fichier_in3 \
-o fichier_out
Ceci est souvent le cas dans les exemples que vous trouverez sur internet, et peut s'avérer très pratique pour la lisibilité de vos scripts (voir ci-après).
Un shell pour plusieurs programmes
Nous avons jusqu'à présent invoqué commandes/programmes en tapant directement le nom désiré puis avons attendus le résultat.
Dans un système d'exploitation, chaque programme est en fait exécuté par une logique spéciale du système appelée processus (process en anglais), de manière à isoler le programme du reste du monde (mémoire, accès aux ressources, etc...).
Le processus du shell est différent du processus exécutant la commande entrée, afin que si une erreur fatale advienne dans le programme démarré et qu'il doive être arrêté par le système (on parlera de tuer un processus ou kill a process), le shell ne soit pas tué lui aussi.
Lorsqu'un programme est lancé dans le shell comme vu précédemment, le shell attendra que le programme finisse avant de vous rendre la main. On parle de lancement en premier plan
ou foreground
.
Il est possible de lancer un programme sans bloquer le shell, on parlera de lancement en arrière-plan
ou background
. On utilise la plupart du temps le symbole &
en fin de ligne pour cela.
Voici un exemple utilisant la commande sleep
qui fait dormir le shell pour le nombre de secondes indiqué:
Avec cette méthode, vous pouvez lancer plusieurs programmes en même temps, et attendre le résultat de l'ensemble via la commande wait
:
for i in {1..10} ; do echo "CompteurA $i" && sleep 1 ; done &
for i in {1..5} ; do echo "CompteurB $i" && sleep 2 ; done &
echo "Compteurs démarrés"
wait
Ceci est particulièrement utile dans les scripts abordés ci-dessous.
Warning
Indépendamment du mode de lancement du programme, le processus créé sera automatiquement tué si le processus qui l'a lancé (ici le shell) est tué. Nous reviendrons sur cet aspect dans la partie réseau.
Scripts shell
Vous aurez assez rapidement un ensemble de commande à appliquer pour configurer un projet, lancer un programme et regarder les résultats, etc...
Au lieu de taper chaque commande à chaque fois, il est recommandé de passer par un script shell, qui est dans le plus simple des cas la liste de vos commandes ligne par ligne.
Pour ceci il faut:
- Créer un fichier vide (
touch monscript.sh
) et donner les droits d'exécution (chmod +x monscript.sh
) au fichier. - Ajouter au tout début
#!/bin/sh
- ou
#!/bin/bash
si vous voulez forcer l'utilisation du shell bash
- ou
- Coller vos commandes
- Mettre des commentaires en commençant une ligne par
#
- Sauver le fichier
Exemples de commandes:
La version script run.sh
sera:
#!/bin/sh
#aller dans le répertoire
cd mon_dir
#on invoque ici blabla
prog1
#puis on fait blabla
prog2
cd ..
L'avantage est de mieux documenter vos commandes, de simplifier l'utilisation du shell et d'éviter les fautes de frappes en retapant la même commande à longueur de temps.
Info
Il est possible de lancer un script n'ayant pas les droits d’exécution en invoquant le shell désiré, par exemple bash script.sh
Vous pouvez aussi utiliser la notion de lancement en arrière-plan vu précédemment dans vos scripts.
Par exemple, au lieu de lancer prog1
dans un terminal et prog2
dans un deuxième terminal, faites simplement un script: