Niveau 4 : Environnement de Travail Avancé

Introduction

Le but de cette séance de TP est de vous familiariser avec la notion de processus, avec les mécanismes de redirection d'entrée/sorties de commandes et la programmation shell.

man bash

Comme toutes les commandes unix, le shell bash dispose d’une documentation en ligne accessible par la commande man. C'est dans le manuel de bash qu'est décrit le fonctionnement des commandes internes au shell telle que la commande cd ou l'affectation d'une variable.

Notion de processus

En première approche, un processus est une occurrence d'un programme en cours d'exécution. Les processus ont en quelque sorte une vie ; ils naissent (lorsque vous lancez un programme ou tapez une commande), ils vivent (lorsqu'ils s'exécutent) et meurent (lorsqu'ils ont terminé leur exécution). A leur naissance, chaque processus se voit attribuer par le système un PID unique (process identifier), il s'agit d'un entier identifiant le processus. Ainsi, à un instant donné, tous les processus vivants ont un PID différent, qui permet de les identifier sans ambiguité. Enfin, lorsqu'un processus meurt, son pid redevient disponible et pourra être réattribué par le système à un nouveau processus.

Un job correspond à l'exécution d'une ligne de commande sur un shell. Cette ligne de commande est exécutée par un ensemble de processus que l'on peut contrôler en tant que groupe.

Gestion des processus

jobs   Lister les tâches lancées sur un terminal
bg     Relancer en arrière plan une tâche stoppée
fg     Mettre en avant plan une tâche
ps     Lister les processus occupant la machine
top    Observer et gérer les processus occupant la machine
kill   Envoyer un signal à un processus ou à un groupe de processus
  • Tapez la commande xeyes. Avez-vous toujours le contrôle de votre terminal ?
  • Testez maintenant la commande Ctrl-z dans votre terminal. Que se passe-t-il ?
  • Testez la commande jobs. Qu'indique-t-elle ?
  • Testez la commande fg.
  • Interrompez à nouveau le processus avec Ctrl-z. Testez la commande bg. Que se passe-t-il ? Avez-vous toujours le contrôle de votre terminal ?
  • Lancez à nouveau la commande xeyes. Utilisez Ctrl-c. Que se passe-t-il ?
  • Lancez à nouveau la commande xeyes, puis Ctrl-z, puis jobs.
  • Que provoque la commande kill -9 %n, avec n le numéro d'un job ?
  • Testez la commande ps. Avez-vous trouvé les PIDs de vos processus ?

Pour tuer un processus, on aurait pu aussi utiliser killall -9 xeyes ou bien kill -9 pid-du-processus ou encore l'interface proposée par la commande top.

Les Mécanismes de Redirection

Sous Unix, les processus peuvent lire et écrire sur la console. Techniquement cela se fait au moyen de fichiers. Par défaut un processus lancé depuis la console exploite trois fichiers : un fichier pour lire les caractères écrits depuis le clavier sur la console, un fichier pour écrire les affichages demandés par le processus sur la console et un fichier pour écrire sur la console les messages d'erreurs rapportés par le processus. Ces trois fichiers sont appelés entrée standard, sortie standard, et sortie d'erreur standard du processus.

Il est possible de modifier, de rediriger, l'entrée et les sorties standards d'un processus :

cmd < file   # 'cmd' utilise le contenu de 'file' comme entrée standard
cmd > file   # Écrit la sortie de ’cmd’ en écrasant le fichier ’file’
cmd >> file  # Écrit la sortie de ’cmd’ en l’ajoutant à la suite du fichier ’file’
cmd 2> file  # Écrit la sortie d’erreur de ’cmd’ dans ’file’
cmd &> file  # Écrit la sortie standard et d’erreur de ’cmd’ dans ’file’
  • Exécutez la commande ls depuis votre répertoire utilisateur.
  • Réexécutez la commande en redirigeant cette fois-ci la sortie standard dans un fichier dont le nom est output. Pour cela, utilisez la commande : ls > output. Affichez le contenu du fichier output.
  • Exécuter la commande ls 123456789 > output. Consulter le fichier output. Recommencez en entrant ls 123456789 2> output.
  • Tapez la commande : echo "hello world" > test. Afficher le contenu du fichier test grâce à la commande cat.
  • Tapez la commande cat > texte.txt puis entrez votre poême favoris. Terminez l'écriture en tapant Ctrl-D qui sera interprété comme un caractère signalant la fin du fichier. Afficher le contenu du fichier.

Enchaînement des Processus

cmd1 ; cmd2   # Exécute cmd1 puis cmd2
cmd1 && cmd2  # Exécute cmd1 puis cmd2 si cmd1 réussit
cmd1 || cmd2  # Exécute cmd1 puis cmd2 si cmd1 échoue
cmd1 & cmd2   # Exécute cmd1 et cmd2 en parallèle
cmd1 | cmd2   # Exécute cmd1 et cmd2 en parallèle en redirigeant la sortie standard de cmd1 vers l'entrée standard de cmd2
  • Lancez la commande ls -l > output ; echo fin >> output ; cat -n < output. Expliquez.
  • Lancez 2 commandes parmi true && echo ok , false && echo ok , true || echo ok ,false || echo ok

Utilisation de tubes de communication

  • Lancez la commande ls -l /usr/bin > output ; cat -n output ; rm output

Si on regarde attentivement ligne de commande, on se rend compte que :

  1. ls -l /usr/bin > output écrit dans le fichier output par redirection de la sortie standard la liste des fichiers du répertoire /usr/bin ;
  2. cat -n lit dans le fichier output et affiche le résultat en numérotant les ligne ;
  3. output sert de fichier temporaire et peut être supprimé à l'issue du cat.

Il est possible de faire bien mieux car Unix met à votre disposition un mécanisme particulier appelé tube (pipe en anglais). C'est un opérateur permettant de connecter directement la sortie standard d'un premier programme à l'entrée standard d'un second sans devoir spécifier de fichier temporaire tout en les exécutant en parallèle. L'opérateur de pipe est |.

Exemple : Reprenons l'exemple précédent, et réécrivons-le en utilisant cette fois-ci un pipe :

ls -l /bin | cat -n

Le processus exécutant ls -l /bin envoie son résultat sur l'entrée standard du processus cat -n, ainsi, il n'est plus nécessaire d'utiliser un fichier temporaire comme dans l'exemple précédent (fichier output). Un tube offre un mécanisme de communication puissant qui permet d'échanger des informations entre processus. Il faut bien comprendre que, contrairement à l'enchaînement séquentiel des processus, lors de l'exécution de commande-1 | commande-2, deux processus sont créés puis s'exécutent simultanément en se synchronisant.

                   ---------------------------
 Processus 1 ---> (           TUBE           () ---> Processus 2
                   ---------------------------
  (Ecriture)                                          (Lecture)

Deux nouveaux exemples de pipe très utiles :

ps auxf | more             # affiche page par page
ps auxf | grep bash        # recherche "bash"

Exercices

Les questions suivantes vont vous permettre d'utiliser les mécanismes de redirection et d'enchaînement des processus (<, >, |, >>, ...) avec quelques commandes Unix simples (cat, wc, tail, head). Bien sûr, dans cette partie, il n'est pas nécessaire d'utiliser un éditeur de texte.

  • Créer une copie du fichier /etc/services dans /tmp à l'aide de cat -n afin d'en numéroter les lignes puis placez-vous dans ce répertoire.
  • Combien ce fichier comporte-t-il de lignes ? De mots ? De caractères ?
  • Affichez les 50 premières lignes du fichier /tmp/services.
  • Affichez les lignes 40 à 50 du fichier /tmp/services. Pour cela, il faut combiner head et tail avec un tube.
  • Combien de mots y-a-t-il entre les lignes 40 et 50 ? On utilisera des tubes.

Variables et Substitution

Le shell permet de définir des variables pour conserver certaines informations. Quelques variables au nom particulier sont utilisées par le système mais vous pouvez définir vos propres variables.

  • Exécutez la commande env pour afficher l'ensemble des variables définies dans l'environnement courant.

Une variable est définie par la syntaxe NOM=VALEUR (sans espaces !). La valeur est considérée comme une chaîne de caractère même s'il s'agit d'une succession de chiffres. La valeur d'une variable est accessible par la syntaxe $NOM. Vous pouvez afficher cette valeur à l'écran en utilisant la commande echo.

  • Définissez une variable de nom UN et de valeur toto. Comparez les commandes :
echo UN
echo $UN

Evaluation et Substitution

Il est important que vous vous familiarisiez avec les divers mécanismes d'évaluation du shell. Le caractère $ utilisé devant un nom de variable permet de substituer le contenu de la variable à son nom. Lors de l'évaluation d'une commande, une première opération de substitution a lieu avant l'exécution de la commande substituée.

Plusieurs caractères permettent de contrôler la substitution :

  • les parties d'une commande situées entre apostrophes ' ' ne sont pas substituées ;
  • la construction $( ) provoquent l'exécution d'une partie de la commande dans un sous-shell et le résultat est substitué dans la commande ;
  • de même les parties d'une commande situées entre accents graves ` ` sont exécutée et le résultat est substitué dans la commande ;
  • les parties d'une commande situées entre guillemets anglais " " sont substituées normalement ;
  • des accolades { } permettent de délimiter un nom de variable ;
  • le caractère \ empêche la substitution.

Quelques exemples pour vous aider à comprendre :

echo $HOME                # /net/cremi/auesnard
echo \$HOME               # $HOME
echo '$HOME'              # $HOME
x=pwd ; echo $x           # pwd
x=`pwd` ; echo $x         # /net/cremi/auesnard
x=toto tutu ; echo $x     # Erreur, utilisez les ".."
x="toto tutu" ; echo $x   # toto tutu
  • La commande exprpermet d'interpréter les variables comme des valeurs numériques et de réaliser des calculs simples. Que fait la commande suivante ?
i=99 ; j=$(expr $i + 1) ; echo "i=$i j=$j"
  • Sachant que la commande date +%H:%M:%S affiche l'heure à la seconde près, créez une variable dont le contenu est la concaténation de la chaine "il était " suivit de la chaine contenant l'heure où la variable a été affectée.

Programmation Shell : les structures de contrôles

boucles for

La boucle la plus utilisée en shell est un itérateur sur une liste d’éléments. Voici un exemple codé sur une ligne :

for i in 1 2 3 4 5 ; do echo "Welcome $i times." ; done

Attention aux point-virgules ! Dans un fichier on écrira plutôt :

for i in 1 2 3 4 5
do
   echo "Welcome $i times."
done

On peut utiliser le mécanisme de substitution (*,?,...) pour fabriquer la liste d'éléments sur laquelle on veut itérer :

for i in $(seq 1 5)
do
   echo "Welcome $i times."
done

Les tests

La commande test permet de calculer la valeur de vérité d'une expression. Cependant on préfère utiliser les syntaxes suivantes :

[ EXPR ]    # Retourne la valeur de vérité de EXPR
[[ EXPR ]]  # Étends ’==’ et ’ !=’ sur les chaînes de caractères et les expressions régulières
(( EXPR ))  # Évaluation arithmétique, retourne le résultat de EXPR==1
  • Consultez le manuel de test et modifiez XXX dans la commande suivante pour ne lister que les répertoires :
for file in * ; do [ XXX "$file" ] && echo "$file est un répertoire" ; done

Les conditionnelles

Voici un exemple complet :

if [[ $var -gt 0 ]] && [[ $var -lt 5 ]]; then
   echo "var is between 0 and 5."
elif [[ $var -lt 0 ]]; then
   echo "$var is lesser than 0."
else
   echo "$var is greater than 5."
fi

Notons qu'il existe aussi une construction switch / case.

La boucle conditionnelle

i=1 # initialize i
while [[ $i -le 5 ]]; do
  echo "Welcome $i times."
  i=$(( i+1 ))
done

Il est possible d'utiliser tout exécutable à la place de la commande test. En effet le shell exploite le code de retour de la commande placée derrière le while pour faire son test. Astuce pour exécuter un programme '''./a.out''' en boucle jusqu'à ce qu'il plante :

i=1
while ./a.out > output ; do
  echo -ne "\r Executed $i times."
  i=$(( i+1 ))
done

Si le programme ./a.out se met à boucler, on peut alors utiliser l'option -p de gdb pour intercepter le processus.

Les scripts shell et le sha-bang

Introduction

Un script est avant tout un fichier texte, lisible par un humain, qui contient des commandes qui seront exécutées par un intepréteur. Exécuter un script shell peut se faire de deux manières différentes :

  • La première, la plus simple, consiste à invoquer l’interpréteur de script en lui passant en paramètre le fichier contenant le script. Par exemple : /bin/bash myscript.sh, où /bin/bash est l’interpréteur et myscript.sh est le script.
  • La deuxième méthode est de rendre le script exécutable à l'aide d'un chmod +x et d’indiquer sur la première ligne du script l’interpréteur à utiliser pour l’exécuter. Par convention, on débute la ligne qui pointe l’interpréteur avec ce que l’on appelle communément un “sha-bang” #!’ contraction en anglais de sharp (#) et de bang (!).
#!/bin/bash
echo 'Hello World!'

Paramètres positionnels

Ces paramètres sont utilisés pour les scripts et pour les fonctions

$#    # nombre de paramètres
$0    # nom du script ou de la fonction
$1    # 1er paramètre
$2    # 2ème paramètre
      # ...
$9    # 9ième paramètre
$*    # tous les paramètres
shift # supprimer le 1er paramètre et décaler les arguments ($1 prend la valeur de $2, $2 celle de $3,...)

Exemples :

#!/bin/bash

if [[ "$1" -gt "$2" ]] ;
then
    echo "La valeur $1 est supérieure à $2."
else
    echo "La valeur $1 est inférieure égale à $2"
fi

La commande shift permet d'itérer sur la liste de paramètres.

#!/bin/bash

while [ $# -gt 0 ] ;
do
    echo "$1"
    shift
done

Fonctions

Pour déclarer une fonction on utilise la syntaxe suivante :

nom_de_la_fonction() {

# commande 1
# commande 2
...
}

Pour appeler une fonction il suffit de mettre le nom de la fonctionpui ses paramètres :

nom_de_la_fonction arg1 arg2 ... argk

Exemple :

#!/bin/bash

print_args() {
  while [[ $# -gt 0 ]] ;
  do
    echo "$1"
    shift
  done
}

print_args 3 2 1
print_args "$*"
print_args $*

Pour aller plus loin

Quelques Commandes du Shell

Le shell dispose d'un ensemble très complet de commandes pour la manipulation des fichiers et des processus. Certaines commandes sont internes aux shell. Par exemple, les commandes cd, echo et pwd que vous avez utilisées sont des commandes internes. D'autres commandes sont des programmes exécutables accessibles depuis le shell. Nous allons d'abord explorer les principales commandes internes.

  • Consultez l'aide en ligne. En particulier, regardez les commandes internes au shell (ou builtin commands).
  • Que signifie la commande cd - ?
  • La commande exit permet de quitter le shell. Essayez.
  • Testez la commande history. Rappelez une commande quelconque de l'historique. La commande !numéro relance la commande de numéro donné dans l'historique.
  • Utilisez la commande which. Testez avec les paramètres ls et cd. S'il n'y a pas de réponse, c'est qu'il s'agit d'une commande interne.

Fichiers de configuration

bash utilise des fichiers de configuration, qui se trouvent dans votre répertoire utilisateur.

  • .bash_login : les commandes de ce fichier sont exécutées lorsque vous vous connectez.
  • .bash_logout : les commandes de ce fichier sont exécutées lorsque vous vous déconnectez.
  • .bashrc : on y place les initialisations des sessions interactives (rattachées à un terminal).

L’ensemble de ces fichiers sont exécutés par le shell s’ils se trouvent dans votre répertoire racine. Cela signifie que vous pouvez en modifier le contenu afin de personnaliser votre environnement de travail.

Variables exportée

En fait, il existe deux types de variables : les variables d'environnement ou variables exportées, et celles qui ne le sont pas. La valeur d'une variable d'environnement est accessible par tous les processus issus du shell dans lequel la variable a été définie. En revanche, la valeur d'une variable non exportée est uniquement accessible dans le shell où elle a été définie.

Les exercices suivants vont mettre en évidence ce comportement. Tout d'abord, notez que pour exporter une variable, il suffit d'exécuter la commande export suivie du nom de la variable à exporter :

x=toto ; export x

ou encore :

export x=toto
  • Définissez une seconde variable de nom DEUX et de même valeur que UN. Exportez la variable DEUX. Affichez les valeurs de UN et DEUX dans le shell courant.
  • Exécutez un nouveau shell en tapant la commande xterm. Affichez la valeur des deux variables. Que remarquez-vous ?
  • La commande unset permet de supprimer une variable. Faire unset sur la variable PATH de votre environnement. Essayez maintenant de lancer la commande ls. Que remarquez-vous ? Lancez maintenant /bin/ls. Pour réinitialisez son environnement, vous pouvez utiliser la commande source : source ~/.bashrc.

La Variable PATH

D'une façon générale, les commandes que vous exécutez sont des programmes placés dans des fichiers de l'arborescence. Comment l'interprète de commandes trouve-t-il le répertoire où se trouve le programme à exécuter ? Il consulte une variable d'environnement, PATH, qui représente une liste de répertoires séparés par le symbole :. Vous pouvez voir la valeur de votre variable PATH en tapant : echo $PATH.

Quand vous tapez une commande, l'interprète de commandes effectue une recherche dans chacun des répertoires cités dans la variable PATH. S'il la trouve, il l'exécute, sinon il renvoie le message command not found. Pour des raisons de sécurité, il n'est pas bon que la variable PATH contienne le répertoire courant (i.e. .). Pour exécuter un programme dans le répertoire courant, vous taperez :

$ ./nom_du_programme

Alias

Il est possible de définir des alias sous Unix permettant de personnaliser vos appels de commandes les plus utilisées. Les commandes à utiliser ici sont alias et unalias.

Par exemple :

alias ll='ls -l'
alias ls='ls --color=auto'

En utilisant les commandes alias et unalias, répondez aux questions suivantes :

  • Affichez la liste des alias définis dans le shell courant.
  • Définissez un alias date sur la commande ls et exécutez la commande date.
  • La commande date originale n'est plus disponible. Trouvez un moyen de l'appeler.
  • Détruisez cet alias.

Commandes Externes Classiques

Les commandes externes sont des programmes exécutables qui peuvent être disponibles ou pas suivant l'installation du système que vous utilisez. Certains programmes sont néanmoins tellement utilisés qu'ils sont devenus indissociables du système. Les programmes ls, cp et mv sont des exemples de commandes externes classiques. Nous allons découvrir d'autres commandes très pratiques.

  • Testez les commandes suivantes : times, date, whoami, who, w, id, hostname, uname -a, df, uptime, free. A quoi servent-elles ?
  • Les commandes cat, more, less, sont utilisées pour visualiser le contenu d'un fichier texte. Essayez ces commandes avec pour paramètre /proc/cpuinfo et /proc/meminfo. Quel infos contiennent ces fichiers ?
  • Etudiez les options de ces commandes. Affichez les cinq premières lignes et les cinq dernières lignes de ~/.bashrc.
  • La commande "expr" permet d'interpréter les variables comme des valeurs numériques et de réaliser des calculs simples.

Par exemple :

i=99 ; j=`expr $i + 1` ; echo "i=$i j=$j"

Il est également possible de réaliser des tests logiques entre la valeur de deux variables. Utilisez expr pour déterminer si i < j.

  • La commande grep permet d'isoler une ligne contenant une expression particulière. Essayez la commande : grep "udp" /etc/services
  • Créez le fichier suivant avec la commande cat (cf. TP1).
d
b
a
c
b
d

En utilisant les commandes sort et uniq, triez ce fichier par ordre alphabétique sans doublon et écrivez le résultat dans un nouveau fichier.

  • La commande find permet de rechercher un fichier à partir d'un point de l'arborescence. Ainsi :
find $HOME -name "*.txt"

permet de retrouver tous les fichier nommés "*.txt" à partir du répertoire $HOME et dans ses sous-répertoires.

Ecrivez une commande pour déterminer l'emplacement des fichiers débutant par le préfixe "pass" sur votre système (/).