Programmation shell sous GNU/Linux

Bash Shell

Qu'est ce que le shell sous GNU/Linux ?

Le shell est à Linux ce que MS-DOS est à Windows.

En effet, sur un système GNU/Linux, il est très important de connaitre les bases du shell.
Même si les interfaces graphique sont là pour simplifier les opérations, il est parfois nécessaire, voir indispensable, de savoir utiliser des commandes GNU/Linux.

Parfois, il n'y a pas toujours de programmes avec de belles interfaces graphiques pour faire ce que nous souhaitons.
Parfois, il est plus simple et plus rapide de faire telle ou telle action via les lignes de commandes.

Rechercher des fichiers, créer des répertoires, rechercher du texte dans plusieurs fichiers, renommer en masse tout un ensemble de fichier selon certains critères, se déplacer au sein de l'arborescence GNU/Linux etc etc ...

Et enfin, le plus important, écrire ses propres scripts afin d'automatiser certaines actions répétitives.

Extraits du livre "Programmation Shell sous Unix / Linux" aux éditions ENI que je conseil très fortement de se procurer.

Programmation shell sous Linux

Les mécanismes du shell

Commandes internes et externes

Les commandes externes

Une commande externe est un fichier présent dans l'arborescence.

Quand un utilisateur exécute la commande ls, le shell demande au noyau Linux d'exécuter le fichier /bin/ls

Sont considérés comme commandes externes, tous les fichiers au format binaire exécutable ainsi que tous les fichiers au format texte représentant un script de commandes.

La commande file indique le type de données contenues dans un fichier.

$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped

$ file monscript
monscript: POSIX shell script text executable

L'argument de la commande file est un nom de fichier indiqué en relatif ou en absolu

Etiquettes: 

Les commandes internes

Une commande interne est intégrée au processus shell.

Elle n'a aucune correspondance avec un fichier sur le disque.

La commande type indique si une commande est interne ou externe.

$ type cd
cd est une primitive du shell

$ type ls
ls est un alias vers « ls --color=auto »

La commande type prend en argument le nom d'une commande. Si cette dernière n'est pas une commande interne, elle est recherchée dans les répertoires indiqués dans la variable PATH

Etiquettes: 

Les commandes internes et externes

Certaines commandes ont une implémentation interne et externe.

  • La commande interne est exécutée en priorité.
  • L'exécution d'une commande interne est plus rapide.
  • La commande type, indique que la commande est interne mais ne précise pas qu'elle peut également être une commande externe.

La commande pwd est une commande interne :

$ type pwd
pwd est une primitive du shell

La commande pwd possède également une implémentation externe :

$ ls -l /bin/pwd
-rwxr-xr-x 1 root root 34376 2010-09-21 20:33 /bin/pwd

Pour forcer l'utilisation de la commande externe, il faut indiquer l'emplacement de la commande :

$ /bin/pwd
/home
$ cd /bin
$ ./pwd
/bin

Affichage à l'écran

La commande interne echo permet d'afficher des données à l'écran.

$ echo Ceci est un exemple
Ceci est un exemple

Il faut échapper l'apostrophe avec l'antislash

$ echo Un exemple avec l\'apostrophe
Un exemple avec l'apostrophe

Il faut échapper les guillemets avec l'antislash

$ echo Un exemple avec des \"guillemets\"
Un exemple avec des "guillemets"

En bash, il faut obligatoirement utilisé l'option "-e" pour utiliser les caractères spéciaux suivants.

  • Le caractère "\n" sert à provoquer un saut de ligne :

$ echo -e "Un exemple\navec un saut de ligne"
Un exemple
avec un saut de ligne

  • Le caractère "\c" supprime le saut de ligne naturel de la commande echo :

Sans le caractère "\c"

$ echo -e "Une ligne" ; echo -e "Une autre ligne"
Une ligne
Une autre ligne

Avec le caractère "\c"

$ echo -e "Une ligne\c" ; echo -e "Une autre ligne"
Une ligneUne autre ligne

L'option -n remplace le caractère "\c"

$ echo -n "Une ligne" ; echo "Une autre ligne"
Une ligneUne autre ligne

  • Le caractère "\t" sert à afficher une tabulation :

$ echo -e "Voici 1 tabulation\tet la suite"
Voici 1 tabulation      et la suite

  • Le caractère "\\" permet d'afficher un antislash :

$ echo -e "Afficher un antislash\\"
Afficher un antislash\

Liste des caractères spéciaux :

\0NNN Valeur d'un caractère exprimé en octal
\\ Antislash
\a Bip
\b Effacement du caractère précédent
\c Suppression du saut de ligne en fin de ligne
\f Saut de page
\n Saut de ligne
\r Retour chariot
\t Tabulation horizontale
\v Tabulation verticale

 

Le caractère ~ (tilde)

Le caractère "~" représente le répertoire HOME de l'utilisateur.

Par exemple, pour l'utilisateur "toto"

$ cd ~
$ pwd
/home/toto

Copier le fichier "fichier1" du répertoire /tmp dans le répertoire /home/toto

$ cd /tmp
$ cp fichier1 ~

Copier le fichier "fichier2" du répertoire /tmp dans le répertoire /home/toto/mesfichiers

$ cd /tmp
$ cp fichier2 ~/mesfichiers

Si le caractère "~" est immédiatement suivi d'un mot, ce dernier est considéré comme un nom d'utilisateur.

Copier le fichier "fichier3" du répertoire /home/toto vers le répertoire /home/titi

$ cd ~
$ pwd
/home/toto
$ cp fichier3 ~titi

Copier le fichier "fichier4" du répertoire /home/toto vers le répertoire /home/titi/tmp

$ cd ~
$ pwd
/home/toto
$ cp fichier4 ~titi/tmp

 

La commande interne cd

La commande cd, sans arguments, permet à l'utilisateur de se placer dans son répertoire HOME.

$ pwd
/tmp
$ cd
$ pwd
/home/toto

Identique en utilisant le caractère "~"

$ cd ~

Pour se rendre dans le répertoire HOME de tata :

$ pwd
/home/toto
$ cd ~tata
$ pwd
/home/tata

Pour revenir au répertoire précédent avec la commande "cd -" :

$ cd -
$ pwd
/home/toto

Remplacement de noms de fichiers

Expressions simples

Attention, certaines de ces expressions ne fonctionnent pas toutes avec les mêmes shell.
Pour certaines, elles sont compatibles BASH, pour d'autres ZSH et certaines ne fonctionnent qu'avec KSH.

Le caractère "*" permet de remplacer n'importe quel caractère.

$ ls
fichier1 fichier2.a monfichier tOnfichier.b

  • Afficher uniquement les fichiers se terminant par ".a" :

$ ls *.a
fichier2.a

  • Afficher uniquement les fichiers commançant par "f" :

$ ls f*
fichier1 fichier2.a

Le caractère "?" représente un caractère quelconque.

  • Afficher uniquement les fichiers ayant une extension composée d'un seul caractère :

$ ls *.?
fichier2.a tOnfichier.b

  • Afficher uniquement les fichiers composés de 8 caractères :

$ ls ????????
fichier1

Les caractères "[ ]" permettent d'indiquer la liste des caractères que l'on recherche à une position bien précise dans le noms des fichiers.

  • Afficher les fichiers commençant par la lettre "f" ou "t" et se terminant
    par le "." suivi d'une lettre minuscule :

$ ls [ft]*.[a-z]
fichier2.a tOnfichier.b

  • Afficher tous les fichiers ayant en deuxième caractère une lettre majuscule, ou un chiffre,
    ou la lettre "e" :

% ls ?[A-Z0-9e]*
cOucou  f1chier  F2chier  Hello

  • Afficher tous les fichiers ne commançant pas par une lettre minuscule :

$ ls [!a-z]*
1coucou  Coucou  F2chier  Fichier  Hello

  • Supprimer tous les fichiers se terminant par .b ou .c :

% rm -i *.b *.c
rm : supprimer fichier vide «fichier1.b» ? y
rm : supprimer fichier vide «fichier1.c» ? y

 

Expressions complexes

Les expressions complexes sont compatible BASH à condition d'activer l'option extglob avec la commande shopt (shopt -s extglob).

$ ls
1coucou   coucou.t    fichier159159159.log  fichier161.log  fichier.log
cOucou    coucou.uvw  fichier159159.log     fichier1.a      Hello.txt
Coucou    f1chier     fichier159160.log     fichier1.abc
coucou.r  F2chier     fichier159.log        fichier1.b
coucou.s  Fichier     fichier160.log        fichier1.c

  • ?(expression)

L'expression sera présente 0 ou 1 fois.

Afficher tous les fichiers dont le nom contient 0 ou 1 fois "159"

$ ls fichier?(159).log
fichier159.log  fichier.log

  • *(expression)

L'expression sera présente entre 0 et x fois.

Afficher tous les fichiers dont le nom contient 0 ou x fois "159"

$ ls fichier*(159).log
fichier159159159.log  fichier159159.log  fichier159.log  fichier.log

  • +(expression)

L'expression sera présente entre 1 et x fois.

Afficher tous les fichiers dont le nom contient 1 ou x fois "159"

$ ls fichier+(159).log
fichier159159159.log  fichier159159.log  fichier159.log

  • @(expression)

L'expression sera présente exactement 1 fois.

Afficher tous les fichiers dont le nom contient 1 fois "159"

$ ls fichier@(159).log
fichier159.log

  • !(expression)

L'expression ne sera pas présente.

Afficher tous les fichiers dont le nom ne contient pas 1 fois "159"

$ ls fichier!(159).log
fichier159159159.log  fichier159160.log  fichier161.log
fichier159159.log     fichier160.log     fichier.log

Afficher tous les fichiers dont le nom ne contient pas "fichier"

$ ls !(fichier*)
1coucou  Coucou    coucou.s  coucou.uvw  F2chier  Hello.txt
cOucou   coucou.r  coucou.t  f1chier     Fichier

Une barre verticale dans une expression correspond à "ou bien"

Afficher tous les fichiers dont le nom contient 1 fois "159" ou "160"

$ ls fichier@(159|160).log
fichier159.log  fichier160.log

Afficher tous les fichiers dont le nom contient 1 ou x fois "159" ou "161"

$ ls fichier+(159|161).log
fichier159159159.log  fichier159159.log  fichier159.log  fichier161.log

Afficher tous les fichiers dont le nom contient 1 fois ( 1 ou x fois "159" ou "161")

$ ls fichier@(+(159)|+(161)).log
fichier159159159.log  fichier159159.log  fichier159.log  fichier161.log

Séparateur de commandes

Le caractère ";" permet d'écrire plusieurs commandes sur une même ligne. Toutes les commandes sont exécutées dans l'ordre.

$ pwd
/home/toto
$ ls
1coucou   coucou.t    fichier159159159.log  fichier161.log  fichier.log
cOucou    coucou.uvw  fichier159159.log     fichier1.a      Hello.txt
Coucou    f1chier     fichier159160.log     fichier1.abc
coucou.r  F2chier     fichier159.log        fichier1.b
coucou.s  Fichier     fichier160.log        fichier1.c
$ mkdir tmp; chmod 755 tmp; chown toto:toto tmp; cd tmp; pwd
/home/toto/tmp

Redirections

Les redirections sont souvent utilisées dans les commandes Linux.
Elles permettent de récupérer le résultat d'une ou plusieurs commandes dans un ou plusieurs fichiers ou de faire lire le contenu d'un fichier à une commande.

Entrée et sorties standard des commandes

Les commandes Linux ont par défaut 3 descripteurs de fichier différent.

  • Entrée standard :

L'entrée standard d'une commande correspond au descripteur de fichier 0.
Les commandes qui attendent des informations de la part de l'utilisateur déclenche une requête de lecture sur le descripteur 0.
Par défaut, ce descripteur est associé au terminal et donc par une saisie au clavier.

  • Sortie standard :

La sortie standard d'une commande correspond au descripteur de fichier 1.
Le résultat d'une commande est, par défaut, affichée à l'écran via ce descripteur mais il peut être redirigé dans un fichier.

  • Sortie d'erreur standard :

La sortie d'erreur standard d'une commande correspond au descripteur de fichier 2.
Quand une commande rencontre une erreur, celle-ci est retournée à l'écran via le descripteur 2 mais elle peut également être retournée dans un fichier.

Redirection des sorties vers un fichier

Cette redirection permet d'écrire dans un fichier le résultat d'une commande au lieu de l'afficher à l'écran.

  • Sortie standard (descripteur de fichier 1)

Simple redirection

$ commande > fichier
ou
$ commande 1> fichier

Si le fichier n'existe pas, il est automatiquement créé. S'il existe déjà, il est tout simplement écrasé.

Récupérer le résultat de la commande ls dans un fichier "liste"

$ ls > liste
$ cat liste
1coucou
cOucou
Coucou

Double redirection

Elle permet de concaténer le résultat d'une commande au contenu d'un fichier déjà existant.

$ commande >> fichier
ou
$ commande 1>> fichier

Si le fichier n'existe pas, il est automatiquement créé. S'il existe déjà, le résultat de la commande est ajouté au fichier.

$ pwd >> liste
$ cat liste
1coucou
cOucou
Coucou
/home/toto

  • Sortie d'erreur standard (descripteur de fichier 2)

Simple redirection

$ commande 2> fichier

$ ls /root 2> erreur
$ cat erreur
ls: impoossible d'ouvrir le répertoire /root: Permission non accordée

Double redirection

$ commande 2>> fichier

$ mkdir /root/tmp 2>> erreur
$ cat erreur
ls: impoossible d'ouvrir le répertoire /root: Permission non accordée
mkdir: impossible de créer le répertoire «/root/tmp»: Permission non accordée

  • Sortie standard et sortie d'erreur standard

Il est possible de rediriger plusieurs sorties sur une même ligne de commande.

$ commande 1> fichier1 2> fichier2
ou
$ commande 2> fichier2 1> fichier1

$ find /etc -name smb.conf 1> resultat 2> erreur
$ cat resultat
/etc/samba/smb.conf
$ cat erreur
find: "/etc/lvm/cache": Permission non accordée
find: "/etc/lvm/backup": Permission non accordée
find: "/etc/lvm/archive": Permission non accordée

Si l'on ne souhaite pas afficher et/ou enregistrer les erreurs retournées, il est possible de rediriger le descripteur 2 vers un fichier spécial existant sur tous les systèmes Linux "/dev/null"

$ find /etc -name smb.conf 1> resultat 2> /dev/null
$ cat resultat
/etc/samba/smb.conf
$ cat /dev/null
$
 

Redirection de l'entrée standard

La redirection de l'entrée standard concerne toutes les commandes attendant une saisie de la part de l'utilisateur sur le descripteur 0 (saisie écran).

$ mail toto
>Coucou
>Comment vas tu ?
>^d (équivalent de CTRL+d)
$

La commande mail lit sur l'entrée standard toutes les données saisies à l'écran. La saisie se termine par la fonction CTRL+d.
Ensuite, la commande mail envoie ces données dans un message à l'utilisateur indiqué (toto).

Il est tout à fait possible d'écrire le contenu du message dans un fichier et de "l'injecter" à la commande mail sur son descripteur 0.

$ commande 0< fichier
ou
$ commande < fichier

$ echo "Coucou, comment vas tu ?" > message
$ cat message
Coucou, comment vas tu ?
$ mail toto < message

 

Redirections avancées

Rediriger les descripteurs 1 et 2 vers le même fichier :

$ commande 1> fichier1 2>&1
ou
$ commande 2> fichier2 1>&2

Le principe consiste à écrire dans un fichier le résulat du descripteur 1 dans le fichier "fichier1" (1> fichier1) puis le résultat du descripteur 2 vers le descripteur 1 (2>&1) et par conséquent dans le fichier "fichier1".

$ find /etc -name smb.conf 1> resultat 2>&1
$ cat resultat
find: "/etc/lvm/cache": Permission non accordée
find: "/etc/lvm/backup": Permission non accordée
find: "/etc/lvm/archive": Permission non accordée
/etc/samba/smb.conf

Attention :

$ commande 2>&1 1> fichier1

Cette syntaxe n'agit pas de la même manière que les précédentes.

En effet, nous indiquons dans un premier temps de rediriger le descripteur 2 (sortie d'erreur standard) vers le descripteur 1, c'est à dire vers la sortie standard qui est, par défaut, l'affichage à l'écran, puis dans un second temps nous redirigeons le descripteur 1 (sortie standard) vers le fichier "fichier1".
En conclusion, tous les messages d'erreurs seront affichés à l'écran et le résulat de la commande dans le fichier "fichier1".

Cela revient à saisir :

$ commande 1> fichier1

La double redirection en lecture :

Elle est principalement utilisée dans les scripts shell.
Elle permet de connecter l'entrée standard d'une commande sur une portion du script

Par exemple, avec la commande mail, cela permet de terminer la saisie du texte avec un caractère ou une chaine de caractère.

$ mail toto<<end
> bonjour
> ceci est un test
> end
$

Pour mettre fin à la commande mail, il suffit simplement de saisir l'expression inscrite juste après les doubles chevrons "<<". Dans l'exemple, la commande se termine après avoir saisi "end".
Identique à la fonction ^d (CTRL+d) en console.

Utilisation dans un script :

$ cat envoiMail.sh
#!/bin/sh
mail toto<<end
Bonjour,
Ceci est un test
Voici la liste des fichiers presents dans ton repertoire :
`ls -lhtr`
end
exit 0

Exécution du script

$ sh ./envoiMail.sh

Fermeture d'un descripteur :

Il est tout à fait possible de fermé un descripteur d'entrée ou de sortie en utilisant les symboles "<&-" ou ">&-" ou "2>&-".

Descripteur d'entrée standard "0" :

$ commande <&-

Descripteur de sortie standard "1" :

$ commande >&-

Descripteur de sortie d'erreur standard "2" :

$ commande 2>&-

Tubes de communications

Un tube de communication, ou pipe en anglais, permet de faire communiquer 2 commandes.
Ce "tube" est représenté par la barre verticale "|".
Le résultat de la commande de gauche est envoyé dans le tube et récupéré par la commande de droite.
C'est à dirte que la sortie standard "1" de la commande de gauche est connecté directement à l'entrée standard "0" de la commande de droite.
La sortie d'erreur standard "2" de la commande de gauche n'est pas envoyée dans le tube.

Pour que cette utilisation est un sens il faut impérativement que la commande de gauche utilise la sortie standard "1" et que la commande de droite utilise l'entrée standard "0".

Les commandes lisant leur entrée standard sont facilement identifiable car elles demandent une saisie au clavier.

Envoyer par mail la liste des users connectés :

$ who | mail toto

Envoyer par mail la liste des fichiers d'un répertoire :

$ ls -lht | mail toto

Envoyer un message à un utilisateur connecté au système :

$ echo "coucou, comment vas tu ?" | write tata

Afficher le nombre de fichiers d'un répertoire :

$ ls | wc -l
29

Envoyer par mail le nombre de fichiers d'un répertoire :

$ ls | wc -l | mail toto

Afficher avec pagination le contenu d'un fichier :

$ cat /etc/passwd | more

Lister le contenu d'un répertoire avec pagination :

$ ls -lht /etc | more

Afficher uniquement certains éléments d'un fichier :

$ cat /etc/passwd | grep root | cut -d':' -f1,7
root:/bin/bash

Envoyer par mail certains éléments d'un fichier :

$ cat /etc/passwd | grep root | cut -d':' -f1,7 | mail toto

Afficher à l'écran et enregistrer dans un fichier le contenu d'un répertoire :

$ ls -t | tee listeFichiers
listeFichiers
envoiMail.sh
message
script.sh
$ cat listeFichiers
listeFichiers
envoiMail.sh
message
script.sh

Il est donc tout à fait possible de cumuler autant de commandes que l'on souhaite à partir du moment où l'on respecte l'ordre des commandes et des sorties / entrées standard.

Rediriger la sortie d'erreur standard dans un tube

$ ls -l /root 2>&1 | tee listeFichiers
ls: impoossible d'ouvrir le répertoire /root: Permission non accordée
$ cat listeFichiers
ls: impoossible d'ouvrir le répertoire /root: Permission non accordée

 

Regrouper des commandes

Le regroupement de commandes est utilisé pour rediriger les sorties standards de plusieurs commandes vers un même fichier ou vers un tube ou pour exécuter des commandes dans un même environnement.

$ date ; ls > listeFichiers
lundi 19 septembre 2011, 08:47:11 (UTC+0200)
$ cat listeFichiers
1coucou
cOucou
Coucou
coucou.r
coucou.s
coucou.t
coucou.uvw
envoiMail.sh

Dans l'exemple ci-dessus, la commande date et ls sont regroupées grâce au caractère ";".
Le résultat de la commande date est affiché à l'écran alors que le résultat de la commande ls est redirigé dans le fichier "listeFichiers".

Afin de rediriger la sortie standard des 2 commandes au même endroit, ils faut obligatoirement les regroupées avec des parenthèses "()" ou des accolades "{}".

$ ( date ; ls ) > listeFichiers
$ cat listeFichiers
lundi 19 septembre 2011, 08:51:58 (UTC+0200)
1coucou
cOucou
Coucou
coucou.r
coucou.s
coucou.t
coucou.uvw
envoiMail.sh

Les commandes regroupées entre parenthèses sont exécutées par le shell enfant alors que les commandes regroupées entre accolades sont exécutées par le shell parent.

Regroupement avec parenthèses :

$ pwd
/home/toto
$ ( mkdir temp ; cd temp ; touch fichierTemp ; pwd ; ls ) > listeFichiers
$ cat listeFichiers
/home/toto/temp
fichierTemp
$ pwd
/home/toto

Après exécution des commandes, le répertoire courant est le même qu'avant exécution.

Regroupement avec accolades :

$ pwd
/home/toto
$ { mkdir temp ; cd temp ; touch fichierTemp ; pwd ; ls ; } > listeFichiers
$ pwd
/home/toto/temp
$ cat ../listeFichiers
/home/toto/temp
fichierTemp

Après exécution des commandes, le répertoire courant n'est plus le même qu'avant exécution.

  • L'exécution de commandes regroupées entre accolades est plus rapide que les commandes regroupées entre parenthèses.
  • Le regroupement entre accolades doit obligatoirement se terminer par un point-virgule.
    Les accolades doivent toujours être suivies et précédées par une espace.

     

Généralement, le regroupement entre parenthèses est plus utilisé que le regroupement entre accolades car la syntaxe est plus simple et cela ne modifie pas le shell courant mais pour un gain de performances le regroupement entre accolades est préférable.

Exécuter des commandes en arrière-plan

Pour exécuter des commandes en arrière-plan, il suffit de rajouter à la fin le caractère "&".
Cela permet de récupérer le shell aussitôt après sans être obligé d'attendre la fin de l'exécution de la commande précédente.
Par contre, il est préférable d'utiliser la redirection de la sortie standard et de la sortie d'erreur standard.

$ find /etc -name passwd 1>listePasswd 2>/dev/null &
$ echo "On peut saisir d'autre commandes en attendant de voir le message ci-dessous une fois le processus terminé"
[1]+  Exit 1                  find /etc -name passwd > listePasswd 2> /dev/null
$ cat listePasswd
/etc/passwd
/etc/webmin/passwd
/etc/pam.d/passwd

Paramétrer son environnement de travail

Les variables d'environnement

Le commande set permet d'obtenir la liste des variables paramétrées pour le shell courant.

$ set
BASH=/bin/bash
HOME=/home/toto
HOSTNAME=myhostname
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
PS1='\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h$
PS2='> '
PS4='+ '
SHELL=/bin/bash
...

Le caractère $ permet d'obtenir la valeur d'une variable :

$ echo $BASH
/bin/bash

Pour créer ou modifier une variable :

$ VARIABLE='valeur'
$ echo $VARIABLE
valeur

Attention, car toutes variables créées ou modifiées de cette manière ne sont valables que pour la session en cours.

$ MAVARIABLE='<= Ceci est le contenu de ma variable =>'
$ echo $MAVARIABLE
<= Ceci est le contenu de ma variable =>

Les principales variables :

HOME : cette variable contient le chemin du répertoire d'accueil de l'utilisateur.

PATH : cette variable contient une liste de répertoire dans lesquels le shell recherche toutes les commandes qui sont exécutées.
Si une commande est exécutée et qu'elle ne se trouve dans aucun des répertoires indiqués dans la variable PATH, une erreur sera retournée en indiquant que la commande est introuvable.
Si l'on souhaite exécuter une commande qui se trouve dans un répertoire non indiqué dans la variable PATH, il suffit tout simplement de modifier le contenu de la variable.

$ PATH=$PATH:/leRepertoireDeMaCommande

Pour modifier la variable PATH définitivement, il suffit d'ajouter une ligne au fichier ~/.bashrc :

> export PATH=$PATH:/mon_autre_repertoire

Ou modifier directement le fichier /etc/environment :

> PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/mon_autre_repertoire"

Pour exécuter une commande présente dans le répertoire courant, il suffit d'ajouter à la variable PATH la valeur ":." ou ":".

PWD : cette variable contient le chemin du répertoire courant.

PS1 : cette variable contient la chaine de caractères correspondant au prompt ($ en règle générale).

Pour faire apparaitre la valeur du répertoire courant dans le prompt :

$ PS1='$PWD$ '
ou
$ PS1='\w$ '

BASH : Séquence d'échappement permettant de paramétrer la variable PS1

Séquence échappement Valeur
\u Nom de l'utilisateur
\h Nom de la machine
\w Répertoire courant
\W Partie terminale du répertoire courant

PS2 : cette variable contient la chaine de caractères correspondant au prompt secondaire (> en règle générale).

$ echo 'ceci est le début
> de ma chaine de texte
> et tant que je ne saisi pas
> le caractère apostrophe
> ça continue
> '
ceci est le début
de ma chaine de texte
et tant que je ne saisi pas
le caractère apostrophe
ça continue
$

$ mail toto<<fin
> un autre exemple
>
> fin
$

TERM : cette variable contient le type du terminal de l'utilisateur.

LOGNAME : cette variable contient le nom de l'utilisateur connecté.

Exporter des variables :

Il est parfois nécessaire d'exporter une variable car toutes ne le sont pas.
Pour savoir quelles sont les variables exportées d'office il suffit d'utiliser la commande env.

$ env
TERM=xterm
SHELL=/bin/bash
CDPATH=:.:/home/
USER=toto
...

Pour exporter une variable :

$ MAVARIABLE='maValeur'
$ export MAVARIABLE

ou

$ export MAVARIABLE='maValeur'

Par exemple, en BASH, pour utiliser le programme nano (éditeur de texte) comme éditeur par défaut du programme mutt (client mail en ligne de commande), il suffit d'inscrire dans son fichier ~/.bashrc :

export EDITOR=/usr/bin/nano

et pour une prise en compte immédiate, sans être obligé de se reconnecter, saisir dans la console :

$ export EDITOR=/usr/bin/nano
Et pour vérifier :
$ set | grep EDITOR
EDITOR=/usr/bin/nano
$ env | grep EDITOR
EDITOR=/usr/bin/nano

Les options du shell

Pour activer ou désactiver les options du shell, il suffit d'utiliser la commande set avec les options -o et +o.

Activation :

$ set -o option

Désactivation :

$ set +o option

Pour visualiser  la liste des options disponibles ainsi que leur état, saisir dans une console :

$ set -o
allexport       off
braceexpand     on
emacs           on
errexit         off
errtrace        off
functrace       off
hashall         on
histexpand      on
history         on
ignoreeof       off
interactive-comments    on
keyword         off
monitor         on
noclobber       off
noexec          off
noglob          off
nolog           off
notify          off
nounset         off
onecmd          off
physical        off
pipefail        off
posix           off
privileged      off
verbose         off
vi              off
xtrace          off

Détails de certaines options :

ignoreeof

Pour quitter le shell, il existe 3 méthodes :

  • La commande exit.
  • La commande logout.
  • La combinaison des touches ^d (CTRL+d).

Si l'option ignoreeof est activée, il n'est plus possible de quitter le shell en appuyant sur ^d.

noclobber

Quand une redirection est faite vers un fichier déjà existant, celui-çi est automatiquement écrasé sans confirmation. Pour inverser se fonctionnement, il suffit d'activer l'option noclobber.

On vérifie l'état de l'option noclobber

$ set -o | grep noclobber
noclobber       off

On redirige le résultat de la commande ls vers le fichier liste

$ ls -l > liste

On redirige le résultat de la commande pwd vers le fichier liste déjà existant

$ pwd > liste

On active l'option noclobber

$ set -o noclobber

On vérifie que l'option noclobber est bien activée

$ set -o | grep noclobber
noclobber       on

On redirige le résultat de la commande pwd vers le fichier liste déjà existant

$ pwd > liste
-bash: liste : impossible d'écraser le fichier existant

On force la redirection de la commande pwd vers le fichier liste déjà existant

$ pwd >| liste

emacs & vi

Ces 2 options permettent de paramétrer la rappel des commandes.
En ksh, ces 2 options sont désactivées.
En bash, seule l'option emacs est activée.

xtrace

Cette option est utilisée pour déboguer les scripts shell.

Les alias

Les alias permettent de créer des raccourcis vers des commandes et de les personnaliser.

Pour visualiser la liste des alias déjà existant :

$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -lF'
alias ls='ls --color=auto'
alias tarc='tar -cvzf'
alias tarx='tar -xvzf'

Pour visualiser un alias en particulier :

$ alias la
alias la='ls -A'

Pour créer un alias :

$ alias c='clear'
$ alias rm='rm -i'
$ rm liste
rm : supprimer fichier «liste» ? y

En bash, pour créer un alias définitivement, il suffit de le renseigner dans le fichier ~/.bashrc

Pour supprimer un alias :

$ unalias c
$ c
c : commande introuvable

Historique des commandes

Le shell enregistre toutes les commandes saisies dans un fichier texte.
En bash, il s'agit du fichier ~/.bash_history
En ksh, il s'agit du fichier ~/.sh_history
Pour utiliser le rappel des commandes, le shell utilise soit emacs soit vi.
En bash, c'est l'option emacs qui est activée par défaut mais il est possible d'utiliser vi.
En ksh, les 2 options sont par défaut désactivées.
Le choix d'activer l'un, désactive l'autre.

$ set -o | grep "^emacs\|^vi"
emacs           on
vi              off

Pour les utilisateurs non habitués à l'éditeur vi, il est préférable d'utiliser emacs car le rappel des commandes se fait avec les flèches du clavier.

Les fichiers d'environnement

Les fichiers d'environnement sont utilisés pour stocker de manière permanente tout le paramétrage de l'environnement de l'utilisateur.

Ce sont des scripts shell qui contiennent un ensemble de commandes Linux.

Certains scripts sont exécutés uniquement par le shell de connexion et d'autres par un shell ordinaire.

Le shell de connexion est lancé immédiatement après l'identification de l'utilisateur.

Le shell de connexion exécute en premier le script /etc/profile. Ce script contient le paramétrage commun à tous les utilisateurs.
Il recherche ensuite dans le répertoire d'accueil de l'utilisateur un script dont le nom dépend du shell utilisé.

En sh / ksh :

Le script de connexion se nomme ~/.profile

En bash :

L'un des 3 scripts suivants dans l'ordre

  1. ~/.bash_profile
  2. ~/.bash_login
  3. ~/.profile

 

En bash, le shell de connexion exécute en premier le script /etc/profile, puis le script ~/.profile et enfin le script ~/.bashrc.

C'est dans ce dernier fichier, ~/.bashrc, que l'on peut définir de nouveaux alias, exporter automatiquement de nouvelles variables, paramétrer l'auto-complétion, définir les variables PS1 et PS2, paramétrer le nombre de commandes à historiser, etc etc.

Les bases de la programmation shell

Les variables utilisateur

Le shell permet de définir ou redéfinir des variables pour l'environnement de l'utilisateur.

Il est également possible de définir d'autres variables dites variables utilisateur qui vont permettre de stocker des informations utiles durant l'exécution de scripts.

Le nomage des variables

  • Le premier caractère doit être obligatoirement une lettre minuscule, majuscule ou un tiret bas [a-zA-Z_]
  • Les caractères suivants peuvent être des lettres en minuscule, majuscule, des chiffres de 0 à 9 ou un tiret bas [a-zA-Z0-9_]

Définition d'une variable

$ variable1=valeur1
$ echo $variable1
valeur1
$

Il ne faut surtout pas mettre d'espace avant et après le signe égal (=) ainsi que dans la valeur de la variable.

Si un espace est placé avant le signe '=', le shell va interpréter la chaine 'variable1' comme étant une commande et la chaine '=valeur1' comme étant un paramètre de la commande variable1.

Pour pouvoir affecter une valeur contenant un espace, il faut obligatoirement le protéger avec des simples quotes.

$ variable2='valeur2 valeur21'
$ echo $variable2
valeur2 valeur21
$

Pour supprimer le contenu d'une variable, il faut utiliser la commande unset :

$ variable3=valeur3
$ echo $variable3
valeur3
$ set | grep variable3
variable3=valeur3
$ unset variable3
$ echo $variable3
$ set | grep variable3
$

Concaténer des variables avec une chaine de caractères :

Un exemple qui produit une erreur

$ fichier=monFichier
$ dateJour=20111006
$ nouveauFichier=$fichier_$dateJour
$ echo $nouveauFichier
20111006
$

Le but de cet exemple est de concaténer la variable $fichier, un tiret bas '_', puis la variable $dateJour.
Le résultat final est erroné car au moment d'affecter la nouvelle variable nouveauFichier (ligne 3), le shell interprète $fichier_ comme étant une variable à part entière puisque le tiret bas '_' est autorisé dans le nommage des variables.
La variable $fichier_ n'existant pas, celle ci est égale à "blanc".
Pour indiquer au shell qu'il faut interpréter le tiret bas '_' comme étant une chaine de caractères, il faut obligatoirement entourer la variable qui le précède avec des {}.

$ fichier=monFichier
$ dateJour=20111006
$ nouveauFichier=${fichier}_$dateJour
$ echo $nouveauFichier
monFichier_20111006
$

Substituer des variables :

Le shell offre la possibilité d'attribuer une valeur par défaut aux variables non initialisées ou au contraire initialisées.

Expression ${variable:-valeur}

  • Si la variable n'est pas vide, l'expression est égale au contenu de la variable
  • Si la variable est vide, l'expression est égale au contenu de valeur

$ jour=mardi
$ echo "Nous sommes : ${jour:-dimanche}"
Nous sommes : mardi
$ unset jour
$ echo "Nous sommes : ${jour:-dimanche}"
Nous sommes : dimanche
$ echo $jour
$

Expression ${variable:=valeur}

  • Si la variable n'est pas vide, l'expression est égale au contenu de la variable
  • Si la variable est vide, l'expression et la variable sont égale au contenu de valeur

$ jour=mardi
$ echo "Nous sommes : ${jour:=dimanche}"
Nous sommes : mardi
$ echo $jour
mardi
$ unset jour
$ echo "Nous sommes : ${jour:=dimanche}"
Nous sommes : dimanche
$ echo $jour
dimanche
$

Expression ${variable:+valeur}

  • Si la variable n'est pas vide, l'expression est égale au contenu de valeur
  • Si la variable est vide, l'expression est égale à variable, donc vide

$ bool=1
$ echo "Si bool=1 alors j'affiche : ${bool:+true}"
Si bool=1 alors j'affiche : true
$ unset bool
$ echo "Si bool=1 alors j'affiche : ${bool:+true}"
Si bool=1 alors j'affiche :
$

Expression ${variable:?message}

  • Si la variable n'est pas vide, l'expression est égale au contenu de la variable
  • Si la variable est vide, l'expression est égale au nom de la variable suivi du contenu de message

Dans un script, si la variable est vide, le message est affiché et le script se termine aussitôt

$ nombre=56
$ echo ${nombre:?"nombre indefini"}
56
$ unset nombre
$ echo ${nombre:?"nombre indefini"}
-bash: nombre: nombre indefini
Affichage du message par défaut
$ echo ${nombre:?}
-bash: nombre : parametre vide ou non defini
$

Substitution de commandes

Les caractères de substitution permettent de remplacer une commande par le résultat de son exécution.

Par exemple, comment faire pour afficher un message affichant la date du jour.

$ echo "Nous sommes le date"
Nous sommes le date
$

Il suffit d'entourer la commande avec les caractères de substitution :

  • Les quotes inversés `commande`
  • $(commande)

$ echo "Nous sommes le `date`"
Nous sommes le vendredi 7 octobre 2011, 08:39:49 (UTC+0200)
$ echo "Nous sommes le $(date)"
Nous sommes le vendredi 7 octobre 2011, 08:40:05 (UTC+0200)
$

Idem pour initialiser une variable :

$ date=`date`
$ echo $date
vendredi 7 octobre 2011, 08:42:34 (UTC+0200)
$

Les caractères de protection

Les caractères de protection servent à utiliser les caractères spéciaux du shell comme n'importe quels caractères.

Les simples quotes

Les simples quotes désactivent tous les caractères spéciaux du shell. Ils doivent être utilisés en nombre pair sur une ligne de commande.

$ echo $PWD
/home/toto

Avec les simples quotes, la fonction du caractère '$' est désactivée :

$ echo '$PWD'
$PWD
$

Afficher tous les fichiers d'un dossier à l'aide de la fonction 'echo' et du caractère spécial '*'

$ echo *
1coucou cOucou Coucou coucou.r ...

Avec les simples quotes, la fonction du caractère '*' est désactivée :

$ echo '*'
*
$

$ echo Bonjour $(logname)
Bonjour toto
$ echo 'Bonjour $(logname)'
Bonjour $(logname)
$

La quote ne se protège pas elle-même.
Tant que les quotes ne sont pas par pair, le shell affiche le prompt secondaire

$ echo 'J'affiche un quote supplémentaire'
>

L'antislash '\'

L'antislash désactive la fonctionnalité du caractère qui le suit.

$ echo La liste des fichiers * du dossier $PWD
La liste des fichiers 1coucou cOucou Coucou coucou.r du dossier /volume1/home/ronan/atuer/toto
$ echo La liste des fichiers \* du dossier \$PWD
La liste des fichiers * du dossier $PWD
$

L'antislash se protège lui-même

$ echo \\
\
$

$ echo L\'antislash protège la quote
L'antislash protège la quote
$

Le premier antislash protège le second, le troisième protège le '$'

$ echo \\\$PWD
\$PWD
$

Les guillemets

Les guillemets protège tous les caractères spéciaux du shell à l'exception du dollar $, des quotes inversés ``, du dollar et des parenthèses $( ), de l'antislash \ et des guillemets ".

$ echo "
> La variable \$HOME est substituée par $HOME
> La commande \$(logname) est exécutée : $(logname)
> Je peux afficher des > et des |
> Et même des guillemets \"."
La variable $HOME est substituée par /home/toto
La commande $(logname) est exécutée : toto
Je peux afficher des > et des |
Et même des guillemets ".
$

En règle générale, il est préférable d'utiliser la commande echo avec des guillemets

Récapitulatif des caractères spéciaux

Caractères Signification
espace - tabulation - saut de ligne Séparateurs de mots sur la ligne de commande
& Arrière-plan
|   <<   <   >   >> Tubes et redirections
() et {} Regroupement de commandes
; Séparateur de commandes
*   ?   [ ]   ?( )   +( )   *( )   !( )   @( ) Caractères de génération de noms de fichier
$ et ${ } Valeur d'une variable
``   $( ) Substitution de commandes
' '   " "   \ Caractères de protection

 

Interprétation d'une ligne de commande

:Les caractères spéciaux du shell sont interprétés dans un ordre bien précis.

  1. Isolement des mots séparés par les caractères espace, tabulation, saut de ligne.
  2. Traitement des caractères de protections ( ' ', " ", \).
  3. Substitution des variables ($)
  4. Substitution des commandes (``, $( ) )
  5. Substitution des caractères de génération des noms de fichiers (*, ?, [ ], ?( ), +( ), *( ), !( ), @( ))
  6. Traitement des tubes et redirections (|, <<, <, >, >>)
  7. Lancement de la commande

Exemple :

$ echo "\$HOME --> $HOME
> Mon login --> $(logname)
> Nous sommes le `date`
> La liste des fichiers :" * | nl > test ; cat test
     1  $HOME --> /home/toto
     2  Mon login --> toto
     3  Nous sommes le jeudi 20 octobre 2011, 08:27:05 (UTC+0200)
     4  La liste des fichiers : fichier1 fichier2 fichier3 fichier4
$

Interprétation :

  1. \$HOME (traitement des caractères de protection)
  2. $HOME (substitution des variables)
  3. $(logname) et `date` (substitution des commandes)
  4. * (substitution des caractères de génération des noms de fichiers)
  5. | nl > test (traitement des tubes et redirections)
  6. echo "...." ; cat test (exécution des commandes)

Ecriture et exécution d'un script shell

Un script shell est un fichier texte contenant des commandes Linux internes ou externes.

Le nom du script peut avoir une extension mais cela n'est pas obligatoire.

En règle générale, tous les scripts portent l'extension .sh

Exemple :

$ nl monPremierScript.sh
     1  pwd
     2  echo "Liste des fichiers commençant par la lettre C"
     3  ls -lh c*
$

Exécution :

$ bash monPremierScript.sh
/home/toto
Liste des fichiers commençant par la lettre C
-rw-r--r-- 1 toto toto 0 2011-09-14 13:55 cOucou
-rw-r--r-- 1 toto toto 0 2011-09-15 07:20 coucou.r
-rw-r--r-- 1 toto toto 0 2011-09-15 07:20 coucou.s
-rw-r--r-- 1 toto toto 0 2011-09-15 07:20 coucou.t
-rw-r--r-- 1 toto toto 0 2011-09-15 07:21 coucou.uvw
$

Exécuter un script par le shell enfant

Il existe 3 méthodes pour exécuter un script.

La première méthode consiste à appeler la commande bash, sh, ksh etc.. (l'interpréteur de commande désiré) suivi du nom du script.

$ bash monPremierScript.sh

Dans ce cas, le droit de lecture sur le fichier est suffisant.

$ ls -l monPremierScript.sh
-rw-r--r-- 1 toto toto 68 2011-10-20 08:52 monPremierScript.sh
$

La seconde méthode consiste à utiliser l'entrée standard de la commande bash, sh, ksh etc.. afin de lui faire lire le contenu du script.

$ bash < monPremierScript.sh

Dans ce cas, le droit de lecture sur le fichier est également suffisant.
Cette méthode est très peu utilisée.

La troisième méthode consiste à rendre le script exécutable.

$ chmod u+x monPremierScript.sh
$ ls -l monPremierScript.sh
-rwxr--r-- 1 toto toto 68 2011-10-20 08:52 monPremierScript.sh
$ ./monPremierScript.sh

Choisir un interpréteur de commande :

Pour indiquer au script quel interpréteur de commande utiliser, il faut indiquer sur la première ligne du script les caractères #! suivi du chemin absolu de l'interpréteur.

Par exemple, pour utiliser l'interpéteur de commande BASH, on recherche son chemin absolu :

$ whereis bash
bash: /bin/bash /etc/bash.bashrc /usr/share/man/man1/bash.1.gz
$

La résultat de la commande nous indique le répertoire /bin/bash
Inscrire sur la première ligne du script #! /bin/bash

$ nl monPremierScript.sh
     1  #! /bin/bash
     2  pwd
     3  echo "Liste des fichiers commençant par la lettre C"
     4  ls -lh c*
$

De cette manière, le script sera toujours exécuté avec l'interpréteur de commande BASH.

Exécuter un script par le shell courant

L'exécution d'un script par le shell courant permet de modifier l'environnement du shell courant.

Les droits d'exécution sur le script ne sont pas nécessaires.
L'utilisation de la commande interne "." est nécessaire pour l'exécution.

Par exemple, pour modifier l'éditeur par défaut, il est nécessaire de modifier la variable environnement EDITOR.

$ env | grep EDITOR
EDITOR=/usr/bin/vi
echo $EDITOR
/usr/bin/vi
nano .profile
export EDITOR='/usr/bin/nano'
^o (enregistrer)
^x (quitter)
$ . .profile
$ echo $EDITOR
/usr/bin/nano
env | grep EDITOR
EDITOR=/usr/bin/nano
$

Attention, l'appel de la commande interne "." est obligatoirement suivi d'un espace et d'un script shell.

Les commentaires

Il est possible d'écrire dans un script des commentaires afin de faciliter la compréhension du script.

Les commentaires sont désignés à l'aide du caractère #.
Ne pas confondre avec #! inscrit sur la première ligne du script qui sert à renseigner l'interpréteur de commande.

$ nl monPremierScript.sh
     1    #! /bin/bash
       
     2    # Ceci est un commentaire
     3    # La commande PWD indique le répertoire courant
     4    pwd
     5    # La commande ECHO permet d'afficher du texte et des informations
     6    echo "Liste des fichiers commençant par la lettre C"
     7    # La commande LS permet de lister tous les fichiers commençant par la lettre "c"
     8    ls -lh c*
$

Les variables réservées du shell

Dans un script, il est possible de récupérer en lecture un certain nombre de variables réservées.
Ces variables sont initialisées par le shell et contiennent des informations diverses et variées.

Les paramètres positionnels

Les scripts shell sont capables de récupérer des arguments placés juste après l'appel du script.

  • $# indique le nombre d'arguments passés au script
  • $0 indique le nom du script
  • $1, $2, $3 ... $9 contient la valeur de l'argument 1, 2, 3 ... 9

En KSH et en BASH, il est possible d'aller au delà de 9 arguments en utilisant les variables spéciales ${10}, ${11} etc...
Les accolades sont obligatoires à partir du moment où le nom de la variable contient plus d'un chiffre.

  • $* et $@ contient la liste de tous les arguments

Exemple :

$ nano monSecondScript.sh
#! /bin/bash
echo "Il y a $# arguments passés au script"
echo "Le script se nomme $0"
echo "La valeur du premier argument : $1"
echo "La valeur du second argument : $2"
echo "La valeur du troisième argument : $3"
echo "Les valeurs de tous les arguments : $*"
^o (enregistrer)
^x (quitter)
$ chmod u+x monSecondScript.sh
$ ./monSecondScript.sh arg1 arg2 arg3 arg4 arg5
Il y a 5 arguments passés au script
Le script se nomme ./monSecondScript.sh
La valeur du premier argument : arg1
La valeur du second argument : arg2
La valeur du troisième argument : arg3
Les valeurs de tous les arguments : arg1 arg2 arg3 arg4 arg5
$

Exemple avec 3 arguments :

$ ./monSecondScript.sh 25 + 15
Il y a 3 arguments passés au script
Le script se nomme ./monSecondScript.sh
La valeur du premier argument : 25
La valeur du second argument : +
La valeur du troisième argument : 15
Les valeurs de tous les arguments : 25 + 15
$

Tous les arguments doivent être séparés les uns des autres par un espace ou une tabulation

La commande shift

La commande shift permet de décaler la liste des arguments d'une ou plusieurs positions vers la gauche.

Cette commande est utilisée dans le cas où le premier argument n'a pas à subir le même traitement que les suivants.
Dans ce cas, le premier argument doit être sauvegardé dans une variable et l'exécution de la commande shift entraine le remplacement du premier argument par le second et ainsi de suite.

$ nl monTroisiemeScript.sh
     1  #! /bin/bash
     2  # Ce script doit recevoir en premier argument le nom d'un dossier puis des noms de fichiers pour les suivants
     3  # Affichage des variables avant l'utilisation de la commande shift
     4  echo -e "Affichage avant exécution de la commande shift\n"
     5  echo "1er argument \$1 : $1"
     6  echo "1er argument \$2 : $2"
     7  echo "1er argument \$3 : $3"
     8  echo "1er argument \$4 : $4"
     9  echo "Tous les arguments \$* : $*"
    10  echo -e "Le nombre d'argument \$# : $#\n"
    11  # On sauvegarde le premier argument dans la variable rep
    12  rep=$1
    13  # On décale tous les arguments avec la commande shift
    14  shift
    15  # Affichage des variables après l'exécution de la commande shift
    16  echo -e "Affichage après exécution de la commande shift\n"
    17  echo "1er argument \$1 : $1"
    18  echo "1er argument \$2 : $2"
    19  echo "1er argument \$3 : $3"
    20  echo "1er argument \$4 : $4"
    21  echo "Tous les arguments \$* : $*"
    22  echo -e "Le nombre d'argument \$# : $#\n"
    23  # Création du répertoire
    24  mkdir $rep
    25  # On se positionne dans le nouveau répertoire
    26  cd $rep
    27  # Création des fichiers dans le nouveau répertoire
    28  for fichier in $*
    29  do
    30          touch $fichier
    31  done
$ chmod u+x monTroisiemeScript

Exécution du script :

$ ./monTroisiemeScript.sh test fic1 fic2 fic3 fic4 fic5 fic6
Affichage avant exécution de la commande shift
1er argument $1 : test
1er argument $2 : fic1
1er argument $3 : fic2
1er argument $4 : fic3
Tous les arguments $* : test fic1 fic2 fic3 fic4 fic5 fic6
Le nombre d'argument $# : 7
Affichage après exécution de la commande shift
1er argument $1 : fic1
1er argument $2 : fic2
1er argument $3 : fic3
1er argument $4 : fic4
Tous les arguments $* : fic1 fic2 fic3 fic4 fic5 fic6
Le nombre d'argument $# : 6
ls -l ./test/
total 0
-rw-r--r-- 1 toto toto 0 2011-10-25 09:01 fic1
-rw-r--r-- 1 toto toto 0 2011-10-25 09:01 fic2
-rw-r--r-- 1 toto toto 0 2011-10-25 09:01 fic3
-rw-r--r-- 1 toto toto 0 2011-10-25 09:01 fic4
-rw-r--r-- 1 toto toto 0 2011-10-25 09:01 fic5
-rw-r--r-- 1 toto toto 0 2011-10-25 09:01 fic6
$

Code de retour d'une commande

Toutes les commandes Linux retournent un code d'erreur compris entre 0 et 255.
La valeur 0 représente la valeur vrai (succès de la commande).
Les valeurs supérieur à 0 représente la valeur faux (échec de la commande).
Le code erreur de la dernière commande utilisée est contenu dans la variable $?

$ ls f*
f1chier               fichier159159.log  fichier159.log  fichier161.log  fichier1.abc  fichier1.c   fset
fichier159159159.log  fichier159160.log  fichier160.log  fichier1.a      fichier1.b    fichier.log
$ echo $?
0
$ ls 2*
ls: impossible d'accéder à 2*: Aucun fichier ou dossier de ce type
$ echo $?
2

Dans un script shell, le test du code de retour d'une commande permet d'effectuer différentes actions.

Un script shell étant lui-même une commande, il est possible de lui faire retourner un code d'erreur avec la commande exit.

Par exemple, il est possible d'indiquer dans un script exit 1 afin d'indiquer une erreur rencontrée et/ou exit 0 afin d'indiquer que tout c'est bien déroulé.

Dans l'exemple suivant, le script test s'il reçoit bien au minimum 2 arguments et renvoi le code erreur 1 si c'est faux.

$ nl ./script.sh
1  #! /bin/bash
2  # Le script doit recevoir au minimum 2 arguments
3  if [ $# -lt 2 ]
4  then
5          # Si le nombre d'arguments est inférieur à 2
6          # on retourne le code erreur 1
7          echo "Nombre arguments incorrect"
8          exit 1
9  else
10          # Si le nombre d'arguments est supérieur ou égal à 2
11          # on retourne le code erreur 0
12          echo "Nombre arguments correct"
13          exit 0
14  fi
$

Exécution du script :

$ ./script.sh
Nombre arguments incorrect
$ echo $?
1
$ ./script.sh test
Nombre arguments incorrect
$ echo $?
1
$ ./script.sh test test2
Nombre arguments correct
$ echo $?
0
$

Autres variables spéciales

La variable $$ représente le PID du shell qui interprète le script.
La valeur de cette variable est la même pendant toute la durée d'exécution du script et différente à chaque utilisation du script.

Exemple :

Dans ce script, la variable $$ est utilisée pour générer le nom d'un dossier différent à chaque exécution du script.

$ nl monQuatriemeScript.sh
     1  #! /bin/bash
     2  dossierTemp=dossier_$$
     3  echo "Création du dossier \"$dossierTemp\""
     4  mkdir $dossierTemp
     5  cd $dossierTemp
     6  for (( i=0 ; i<10 ; i++)) do
     7          touch fichier_$i
     8  done
     9  exit 0
$

$ ./monQuatriemeScript.sh
Création du dossier "dossier_26563"
$ ./monQuatriemeScript.sh
Création du dossier "dossier_26581"
$

La variable $! représente le PID d'une commande exécutée en arrière plan.

exemple :

$ nl monCinquiemeScript.sh
     1  #! /bin/bash
     2  echo "Le script est exécuté sous le PID $$"
     3  find /etc -name $1 1> resultat 2> /dev/null &
     4  echo "La commande FIND est en cours d'exécution sous le PID $!"
     5  ps
     6  exit 0
$

$ ./monCinquiemeScript.sh hosts.allow
Le script est exécuté sous le PID 29999
La commande FIND est en cours d'exécution sous le PID 30000
  PID TTY          TIME CMD
19703 pts/0    00:00:00 bash
29999 pts/0    00:00:00 monCinquiemeScr
30000 pts/0    00:00:00 find
30001 pts/0    00:00:00 ps
$

La commande read

La commande read lit son entrée standard et affecte les valeurs saisies dans la ou les variables passées en argument.

Lecture au clavier

$ read var1
coucou
$ echo $var1
coucou
$

Tous les mots saisis sont stockés dans la variables var1

$ read var1
coucou tout le monde
$ echo $var1
coucou tout le monde
$

Le premier mot est stocké dans var1 et le second dans var2

$ read var1 var2
A bientot
$ echo $var1
A
$ echo $var2
bientot
$

Le premier mot est stocké dans var1 et les autres dans var2

$ read var1 var2
A bientot tout le monde
$ echo $var1
A
$ echo $var2
bientot tout le monde
$

Le mot est stocké dans var1 et var2 est vide

$ read var1 var2
Bonjour
$ echo $var1
Bonjour
$ echo $var2
 
$

En KSH, il est possible d'associer un message à la commande read

$ read var1?"Saisir une valeur : "
Saisir une valeur : coucou
$ echo $var1
coucou
$

Code de retour

Le code retour de la commande read est vrai (0) si un texte est saisi et faux (1) si la commande est interrompue en appuyant sur ctrl+d (^d)

$ read var1
coucou
$ echo $?
0
$ echo $var1
coucou
$

$ read var1
[Entrée]
$ echo $?
0
$ echo $var1
 
$

$ read var1
$ ^d
$ echo $?
1
$ echo $var1
 
$

La variable IFS

Cette variable contient les caractères qui permettent de scinder les entrées au clavier.
Par défaut elle contient les caractères espace, tabulation (\t) et saut de ligne (\n)

Le contenu de cette variable peut être modifié

$ OLDIFS="$IFS"
$ IFS="|"                                       # modification du caractère de séparation
$ read var1 var2 var3
colonne1|colonne2|colonne3
$ echo $var1
colonne1
$ echo $var2
colonne2
$ echo $var3
colonne3
$ IFS="$OLDIFS"
$

Les expression $IFS et $OLDIFS doivent obligatoirement être placées entre guillemets pour que les caractères internes ne soient pas interprétés par le shell

En BASH, il est possible d'associer un message à la commande read grâce à l'option -p :

$ read -p "Votre nom ? " NAME
Votre nom ? toto
$ echo $NAME
toto

En y ajoutant l'option -s , il est possible d'utiliser la commande read pour saisir un mot de passe :

$ read -s -p "Votre passwd ? " PASSWD; echo
Votre passwd ?
$ echo $PASSWD
aaaa

Avec l'option -s, il ne faut pas oublier d'ajouter la commande echo pour afficher un saut de ligne.

Exécution de tests

La commande test permet de faire des tests sur des fichiers, des chaines de caractères et des nombres.

Elle renvoie le code retour 0 (vrai) ou 1 (faux) qu'il est possible de consulter en affichant la valeur de $?

Il existe 2 syntaxes pour utiliser la commande test

$ test expression

$ [ expression ]

La paire de crochet signifie la même chose que la commande test.
Les crochets ouvrants et fermants doivent obligatoirement être suivis et précédés d'un espace.

La commande test

Tests sur les fichiers

Expression Code de retour
-b FILE Vrai si le fichier existe et est du type spécial bloc
-c FILE Vrai si le fichier existe et est du type spécial caractère
-d FILE Vrai si le fichier existe et est du type répertoire
-e FILE Vrai si le fichier existe
-f FILE Vrai si le fichier existe et est du type ordinaire
-G FILE Vrai si le fichier existe et si l'utilisateur appartient au groupe propriétaire du fichier
-h FILE Vrai si le fichier existe et est du type lien symbolique
-L FILE Vrai si le fichier existe et est du type lien symbolique (idem -h)
-O FILE Vrai si le fichier existe et si l'utilisateur est le propriétaire du fichier
-r FILE Vrai si le fichier existe et est accessible en lecture
-s FILE Vrai si le fichier existe et n'est pas vide
-S FILE Vrai si le fichier existe et est du type socket
-w FILE Vrai si le fichier existe et est accessible en écriture
-x FILE Vrai si le fichier existe et est exécutable
FILE1 -ef FILE2 Vrai si les fichiers ont le même lien physique
FILE1 -nt FILE2 Vrai si FILE1 est plus récent que FILE2
FILE1 -ot FILE2 Vrai si FILE1 est plus ancien que FILE2

Exemple :

Le fichier /etc/group est un fichier ordinaire

$ test -f /etc/group
$ echo $?
0
$

Le fichier /etc/groupe n'existe pas (test avec l'autre syntaxe)

$ [ -f /etc/groupe ]
$ echo $?
1
$

Le fichier /etc/init.d existe et est un répertoire

$ [ -d /etc/init.d ]
$ echo $?
0
$

Le fichier /etc/group n'est pas un répertoire ou il n'exsite pas

$ test -d /etc/group
$ echo $?
1
$

Le fichier contenu dans la variable file n'est pas un répertoire ou n'existe pas

$ file=/etc/group
$ test -d $file
$ echo $?
1
$

L'utilisateur n'a pas le droit d'écriture sur le fichier /etc/group

$ [ -w /etc/group ]
$ echo $?
1
$

Tests sur les chaines de caractères

Expression Code de retour
-n STRING Vrai si la longueur de la chaine n'est pas égale à 0
-z STRING Vrai si la longueur de la chaine est égale à 0
STRING1 = STRING2 Vrai si les 2 chaines sont égales
STRING1 != STRING2 Vrai si les 2 chaines sont différentes
STRING Vrai si la chaine n'est pas vide (idem -n)

Pour les tests sur les chaines de caractères, il est recommandé de mettre le nom des variables entre guillemets.

Exemple :

Avec les variables suivantes :

$ str1="test1"
$ str2="test2"
$ str3="test1"
$

La variable $str1 n'est pas vide

$ [ -n "$str1" ]
$ echo $?
0
$

$ [ "$str1" ]
$ echo $?
0

$ [ -z "$str1" ]
$ echo $?
1
$

La variable $str4 est vide

$ [ -n "$str4" ]
$ echo $?
1
$

$ [ -z "$str4" ]
$ echo $?
0
$

$ [ "$str4" ]
$ echo $?
1
$

$str1 & $str3 sont identiques

$ [ "$str1" = "$str3" ]
$ echo $?
0
$

$ [ "$str1" != "$str3" ]
$ echo $?
1
$

$str1 & $str2 sont différentes

$ [ "$str1" != "$str2" ]
$ echo $?
0
$

$ [ "$str1" = "$str2" ]
$ echo $?
1
$

Tests sur les nombres

Expression Code de retour
INT1 -eq INT2 Vrai si INT1 est égal à INT2 (=)
INT1 -ge INT2 Vrai si INT1 est supérieur ou égal à INT2 (>=)
INT1 -gt INT2 Vrai si INT1 est supérieur à INT2 (>)
INT1 -le INT2 Vrai si INT1 est inférieur ou égal à INT2 (<=)
INT1 -lt INT2 Vrai si INT1 est inférieur à INT2 (<)
INT1 -ne INT2 Vrai si INT1 est différent de INT2 (!=)

Exemple :

Avec les variables suivantes

$ int1=1
$ int2=2
$ int3=3
$ int4=2
$

$int2 & $int4 sont égaux

$ [ $int2 -eq $int4 ]
$ echo $?
0

$int2 est supérieur ou égal à $int4

$ [ $int2 -ge $int4 ]
$ echo $?
0

$int3 est supérieur ou égal à $int1

$ [ $int3 -ge $int1 ]
$ echo $?
0

$int4 n'est pas supérieur ou égal à $int3

$ [ $int4 -ge $int3 ]
$ echo $?
1

$int3 est supérieur à $int2

$ [ $int3 -gt $int2 ]
$ echo $?
0

$int2 n'est pas supérieur à $int3

$ [ $int2 -gt $int3 ]
$ echo $?
1

$int2 est inférieur ou égal à $int3

$ [ $int2 -le $int3 ]
$ echo $?
0

$int2 est inférieur à $int3

$ [ $int2 -lt $int3 ]
$ echo $?
0

$int2 est inférieur ou égal à $int4

$ [ $int2 -le $int4 ]
$ echo $?
0

$int2 est différent de $int3

$ [ $int2 -ne $int3 ]
$ echo $?
0

$int2 n'est pas différent de $int4

$ [ $int2 -ne $int4 ]
$ echo $?
1
$

Les opérateurs

Opérateur Signification
! Négation
-a ET
-o OU

Les opérateurs sont exécutés avec une priorité bien précise :

  1. ! (négation)
  2. -a (ET)
  3. -o (OU)

 

Le fichier /etc/group n'est pas un répertoire

$ [ ! -d /etc/group ]
$ echo $?
0
$

Le fichier monPremierScript.sh existe et est exécutable

$ [ -f monPremierScript.sh -a -x monPremierScript.sh ]
$ echo $?
0
$

Le fichier monPremierScript.sh n'est pas un répertoire mais il est exécutable

$ [ -d monPremierScript.sh -o -x monPremierScript.sh ]
$ echo $?
0
$

Il est possible de modifier la priorité d'exécution des opérateurs en utilisant des paranthèses.
\(........\)
Les parenthèses doivent être protégées par des antislashes afin de ne pas être interprétées par le shell comme étant un regroupement de commandes.

L'utilisateur doit avoir le droit d'écriture sur le fichier fic1 et le fichier fic4 ou fic7 doit exister

$ ls
fic1  fic2  fic3  fic4  fic5  fic6  script.sh
$ [ -w fic1 -a \( -e fic4 -o -e fic7 \) ]
$ echo $?
0
$

Il doit toujours y avoir un espace autour des opérateurs !, -a et -o.

Exemples d'utilisation

Utilisation de la commande test avec la structure de controle if.

Principe d'utilisation :

if  commande1 
then
     commande2
     commande3
     ...
else
     commande4
     ...
fi

if  commande1  ; then
     commande2
     commande3
     ...
else
     commande4
     ...
fi

La commande1 est exécutée, si son code retour ($?) vaut 0 (vrai) alors les commandes 2 & 3 sont exécutées, sinon c'est la commande4 qui est exécutée (code retour de la commande1 supérieur à 0 - faux).

Exemple :

Dans l'exemple suivant, le script test s'il reçoit bien au minimum 2 arguments.
Dans le cas contraire, le script affiche un message indiquant que le nombre d'arguments est incorrect et se termine en retournant un code erreur 1.

$ nl monTroisiemeScript.sh
     1  #! /bin/bash
     2  # Ce script doit recevoir en premier argument le nom d'un dossier puis des noms de fichiers pour les suivants
     4  # Ce script doit recevoir au minimum 2 arguments
     5  # Le premier étant le nom d'un dossier
     6  # Les suivants étant les noms des fichiers
     7  # On test si il y a 2 arguments au minimum et on retourne le code erreur 1 si c'est faux
     8  if  [ $# -lt 2 ] 
     9  then
    10          echo "Nombre d'arguments incorrect !!!"
    11          exit 1
    12  fi
    13  # On sauvegarde le premier argument dans la variable rep
    14  rep=$1
    15  # On décale tous les arguments avec la commande shift
    16  shift
    17  # Création du répertoire
    18  mkdir $rep
    19  # On se positionne dans le nouveau répertoire
    20  cd $rep
    21  # Création des fichiers dans le nouveau répertoire
    22  for fichier in $*
    23  do
    24          touch $fichier
    25  done
    26  exit 0

$ ./monTroisiemeScript.sh test
Nombre d'arguments incorrect !!!
$ echo $?
1
$

$ ./monTroisiemeScript.sh test3 coucou
$ echo $?
0
$

La commande [[ ]]

La commande [[ ]] est une version améliorée de la commande test.
Tous les opérateurs utilisés avec la commandes test restent valables à l'exception des opérateurs logiques -a et -o respectivement remplacés par && et ||.

Tests sur les chaines

Contrairement à la commande test, il n'est plus nécessaire d'entourer les variables avec des guillemets.

Les différentes syntaxes utilisables :

$ echo $null
 
$ test -z "$null"
$ echo $?
0
$ [ -z "$null" ]
$ echo $?
0
$ [[ -z $null ]]
$ echo $?
0
$ [[ -z "$null" ]]
$ echo $?
0
$ [[ $null = "" ]]
$ echo $?
0
$

Les opérateurs suivants ont été ajoutés :

Opérateurs Code de retour
$chaine = modele Vrai si $chaine correspond au modèle
$chaine != modele Vrai si $chaine ne correspond pas au modèle
$chaine1 < $chaine2 Vrai si $chaine1 est lexicographiquement avant $chaine2
$chaine1 > $chaine2 Vrai si $chaine1 est lexicographiquement après $chaine2

En utilisant les expressions, il est possible de comparer des chaines à des modèles identiques à celles permettant le remplacement des noms de fichiers.

Caractères spéciaux pour modèles de chaines de caractères Signification
Caractères spéciaux valables dans tous les shells :
* 0 à n caractères
? 1 caractère quelconque
[abc] 1 caractère parmis ceux inscrits entre les crochets
[!abc] 1 caractère ne faisant pas partie de ceux inscrits entre les crochets
Caractères spéciaux non valides en Bourne Shell.
En bash, il faut activer l'option extglob (shopt -s extglob)
?(expression) de 0 à 1 fois l'expression
*(expression) de 0 à n fois l'expression
+(expression) de 1 à n fois l'expression
@(expression) 1 fois l'expression
!(expression) 0 fois l'expression
?(expression1 | expression2 | ...)
*(expression1 | expression2 | ...)
+(expression1 | expression2 | ...)
@(expression1 | expression2 |...)
!(expression1 | expression2 | ...)
alternatives

Exemple :

Le script suivant test si le numéro de téléphone saisi correspond bien au format +33240346523 ou 0240346523

$ nl test_telephone.sh
     1  #! /bin/bash
 
     2  echo -e "Saisir un numéro de téléphone : \c"
     3  read telephone
 
     4  # Test si le téléphone saisi est de la forme
     5  # +33240346523 ou 0240346523
 
     6  if [[ $telephone = @(+33)@([1-9])@([0-9])@([0-9])@([0-9])@([0-9])@([0-9])@([0-9])@([0-9])@([0-9]) ||
     7        $telephone = @(0)@([0-9])@([0-9])@([0-9])@([0-9])@([0-9])@([0-9])@([0-9])@([0-9])@([0-9]) ]]
     8  then
     9          echo "Le numéro est correct"
    10          exit 0
    11  else
    12          echo "Le numéro est incorrect"
    13          exit 1
    14  fi
$ ./test_telephone.sh
Saisir un numéro de téléphone : 0240020202
Le numéro est correct
$ ./test_telephone.sh
Saisir un numéro de téléphone : +33256985478
Le numéro est correct
$ ./test_telephone.sh
Saisir un numéro de téléphone : 2356958457
Le numéro est incorrect
$ ./test_telephone.sh
Saisir un numéro de téléphone : +33025146987
Le numéro est incorrect
$ ./test_telephone.sh
Saisir un numéro de téléphone : g52365
Le numéro est incorrect
$

Tests logiques

Rappels :

  • Les opérateurs -a et -o sont respectivement remplacés par && et ||
  • Les parenthèses n'ont plus besoin d'être protégées
Commande test ( [ ] ) Commande [[ ]] Signification
\(.....\) (.....) Regroupement d'expressions
! ! Négation
-a && ET logique
-o || OU logique

Exemple avec la commande test :

if [ -w $fic1 -a \( -e $rep1 -o -e $rep2 \) ]
then
.....

Exemple avec la commande [[ ]] :

if [[ -w $fic1 && ( -e $rep1 || -e $rep2 ) ]]
then
.....

Les opérateurs du shell

Ces opérateurs permettent d'exécuter ou non une commande en fonction du code de retour d'une autre commande.

Opérateur Signification
&& ET logique
|| OU logique

 

Evaluation de l'opérateur &&

Syntaxe :

commande1 && commande2

La seconde commande (commande2) est exécutée uniquement si le code de retour de la commande (commande1) est égale à 0 (vrai).

Le code de retour global est égal à 0 (vrai) si le code de retour de chaque commande est égal à 0 (vrai).

Exemple :

Le répertoire temp/temp2 n'existe pas donc la commande cd n'est pas exécutée

$ ls -d temp/temp2
ls: impossible d'accéder à temp/temp2: Aucun fichier ou dossier de ce type
$ pwd
/home/toto
$ [[ -d temp/temp2 ]] && cd temp/temp2
$ echo $?                     # code de retour de la commande [[ ]]
1
$ pwd
/home/toto
$

Le répertoire temp/temp2 existe donc la commande cd est exécutée

$ mkdir temp/temp2
$ pwd
/home/toto
$ [[ -d temp/temp2 ]] && cd temp/temp2
$ echo $?                     # code de retour de la commande globale
0
$ pwd
/home/toto/temp/temp2
$

Le répertoire temp/temp2 existe mais le répertoire temp/temp3 n'existe pas donc la commande cd retourne un code erreur égal à 1

$ [[ -d temp/temp2 ]] && cd temp/temp3
-bash: cd: temp/temp3: Aucun fichier ou dossier de ce type
$ echo $?                     # code de retour de la commande cd
1
$

Ces actions peuvent également être exécutées avec la structure de commande if

$ pwd
/home/toto
$ ls -d temp/temp2
temp/temp2
$ if [[ -d temp/temp2 ]]
> then
> cd temp/temp2
> fi
$ echo $?
0
$ pwd
/home/toto/temp/temp2
$

Evaluation de l'opérateur ||

Syntaxe :

commande1 || commande2

La seconde commande (commande2) est exécutée uniquement si le code de retour de la commande (commande1) est égale à 1 (faux).

Le code de retour global est égal à 0 (vrai) si au moins une des commandes retourne un code égal à 0 (vrai).

Exemple :

Le répertoire temp/temp2 n'existe pas donc la commande echo est exécutée

$ ls -d temp/temp2
ls: impossible d'accéder à temp/temp2: Aucun fichier ou dossier de ce type
$ [[ -d temp/temp2 ]] || echo "Le répertoire n'existe pas"
Le répertoire n'existe pas
$ echo $?
0
$

Le répertoire temp/temp2 existe donc la commande echo n'est pas exécutée

$ mkdir temp/temp2
$ ls -d temp/temp2
temp/temp2
$ [[ -d temp/temp2 ]] || echo "Le répertoire n'existe pas"
$ echo $?
0
$

Ces actions peuvent également être exécutées avec la structure de commande if

$ ls -d temp/temp2
ls: impossible d'accéder à temp/temp2: Aucun fichier ou dossier de ce type
$ if [[ ! -d temp/temp2 ]]
> then
> echo "Le répertoire n'existe pas"
> fi
Le répertoire n'existe pas
$

Ne pas confondre les opérateurs du shell && et || qui effectuent une opération logique entre deux commandes et les opérateurs && et || de la commande [[ ]] qui sont internes à celle-ci

L'arithmétique

La commande expr

Syntaxe :

expr $nbr1 opérateur $nbr2
expr $chaine : expression régulière

Opérateurs :

Attention, certains opérateurs ayant une signification particulière pour le shell, ceux ci doivent être protégés par un antislash.

Opérateurs Signification
Opérateurs arithmétiques
$nb1 + $nb2 Addition
$nb1 - $nb2 Soustraction
$nb1 \* $nb2 Multiplication
$nb1 / $nb2 Division
$nb1 % $nb2 Modulo
Opérateurs de comparaison
$nb1 \> $nb2 VRAI si $nb1 est strictement supérieur à $nb2
$nb1 \>= $nb2 VRAI si $nb1 est supérieur ou égal à $nb2
$nb1 \< $nb2 VRAI si $nb1 est strictement inférieur à $nb2
$nb1 \<= $nb2 VRAI si $nb1 est inférieur ou égal à $nb2
$nb1 = $nb2 VRAI si $nb1 est égal à $nb2
$nb1 != $nb2 VRAI si $nb1 est diférent de $nb2
Opérateurs logiques
$chaine1 \& $chaine2 VRAI si les 2 chaines sont vraies
$chaine1 \| $chaine2 VRAI si l'une des 2 chaines est vraie
Opérateurs divers
-$nb1 Opposé de $nb1
\( expression \) Regroupement
$chaine : expression_reguliere Compare la chaine avec l'expression régulière

Les arguments de la commande expr doivent toujours être séparés par au moins un espace ou une tabulation.

Exemple :

$ nb=3
$ expr $nb + 5
8
$ expr $nb \* 6
18
$ expr $nb / 2
1
$ nb=10
$ expr $nb % 3
1
$ expr $nb - -5
15
$

Récupérer le résultat dans une variable :

$ nb2=`expr $nb - 2`
$ echo $nb2
8
$

Priorité d'exécution des opérateurs :

$ nb=5
$ nb2=`expr $nb \* 3 + 4`
$ echo $nb2
19
$ nb2=`expr $nb \* \( 3 + 4 \)`
$ echo $nb2
35
$

Résultat d'une comparaison VS valeur du code de retour :

$ nb=2
$ expr $nb \>= 1
1                            # Résultat de la comparaison
$ echo $?
0                            # Résultat du code de retour
$ expr $nb \>= 3
0                            # Résultat de la comparaison
$ echo $?
1                            # Résultat du code de retour
$

Différents types de comparaison :

$ nl comparaison.sh
     1  #!/bin/bash
 
     2  # Test sur le nombre d'arguments
     3  if [[ $# -ne 2 ]]
     4  then
     5          echo "Mauvais nombre d'arguments"
     6          echo "Utilisation : $0 expr1 expr2"
     7          exit 1
     8  fi
 
     9  # On compare expr1 et expr2 avec la commande EXPR et redirection du résultat dans /dev/null
    10  if expr $1 \> $2 > /dev/null
    11  then
    12          echo "Comparaison EXPR : $1 est supérieur à $2"
    13  else
    14          echo "Comparaison EXPR : $1 est inférieur à $2"
    15  fi
 
    16  # On compare expr1 et expr2 avec la commande [[ ]] (lexicographiquement)
    17  if [[ $1 > $2 ]]
    18  then
    19          echo "Comparaison lexico.. [[ ]] : $1 est supérieur à $2"
    20  else
    21          echo "Comparaison lexico.. [[ ]] : $1 est inférieur à $2"
    22  fi
 
    23  # On compare expr1 et expr2 avec la commande [[ ]]
    24  if [[ $1 -gt $2 ]]
    25  then
    26          echo "Comparaison [[ ]] : $1 est supérieur à $2"
    27  else
    28          echo "Comparaison [[ ]] : $1 est inférieur à $2"
    29  fi
 
    30  exit 0
$ ./comparaison.sh 9 5
Comparaison EXPR : 9 est supérieur à 5
Comparaison lexico.. [[ ]] : 9 est supérieur à 5
Comparaison [[ ]] : 9 est supérieur à 5
$ ./comparaison.sh 50 9
Comparaison EXPR : 50 est supérieur à 9
Comparaison lexico.. [[ ]] : 50 est inférieur à 9
Comparaison [[ ]] : 50 est supérieur à 9
$ ./comparaison.sh a b
Comparaison EXPR : a est inférieur à b
Comparaison lexico.. [[ ]] : a est inférieur à b
Comparaison [[ ]] : a est inférieur à b
$ ./comparaison.sh t r
Comparaison EXPR : t est supérieur à r
Comparaison lexico.. [[ ]] : t est supérieur à r
Comparaison [[ ]] : t est inférieur à r
$

Le résultat de la commande EXPR est toujours exact que ce soit des chiffres ou des caractères.

Etiquettes: 

La commande (( ))

Syntaxe :

((expression_arithmétique))

Utilisation :

La commande (( )) dispose de nombreux avantages par rapport à la commande expr

  • Opérateurs supplémentaires
  • Les arguments n'ont pas besoin d'être séparés par des espaces
  • Les variables n'ont pas besoin d'être préfixées par le symbole $
  • Les caractères spéciaux du shell n'ont pas besoin d'être protégés par des antislashes
  • Les affectations se font dans la commande
  • Son exécution est plus rapide

Une grande partie des opérateurs proviennent du langage C

Opérateurs Signification
Opérateurs arithmétiques
nbr1 + nbr2 Addition
nbr1 - nbr2 Soustraction
nbr1 * nbr2 Multiplication
nbr1 / nbr2 Division
nbr1 % nbr2 Modulo
Opérateurs travaillant sur les bits
~nbr1 Complément à 1
nbr1 >> nbr2 Décalage sur nbr1 de nbr2 bits à droite
nbr1 << nbr2 Décalage sur nbr1 de nbr2 bits à gauche
nbr1 & nbr2 ET bit à bit
nbr1 | nbr2 OU bit à bit
nbr1 ^ nbr2 OU exclusif bit à bit
Opérateurs de comparaison
nbr1 > nbr2 VRAI si nbr1 est strictement supérieur à nbr2
nbr1 >= nbr2 VRAI si nbr1 est supérieur ou égal à nbr2
nbr1 < nbr2 VRAI si nbr1 est strictement inférieur à nbr2
nbr1 <= nbr2 VRAI si nbr1 est inférieur ou égal à nbr2
nbr1 == nbr2 VRAI si nbr1 est égal à nbr2
nbr1 != nbr2 VRAI si nbr1 est différent de nbr2
Opérateurs logiques
!nbr1 Inverse la valeur de vérité de nbr1
&& ET
|| OU
Opérateurs divers
-nbr1 Opposé de nbr1
nbr1 = expression Assignement
(expression) Regroupement
nbr1 binop= nbr2 binop représente l'un des opérateurs suivants : +, -, /, *, %, >>, <<, &, |, ^. Equivalent à nbr1 = nbr1 binop nbr2

Exemples :

Ajouter 10 à nbr1 (2 méthodes différentes)

$ nbr1=10
$ ((nbr1=nbr1+10))
$ echo $nbr1
20
$ nbr1=10
$ ((nbr1+=10))
$ echo $nbr1
20
$

Test si nbr1 est supérieur à nbr2 et inversement

$ nbr1=5
$ nbr2=6
$ ((nbr1>nbr2))
$ echo $?
1                           # Code retour 1 (faux) car nbr1 n'est pas supérieur à nbr2
$ ((nbr1<nbr2))
$ echo $?
0                           # Code retour 0 (vrai) car nbr1 est inférieur à nbr2
$

Le script suivant compare les 2 arguments passés en paramètre

$ nl comparaison2.sh
     1  #!/bin/bash
 
     2  # Test sur le nombre d'arguments
     3  if (($#!=2))
     4  then
     5          echo "Mauvais nombre d'arguments"
     6          echo "Utilisation : $0 nbr1 nbr2"
     7          exit 1
     8  fi
 
     9  # On compare nbr1 et nbr2 avec la commande (( ))
    10  if (($1<$2))
    11  then
    12          echo "$1 est inférieur à $2"
    13  else
    14          if (($1>$2))
    15          then
    16                  echo "$1 est supérieur à $2"
    17          else
    18                  if (($1==$2))
    19                  then
    20                          echo "$1 est égal à $2"
    21                  else
    22                          echo "Comparaison impossible"
    23                  fi
    24          fi
    25  fi
 
    26  exit 0
$ ./comparaison2.sh 2 8
2 est inférieur à 8
$ ./comparaison2.sh 8 2
8 est supérieur à 2
$ ./comparaison2.sh 8 8
8 est égal à 8
$

Regroupements et tests logiques

$ nbr1=2
$ nbr2=5
$ if (( (nbr1>0) && (nbr2>nbr1) ))
> then
> echo "nbr1 est supérieur à 0 et inférieur à nbr2"
> else
> echo "nbr1 est égal à 0 ou supérieur à nbr2"
> fi
nbr1 est supérieur à 0 et inférieur à nbr2
$

La commande let

La commande let est équivalente à ((expression))

Syntaxe :

let "expression"

Exemple :

Multiplier nbr1 par 3

$ nbr1=5
$ let "nbr1=nbr1*3"
$ echo $nbr1
15
$

Calculer le modulo de nbr1 par 2 et l'affecter à la variable nbr2

$ nbr1=5
$ let "nbr2=nbr1%2"
$ echo $nbr1
5
$ echo $nbr2
1
$

Etiquettes: 

Substitution d'expressions arithmétiques

Il existe les caractères de substitution de commandes mais il existe également les caractères de substitution d'expressions arithmétiques.

Syntaxe :

commande argument1 $((expression_arithmetique)) ... argumentn

Exemple :

Rappel sur la substitution de commandes

$ echo "Nombre de users connectes : `who | wc -l`"
Nombre de users connectes : 1
ou
$ echo "Nombre de users connectes : $(who | wc -l)"
Nombre de users connectes : 1

Substitution d'expressions arithmétiques

$ cpt=1
$ ((cpt+=1))                               # Le résultat de la commande n'est pas affichée
$ echo $cpt
2
$

$ cpt=1
$ echo "Nouveau compteur : `((cpt+=1))`"
Nouveau compteur :                  # Le résultat de la commande n'est pas affichée et le compteur n'est pas incrémenté
$ echo $cpt
1
$

$ cpt=1
$ echo "Nouveau compteur : $((cpt+=1))"
Nouveau compteur : 2                # Le résultat de la commande est affiché et le compteur est incrémenté
$ echo $cpt
2
$

Il ne faut pas confondre (( )) et $(( )).
(( )) est une commande interne au shell.
$(( )) sont des caractères spéciaux du shell à l'égal de `` ou $().

Mise au point d'un script

Le shell propose quelques options qui permettent de débugger des scripts shell.

Option -x

L'option -x permet de débugger un script shell en affichant l'exécution des commandes après traitement des caractères spéciaux du shell.

Les différentes syntaxes permettant d'activer l'option -x :

Activer l'option

  1. set -x
  2. set -o xtrace
  3. bash -x nom_du_script

Désactiver l'option

  1. set +x
  2. set +o xtrace

Exemple :

Dans l'exemple suivant, la variable fichier n'a pas été préfixée par le symbole $.

$ nl liste.sh
     1  #!/bin/bash
     2  for fichier in `ls`
     3  do
     4          if [[ -f fichier ]]
     5          then
     6                  echo "$fichier"
     7          fi
     8  done
$

L'exécution du script ne retourne aucun résultat malgré la présence de fichiers dans le dossier d'exécution du script.

$ ./liste.sh
$

L'exécution du script avec activation de l'option -x démontre effectivement que la variable fichier n'est pas remplacée par sa valeur.

$ bash -x liste.sh
++ ls
+ for fichier in '`ls`'
+ [[ -f fichier ]]
+ for fichier in '`ls`'
+ [[ -f fichier ]]
+ for fichier in '`ls`'
+ [[ -f fichier ]]
...
$

L'activation de l'option de débuggage peut également être lancée directement dans le script.

$ nl liste.sh
     1  #!/bin/bash
     2  set -x
     3  for fichier in `ls`
     4  do
     5          if [[ -f fichier ]]
     6          then
     7                  echo "$fichier"
     8          fi
     9  done
$

Après correction de l'erreur et exécution du script en mode débuggage.

$ nl liste.sh
     1  #!/bin/bash
     2  set -x
     3  for fichier in `ls`
     4  do
     5          if [[ -f "$fichier" ]]
     6          then
     7                  echo "$fichier"
     8          fi
     9  done
$ ./liste.sh
++ ls
+ for fichier in '`ls`'
+ [[ -f 1coucou ]]
+ echo 1coucou
1coucou
+ for fichier in '`ls`'
+ [[ -f 24902 ]]
+ for fichier in '`ls`'
+ [[ -f 25013 ]]
+ for fichier in '`ls`'
+ [[ -f 25031 ]]
+ for fichier in '`ls`'
+ [[ -f 25043 ]]
+ for fichier in '`ls`'
+ [[ -f comparaison2.sh ]]
+ echo comparaison2.sh
comparaison2.sh
...
$

 

Autres options

Fonction Bourne shell, ksh, bash ksh, bash
Lecture des commandes sans exécution et détection des erreurs de syntaxe. set -n
set +n
set -o noexec
set +o noexec
Affichage des commandes avant substitution des caractères spéciaux du shell set -v
set +v
set -o verbose
set +o verbose

Exemple :

Dans l'exemple suivant, un guillemet a été volontairement omis.

$ nl liste.sh
     1  #!/bin/bash
     2  set -n
     3  for fichier in `ls`
     4  do
     5          if [[ -f "$fichier ]]
     6          then
     7                  echo "$fichier"
     8          fi
     9  done
$ ./liste.sh
./liste.sh: line 7: Caractère de fin de fichier (EOF) prématuré lors de la recherche du « " » correspondant
./liste.sh: line 10: argument inattendu pour l'opérateur conditionnel à un argument
./liste.sh: line 10: Erreur de syntaxe : fin de fichier prématurée
$

$ nl liste.sh
     1  #!/bin/bash
     2  set -v
     3  for fichier in `ls`
     4  do
     5          if [[ -f "$fichier ]]
     6          then
     7                  echo "$fichier"
     8          fi
     9  done
$ ./liste.sh
for fichier in `ls`
do
        if [[ -f "$fichier ]]
        then
                echo "$fichier"
        fi
done
./liste.sh: line 7: Caractère de fin de fichier (EOF) prématuré lors de la recherche du « " » correspondant
./liste.sh: line 10: argument inattendu pour l'opérateur conditionnel à un argument
./liste.sh: line 10: Erreur de syntaxe : fin de fichier prématurée
$

Les structures de contrôle

if

La structure de controle if permet de réaliser des tests.
La commande située à droite du if est exécutée.
Si le code retour de la commande ($?) est égal à 0 (vrai), les commandes situées dans le bloc then sont exécutées.
Si le code de retour est supérieur à 0 (faux), ce sont les commandes situées dans le bloc else (optionnel) qui sont exécutées.
Dans le cas où le bloc else n'est pas spécifié, le shell continue à la première commande située sous le fi.

Les différentes syntaxes :

if, then, else, fi

if commande1
then
     commande2
     commande3
     ...
else
     commande4
     ...
fi

if, then, fi

if commande1
then
     commande2
     commande3
     ...
fi

if, then, elif, else, fi

if commande1
then
     commande2
     commande3
     ...
elif commande4
then
     commande5
     ...
else
     commande6
     ...
fi

Le mot clé fi permet de fermer la structure de controle if.
Le mot clé elif n'a pas de fermeture.

Autres syntaxes :

Le mot clé then peut être placé sur la même ligne que le if à condition de les séparer à l'aide d'un point virgule.

if commande1 ; then
     commande2
     commande3
     ...
fi

Plusieurs structures de controles if peuvent être imbriquées les unes dans les autres.

if commande1
then
     commande2
     ...
else
     if commande3
     then
          commande4
          ...
     else
          commande5
          ...
     fi
fi

Exemple :

Le script suivant vérifie si un argument est passé en paramètre et dans le cas contraire demande une saisie clavier à l'utilisateur.
Le script vérifie ensuite que le user saisi existe bien dans le fichier /etc/passwd.

$ nl user_passwd.sh
     1  #!/bin/bash
     2  if [[ $# -ne 1 ]]
     3  then
     4          echo -e "Saisir le nom d'un user : \c"
     5          read user
     6  else
     7          user=$1
     8  fi
     9  if grep -q "^$user:" /etc/passwd
    10  then
    11          echo "Le user $user existe"
    12          echo "Son UID est le : $(grep "^$user:" /etc/passwd | cut -d":" -f3)"
    13          echo "Son GID est le : $(grep "^$user:" /etc/passwd | cut -d":" -f4)"
    14  else
    15          echo "Le user $user n'existe pas !!!"
    16  fi
    17  exit 0
$ ./user_passwd.sh root
Le user root existe
Son UID est le : 0
Son GID est le : 0
$ ./user_passwd.sh
Saisir le nom d'un user : www-data
Le user www-data existe
Son UID est le : 33
Son GID est le : 33
$

Etiquettes: 

case

La structure de controle case permet elle aussi d'effectuer des tests.
Elle permet d'orienter la suite du programme en fonction d'un choix de différentes valeurs.
Quand il y a un nombre important de choix, la commande case est plus appropriée que la commande if.

Syntaxe :

case $variable in
modele1) commande1
     ...
     ;;
modele2) commande2
     ...
     ;;
modele3 | modele4 | modele5 ) commande3
     ...
     ;;
esac

Le shell compare la valeur de la variable aux différents modèle renseignés.
Lorsque la valeur correspond au modèle, les commandes faisant partie du bloc sont exécutées.
Les caractères ;; permettent de fermer le bloc et de mettre fin au case.
Le shell continue à la première commande située sous esac.

Il ne faut surtout pas oublier les caractères ;; car cela engendrera une erreur.

Rappel des caractères spéciaux :

Caractères spéciaux pour modèles de chaines de caractères Signification
Caractères spéciaux valables dans tous les shells :
* 0 à n caractères
? 1 caractère quelconque
[abc] 1 caractère parmis ceux inscrits entre les crochets
[!abc] 1 caractère ne faisant pas partie de ceux inscrits entre les crochets
Caractères spéciaux non valides en Bourne Shell.
En bash, il faut activer l'option extglob (shopt -s extglob)
?(expression) de 0 à 1 fois l'expression
*(expression) de 0 à n fois l'expression
+(expression) de 1 à n fois l'expression
@(expression) 1 fois l'expression
!(expression) 0 fois l'expression
?(expression1 | expression2 | ...)
*(expression1 | expression2 | ...)
+(expression1 | expression2 | ...)
@(expression1 | expression2 |...)
!(expression1 | expression2 | ...)
alternatives

Exemple :

Le script suivant permet de créer, modifier, visualiser et supprimer un fichier dans le répertoire d'exécution du script.
Il prend en argument un nom de fichier et affiche un menu.
Utilisation de case avec imbrication de if.

$ nl file.sh
     1  #!/bin/bash
     2  #set -x
     3  # Si le nombre d'arguments est different de 1 on quitte avec code 1
     4  if [[ $# -ne 1 ]]
     5  then
     6          echo "Nombre d'arguments incorrect"
     7          echo "Usage : $0 file"
     8          exit 1
     9  fi
    10  # On affiche le menu
    11  echo -e "1(Creer) "
    12  echo -e "2(Editer) "
    13  echo -e "3(Afficher) "
    14  echo -e "4(Supprimer)"
    15  echo -e "Votre choix : \c"
    16  # On recupere la valeur saisi
    17  read choix
    18  # Si la valeur saisi est differente de 1, 2, 3 ou 4 on quitte avec code 1
    19  if [[ "$choix" != [1-4] ]]
    20  then
    21          echo "Choix incorrect"
    22          exit 1
    23  fi
    24  # En fonction de la valeur saisi on execute les differentes actions
    25  case "$choix" in
    26  # Si choix = 1 --> creation
    27  1)      if [[ -e "$1" ]]
    28          then
    29                  if [[ -f "$1" ]]
    30                  then
    31                          echo "Fichier $1 deja existant"
    32                  elif [[ -d "$1" ]]
    33                  then
    34                          echo "$1 est un repertoire"
    35                  fi
    36                  exit 1
    37          else
    38                  touch "$1"
    39                  nano "$1"
    40          fi
    41  ;;
    42  # Si choix = 2 --> edition
    43  2)      if [[ -f "$1" ]]
    44          then
    45                  nano "$1"
    46          else
    47                  if [[ -d "$1" ]]
    48                  then
    49                          echo "$1 est un repertoire et ne peut etre edite"
    50                  else
    51                          echo "Fichier $1 inexistant"
    52                  fi
    53                  exit 1
    54          fi
    55  ;;
    56  # Si choix = 3 --> affichage
    57  3)      if [[ -f "$1" ]]
    58          then
    59                  more "$1"
    60          else
    61                  if [[ -d "$1" ]]
    62                  then
    63                          echo "$1 est un repertoire et ne peut etre visualise"
    64                  else
    65                          echo "Fichier $1 inexistant"
    66                  fi
    67                  exit 1
    68          fi
    69  ;;
    70  # Si choix = 4 --> suppression
    71  4)      if [[ -f "$1" ]]
    72          then
    73                  rm "$1"
    74          else
    75                  if [[ -d "$1" ]]
    76                  then
    77                          echo "$1 est un repertoire et ne peut etre supprime"
    78                  else
    79                          echo "Fichier $1 inexistant"
    80                  fi
    81                  exit 1
    82          fi
    83  ;;
    84  # Fin du case
    85  esac
    86  # Tout c'est bien deroule on quitte avec le code 0
    87  exit 0
$ ./file.sh test4
1(Creer)
2(Editer)
3(Afficher)
4(Supprimer)
Votre choix : 1
$

Etiquettes: 

Boucle for

Syntaxe :

La boucle for permet de traiter une liste de valeurs indiquée à droite du mot clé in.
A chaque tour de boucle, la variable var est initialisée avec une des valeurs de la liste.
Elles sont traitées dans l'ordre de leur énumération.

Liste de valeurs citée directement

for var in valeur1 valeur2 valeur3 ... valeurn
do
     commande
done

Liste de valeurs contenue dans une variable

for var in $variable
do
     commande
done

Liste de valeurs générée par substitution de commande

for var in `commande`
do
     commande
done

Liste de valeurs générée par substitution de caractères de génération de noms de fichiers

for var in *.ext
do
     commande
done

Liste par défaut : Arguments de la ligne de commande

for var
do
     commande
done

for var in $*
do
     commande
done

Avec incrémentation d'une variable

for (( var=valeurMin; var<=valeurMax; var++ ))
do
     commande
done

Exemple :

Un script compte à rebours

$ nl boucleFor01.sh
     1  #!/bin/bash
     2  for var in 10 9 8 7 6 5 4 3 2 1 0
     3  do
     4          echo "$var"
     5  done
     6  exit 0
$ ./boucleFor01.sh
10
9
8
7
6
5
...
$

Lister les fichiers d'un ou plusieurs dossiers

$ nl boucleFor02.sh
     1  #!/bin/bash
     2  if [[ $# -lt 1 ]]
     3  then
     4          echo "Nombre d'argument incorrect"
     5          echo "Utilisation $0 dossier1 dossier2 dossiern"
     6          exit 1
     7  fi
     8  for dossier in $*
     9  do
    10          if [[ -d $dossier ]]
    11          then
    12                  echo "Liste des fichiers du dossier $dossier"
    13                  for fichier in `ls $dossier`
    14                  do
    15                          echo "$fichier"
    16                  done
    17          fi
    18  done
    19  exit 0
$ ./boucleFor02.sh coucou 24902 25013 25031
Liste des fichiers du dossier coucou
test
Liste des fichiers du dossier 24902
fichier_0
fichier_1
fichier_2
fichier_3
Liste des fichiers du dossier 25013
fichier_0
fichier_1
fichier_2
Liste des fichiers du dossier 25031
fichier_0
fichier_1
fichier_2
fichier_3
$

En utilisant une variable incrémentée :

$ for (( i=0; i <= 10; i++ )); do echo $i; done
0
1
2
3
4
5
6
7
8
9
10
$

Idem avec la syntaxe suivante :

$ for i in {0..10}; do echo $i; done
0
1
2
3
4
5
6
7
8
9
10
$

Etiquettes: 

Boucle while

Syntaxe :

while commande1
do
     commande2
     ...
done

La boucle while permet d'exécuter les commandes présentes entre le do et le done tant que la commande1 placée à droite du while retourne un code vrai.

Exemple :

Le script suivant demande de saisir 53 et continue tant que c'est faux

$ nl boucleWhile01.sh
     1  #!/bin/bash
     2  nbr=0
     3  while ((nbr!=53))
     4  do
     5          echo -e "Saisir 53 : \c"
     6          read nbr
     7
     8  done
     9  exit 0
$ ./boucleWhile01.sh
Saisir 53 : rt
Saisir 53 : 54
Saisir 53 : R4
Saisir 53 : 53
$

Le script suivant affiche le compteur tant qu'il est inférieur à 10

$ nl boucleWhile02.sh
     1  #!/bin/bash
     2  cpt=0
     3  while ((cpt<10))
     4  do
     5          echo "Le compteur vaut : $cpt"
     6          ((cpt+=1))
     7  done
     8  exit 0
$ ./boucleWhile02.sh
Le compteur vaut : 0
Le compteur vaut : 1
Le compteur vaut : 2
Le compteur vaut : 3
Le compteur vaut : 4
Le compteur vaut : 5
Le compteur vaut : 6
Le compteur vaut : 7
Le compteur vaut : 8
Le compteur vaut : 9
$

Le script suivant effectue une somme des nombres saisis

$ nl boucleWhile03.sh
     1  #!/bin/bash
     2  somme=0
     3  echo "Saisir un nombre, ^d pour afficher la somme"
     4  while read nombre
     5  do
     6          if [[ $nombre != +([0-9]) ]]
     7          then
     8                  echo "$nombre n'est pas un nombre"
     9                  continue
    10          fi
    11          ((somme+=nombre))
    12  done
    13  echo "La somme est de : $somme"
    14  exit 0
$ ./boucleWhile03.sh
Saisir un nombre, ^d pour afficher la somme
56
32
89
9.6
9.6 n'est pas un nombre
g8
g8 n'est pas un nombre
54
La somme est de : 231
$

Le mot clé continue permet de remonter aussitôt à la boucle while sans exécuter la commande suivante

Attention aux boucles infinies

Ce script provoqe une boucle infinie car il manque l'incrémentation du compteur

$ nl boucleWhile04.sh
     1  #!/bin/bash
     2  cpt=0
     3  while ((cpt<10))
     4  do
     5          echo "Le compteur vaut : $cpt"
     6  done
     7  exit 0
$

Le shell propose également la commande interne : qui renvoie toujours vrai et permet donc de faire une boucle infinie avec un while.

$ nl boucleWhile05.sh
     1  #!/bin/bash
     2  while :
     3  do
     4          echo "Boucle infinie"
     5  done
     6  exit 0
$

En bash et ksh, la commande true propose exactement la même chose.

$ nl boucleWhile05.sh
     1  #!/bin/bash
     2  while true
     3  do
     4          echo "Boucle infinie"
     5  done
     6  exit 0
$

Etiquettes: 

until

Syntaxe :

until commande1
do
     commande2
     ...
done

A l'inverse de while, la commande until exécute les commandes situées entre le do et le done tant que la commande située à droite du until retourne un code faux.

Exemple :

Le script suivant boucle tant que le nombre saisi n'est pas égal à 53

$ nl boucleUntil01.sh
     1  #!/bin/bash
     2  nbr=0
     3  until ((nbr==53))
     4  do
     5          echo -e "Saisir 53 : \c"
     6          read nbr
     7  done
     8  exit 0
$ ./boucleUntil01.sh
Saisir 53 : 45
Saisir 53 : rt
Saisir 53 : fd
Saisir 53 : 53
$

Le script suivant permet, en tâche de fond, de surveiller un répertoire donné et d'informer l'utilisateur de l'arrivée d'un fichier attendu dans ce répertoire.
Pour plus de sécurité sur l'intégrité du fichier attendu, un fichier témoin devra être créé à la suite du fichier attendu puisque le principal contrôle se fera sur l'existence de ce fichier.

$ nl boucleUntil02.sh
     1    #!/bin/bash
     2    # Il doit y avoir au minimum 2 paramètres en arguments et un maximum de 3
     3    if [[ $# -lt 2 || $# -gt 3 ]]
     4    then
     5        echo "Utilisation : $0 repertoire fichier [ temoin ]"
     6        exit 1
     7    fi
     8    # Le premier argument doit être un répertoire
     9    if [[ ! -d $1 ]]
    10    then
    11        echo "$1 n'est pas un répertoire"
    12        exit 2
    13    fi
    14    # Nom du fichier témoin par défaut
    15    ficTemoin=${3:-temoin}
    16    # Exécution de la boucle en attendant l'arrivée du fichier témoin avec une pause toutes les 3 secondes
    17    until [[ -e $1/$ficTemoin ]]
    18    do
    19        sleep 3
    20    done
    21    # Vérification que le fichier attendu est bien présent
    22    if [[ ! -e $1/$2 ]]
    23    then
    24        echo "Le fichier témoin existe mais le fichier attendu est absent"
    25        exit 3
    26    fi
    27    # Sauvegarde du fichier attendu dans le HOME de l'utilisateur avec horodatage et suppression du fichier témoin
    28    date=$(date '+%Y%m%d_%H%M')
    29    newFichier=$2.$date
    30    mv $1/$2 $HOME/$newFichier
    31    rm $1/$ficTemoin
    32    # Envoi d'un mail à l'utilisateur
    33    mail $LOGNAME <<FIN
    34    Le fichier $HOME/$newFichier est bien arrivé.
    35    FIN
    36    echo "$0 : Vous avez reçu un message !!! "
    37    exit 0
$ ./boucleUntil02.sh /tmp monFichier &     # Lancement du script en arrière plan grâce à la commande &
[1] 2298
$ touch /tmp/monFichier                                   # Création du fichier attendu
$ touch /tmp/temoin                                           # Création du fichier témoin
$ ./boucleUntil02.sh : Vous avez reçu un message !!!     # Message généré par la ligne 36
 
[1]+  Done                    ./boucleUntil02.sh /tmp monFichier
$

il ne reste plus qu'à consulter sa boite mail pour lire le message envoyé par le script.

Etiquettes: 

break et continue

Les commandes break et continue peuvent s'utiliser à l'intérieur des boucles for, while, until et select.
La commande break permet de sortir d'une boucle.
La commande continue permet de remonter à la condition d'une boucle.

Syntaxe :

Quitter la boucle de premier niveau
break

Quitter la boucle de niveau n
break n

Remonter à la condition de la boucle de premier niveau
continue

Remonter à la condition de la boucle de niveau n
continue n

Exemple :

$ nl boucleWhile06.sh
     1    #!/bin/bash
     2    somme=0
     3    while true
     4    do
     5        echo "Saisir un nombre, ^d pour afficher la somme"
     6        if read nombre
     7        then
     8            if [[ $nombre != +([0-9]) ]]
     9            then
    10                echo "$nombre n'est pas un nombre"
    11                continue
    12            fi
    13            ((somme+=nombre))
    14        else
    15            break
    16        fi
    17    done
    18    echo "La somme est de : $somme"
    19    exit 0
$ ./boucleWhile06.sh
Saisir un nombre, ^d pour afficher la somme
23
Saisir un nombre, ^d pour afficher la somme
56
Saisir un nombre, ^d pour afficher la somme
54
Saisir un nombre, ^d pour afficher la somme
89
Saisir un nombre, ^d pour afficher la somme
La somme est de : 222
$

Etiquettes: 

Aspects avancés de la programmation shell

Comparatif des variables $* et $@

Utilisation de $* et de $@

Les variables $* et $@ contiennent la liste des arguments d'un script shell.
Lorsqu'elles ne sont pas entourées par des guillemets, elles sont équivalentes.

Exemple :

$ nl scr01.sh
     1  #!/bin/bash
     2  cpt=1
     3  echo "Utilisation de la variable \$*"
     4  for arg in $*
     5  do
     6          echo "Argument $cpt : $arg"
     7          ((cpt+=1))
     8  done
     9  cpt=1
    10  echo "Utilisation de la variable \$@"
    11  for arg in $@
    12  do
    13          echo "Argument $cpt : $arg"
    14          ((cpt+=1))
    15  done
    16  exit 0
$ ./scr01.sh a b "c d e" f
Utilisation de la variable $*
Argument 1 : a
Argument 2 : b
Argument 3 : c
Argument 4 : d
Argument 5 : e
Argument 6 : f
Utilisation de la variable $@
Argument 1 : a
Argument 2 : b
Argument 3 : c
Argument 4 : d
Argument 5 : e
Argument 6 : f
$

Interprétation :

  1. $* et $@ contiennent exactement la même liste d'arguments.
  2. Les guillemets protégeant les arguments ne sont pas pris en compte
  3. Ce sont les espaces qui délimitent les arguments

Utilisation de "$*"

Les guillemets autour de $* supprime la signification des espaces contenus dans $*.

Exemple :

$ nl scr02.sh
     1  #!/bin/bash
     2  cpt=1
     3  echo "Utilisation de la variable \"\$*\""
     4  for arg in "$*"
     5  do
     6          echo "Argument $cpt : $arg"
     7          ((cpt+=1))
     8  done
     9  exit 0
$ ./scr02.sh a b c "d e f" g
Utilisation de la variable "$*"
Argument 1 : a b c d e f g
$

Interprétation :

  1. Les guillemets entourant les arguments ne sont pas pris en compte.
  2. Tous les arguments sont considérés comme étant un seul argument.

Utilisation de "$@"

La variable $@ placée entre guillemets permet de conserver la protection des arguments par les guillemets.

Exemple :

$ nl scr03.sh
     1  #!/bin/bash
     2  cpt=1
     3  echo "Utilisation de la variable \"\$@\""
     4  for arg in "$@"
     5  do
     6          echo "Argument $cpt : $arg"
     7          ((cpt+=1))
     8  done
     9  exit 0
$ ./scr03.sh a b c "d e f" g
Utilisation de la variable "$@"
Argument 1 : a
Argument 2 : b
Argument 3 : c
Argument 4 : d e f
Argument 5 : g
$

Interprétation :

  1. Les espaces délimitent la liste des arguments.
  2. Les arguments placés entre guillemets sont considérés comme étant un seul argument

Substitution de variables

Les shells BASH et KSH offrent des fonctionnalités supplémentaires au niveau des substitution de variables.

Connaitre la longueur d'une chaine d'une variable :

Syntaxe :

${#variable}

Exemple :

$ variable="Une chaine de texte"
$ echo $variable
Une chaine de texte
$ echo "La chaine contient ${#variable} caractères"
La chaine contient 19 caractères
$

Manipuler des chaines de caractères :

Exemple avec la variable suivante :

$ variable="col1|col2|col3|col4|col5"
$ echo $variable
col1|col2|col3|col4|col5
$

Cette variable est constituée de 5 segments délimités par le caractère |

Le contenu de la variable n'est jamais modifié

Exclure le premier segment

Syntaxe :

${variable#modele}

modele est une chaine de caractère incluant les caractères spéciaux *, ?, [ ], ?(expression), +(expression), *(expression), @(expression), !(expression)

Rappel des expressions pour la substitution des noms de fichiers

Le caractère # signifie "Chaine la plus courte possible en partant de la gauche"

Exemple :

$ echo ${variable#*|}
col2|col3|col4|col5
$

Conserver le dernier segment

Syntaxe :

${variable##modele}

Les caractères ## signifient "Chaine la plus longue possible en partant de la gauche"

Exemple :

$ echo ${variable##*|}
col5
$

Exclure le dernier segment

Syntaxe :

${variable%modele}

Le caractère % signifie "Chaine la plus courte possible en partant de la droite"

Exemple :

$ echo ${variable%|*}
col1|col2|col3|col4
$

Conserver le premier segment

Syntaxe :

${variable%%modele}

Les caractères %% signifient "Chaine la plus longue possible en partant de la droite"

Exemple :

$ echo ${variable%%|*}
col1
$

En bash, ne pas oublier d'activer l'option extglob avec la commande shopt -s extglob afin de permettre au shell d'interpréter les modèles utilisant les expressions complexes.

Tableaux

Avec les shells récents, il est possible d'utiliser des tableaux à 1 dimension.
Les éléments du tableau commencent toujours à l'indice 0.

Ajouter un élément à un tableau :

Syntaxe :

tableau[indice]=valeur

Exemple :

$ tableau[0]=15
$ tableau[3]=20
$

Les indices non initialisés sont vides.

Afficher un élément d'un tableau :

Syntaxe :

${tableau[indice]}

Exemple :

$ echo ${tableau[3]}
20
$ echo ${tableau[0]}
15
$ echo ${tableau[2]}
 
$

L'indice 2 du tableau est vide.

Initialiser un tableau avec plusieurs valeurs :

Syntaxe :

tableau=(valeur1 valeur2 valeur3 ..... valeurn)

Exemple :

$ tableau=(02 40 35 68 98 45 52 03)
$ echo ${tableau[0]}
02
$ echo ${tableau[2]}
35
$

Toutes les précédentes valeurs contenues dans le tableaux sont effacées.

Afficher tous les éléments d'un tableau :

Syntaxe :

${tableau[*]}

Exemple :

$ echo ${tableau[*]}
02 40 35 68 98 45 52 03
$

Afficher le nombre d'éléments d'un tableau :

Syntaxe :

${#tableau[*]}

Exemple :

$ echo ${#tableau[*]}
8
$

Obtenir la longueur d'un élément d'un tableau :

Syntaxe :

${#tableau[indice]}

Exemple :

$ echo ${#tableau[0]}
2
$

Parcourir tous les éléments d'un tableau :

$ tab=("un" "deux" "trois")
$ for m in ${tab[@]}; do echo $m; done
un
deux
trois
$

Initialisation des paramètres positionnels avec set

La commande set utilisée sans option mais suivie d'arguments affecte ces derniers aux paramètres positionnels $1, $2, $3 ....., $*, $@ et $#.

Cela permet de manipuler plus facilement le résultat de diverses substitutions.

Exemple :

$ ls
fichier_0  fichier_2  fichier_4  fichier_6  fichier_8
fichier_1  fichier_3  fichier_5  fichier_7  fichier_9
$ set `ls`
$

La liste des fichiers obtenue avec la commande ls est maintenant affecté aux paramètres positionnels.

$ echo $#
10
$ echo $*
fichier_0 fichier_1 fichier_2 fichier_3 fichier_4 fichier_5 fichier_6 fichier_7 fichier_8 fichier_9
$ echo $1
fichier_0
$ echo $3
fichier_2
$ echo $9
fichier_8
$ echo ${10}
fichier_9
$

Les fonctions

Les fonctions permettent de regrouper et d'exécuter des commandes à différents endroits d'un script.

Cela permet de créer des fonctions personnalisées réutilisables.

Définition d'une fonction

Une fonction doit être définie au début d'un script, avant sa première utilisation.

Il existe 2 syntaxes permettant de définir une fonction.

Première syntaxe :

Ce sont les doubles parenthèses ( ) qui indique au shell la définition d'une fonction

Définition d'une fonction

maFonction () {
     commande1
     commande2
     .....
}

Appel d'une fonction

maFonction

Seconde syntaxe :

Le mot clé function remplace les doubles parenthèses ( )

Définition d'une fonction

function maFonction {
     commande1
     commande2
     .....
}

Appel d'une fonction

maFonction

Une fonction peut être appelée aussi bien à partir du programme principal qu'à partir d'une autre fonction.

Exemple :

$ nl fonction01.sh
     1  #!/bin/bash
 
     2  fctn01 () {
     3          echo "Fonction fctn01"
     4  }
 
     5  function fctn02 {
     6          echo "Fonction fctn02"
     7  }
 
     8  echo "Début du programme principal"
     9  echo "Appel de la fonction fctn01"
    10  fctn01
    11  echo "Appel de la fonction fctn02"
    12  fctn02
    13  echo "Fin du programme principal"
    14  exit 0
$ ./fonction01.sh
Début du programme principal
Appel de la fonction fctn01
Fonction fctn01
Appel de la fonction fctn02
Fonction fctn02
Fin du programme principal
$

Dès qu'une fonction est définie, celle-ci est considérée par le shell comme étant une commande interne.

Code de retour d'une fonction

Comme toutes commandes Linux, une fonction retourne également un code d'erreur.
Si le code erreur n'est pas spécifié, celui retourné par défaut correspond au code erreur de la dernière commande exécutée dans la fonction.
La commande return permet de retourner le code erreur de la fonction concernée. Ce code doit obligatoirement correspondre à un nombre compris entre 0 et 255.
Le code erreur retourné par la fonction est récupérable grâce à la variable $?.

Exemple :

Le script suivant test si l'utilisateur saisi existe sur le système.

$ nl fonction02.sh
     1  #!/bin/bash
 
     2  function pause {
     3          echo "Appuyer sur Entrée pour continuer"
     4          read x
     5  }
 
     6  function existUser {
     7          echo -e "Saisir le nom d'un utilisateur : \c"
     8          read user
     9          if grep -q "^$user:" /etc/passwd ; then
    10                  return 0
    11          fi
    12          return 1
    13  }
 
    14  while true
    15  do
    16          clear
    17          echo "- 1 - Savoir si un utilisateur existe"
    18          echo "- 2 - Connaitre l'UID d'un utilisateur"
    19          echo "- 3 - Fin"
    20          echo -e "Votre choix : \c"
    21          read choix
    22          case $choix in
    23                  1)      if existUser
    24                          then
    25                                  echo "L'utilisateur $user existe"
    26                          else
    27                                  echo "l'utilisateur $user n'existe pas"
    28                          fi
    29                          ;;
 
    30                  2)      echo "Option non disponible"
    31                          ;;
 
    32                  3)      exit 0
    33                          ;;
    34          esac
    35          pause
    36  done
$

Portée des variables

Dans un script shell, sans définition particulière, toutes les variables utilisées sont globales à tout le script.

Qu'une variable soit définie au niveau du programme principal ou d'une fonction, elle est accessible n'importe où dans le script.

Par exemple, dans le script fonction02.sh, la variable $user est initialisée dans la fonction existUser (ligne 8) et utilisée également au niveau du programme principal (ligne 25 et 27).

$ nl fonction02.sh
     1  #!/bin/bash
 
     2  function pause {
     3          echo "Appuyer sur Entrée pour continuer"
     4          read x
     5  }
 
     6  function existUser {
     7          echo -e "Saisir le nom d'un utilisateur : \c"
     8          read user
     9          if grep -q "^$user:" /etc/passwd ; then
    10                  return 0
    11          fi
    12          return 1
    13  }
 
    14  while true
    15  do
    16          clear
    17          echo "- 1 - Savoir si un utilisateur existe"
    18          echo "- 2 - Connaitre l'UID d'un utilisateur"
    19          echo "- 3 - Fin"
    20          echo -e "Votre choix : \c"
    21          read choix
    22          case $choix in
    23                  1)      if existUser
    24                          then
    25                                  echo "L'utilisateur $user existe"
    26                          else
    27                                  echo "l'utilisateur $user n'existe pas"
    28                          fi
    29                          ;;
 
    30                  2)      echo "Option non disponible"
    31                          ;;
 
    32                  3)      exit 0
    33                          ;;
    34          esac
    35          pause
    36  done
$

Définition de variables locales

La commande typeset permet de définir des variables locales à une fonction.

Syntaxe :

typeset variable
typeset variable=valeur

Exemple :

$ nl fonction03.sh
     1  #!/bin/bash
     2  function f1 {
     3          # var1 est une variable locale
     4          typeset var1
     5          echo "Dans la fonction f1 => var1 avant : $var1"
     6          var1=100
     7          echo "Dans la fonction f1 => var1 après : $var1"
     8          echo "Dans la fonction f1 => var2 avant : $var2"
     9          var2=200
    10          echo "Dans la fonction f1 => var2 après : $var2"
    11  }
    12  # var1 et var2 sont des variables globales
    13  var1=1
    14  var2=2
    15  echo "Dans le programme principal => var1 avant appel f1 : $var1"
    16  echo "Dans le programme principal => var2 avant appel f1 : $var2"
    17  f1
    18  echo "Dans le programme principal => var1 après appel f1 : $var1"
    19  echo "Dans le programme principal => var2 après appel f1 : $var2"
    20  exit 0
$ ./fonction03.sh
Dans le programme principal => var1 avant appel f1 : 1
Dans le programme principal => var2 avant appel f1 : 2
Dans la fonction f1 => var1 avant :
Dans la fonction f1 => var1 après : 100
Dans la fonction f1 => var2 avant : 2
Dans la fonction f1 => var2 après : 200
Dans le programme principal => var1 après appel f1 : 1
Dans le programme principal => var2 après appel f1 : 200
$

2 variables globales var1 et var2 sont définies et initialisées ligne 13 et 14.
1 variable locale var1 est définie dans la fonction ligne 4 et initialisée à 100 ligne 6.
Après exécution de la fonction f1, la variable globale var1 a conservée sa valeur (1) alors que la variable globale var2 a été modifiée.

Passage d'arguments

Dans un script shell, il est tout à fait possible de passer des arguments à une fonction étant donné qu'une fonction est reconnue par le shell comme étant une commande à part entière.

Ces arguments sont récupérables dans les fonctions grâce aux variables spéciales $1, $2, $3, ....., ${10} ......$*, $@ et $#. Ces variables sont aussi locales aux fonctions.

Par contre, la variable $0 contient toujours le nom du script.

Exemple :

$ nl fonction04.sh
     1  #!/bin/bash
 
     2  function f1 {
     3          echo "Arguments de la fonction f1 :"
     4          echo "\$0 => $0"
     5          echo "\$1 => $1"
     6          echo "\$2 => $2"
     7          echo "\$3 => $3"
     8          echo "\$* => $*"
     9          echo "\$# => $#"
    10  }
 
    11  function f2 {
    12          echo "Arguments de la fonction f2 :"
    13          echo "\$0 => $0"
    14          echo "\$1 => $1"
    15          echo "\$2 => $2"
    16          echo "\$3 => $3"
    17          echo "\$* => $*"
    18          echo "\$# => $#"
    19  }
 
    20  function f3 {
    21          echo "Arguments de la fonction f3 :"
    22          echo "\$0 => $0"
    23          echo "\$1 => $1"
    24          echo "\$2 => $2"
    25          echo "\$3 => $3"
    26          echo "\$* => $*"
    27          echo "\$# => $#"
    28  }
 
    29  echo "Arguments du programme principal :"
    30  echo "\$0 => $0"
    31  echo "\$1 => $1"
    32  echo "\$2 => $2"
    33  echo "\$3 => $3"
    34  echo "\$* => $*"
    35  echo "\$# => $#"
 
    36  # Appel de la fonction f1 avec 3 arguments
    37  f1 a b c
 
    38  # Appel de la fonction f2 avec 3 arguments
    39  f2 file.c 2000 500
 
    40  # Appel de la fonction f3 avec 2 arguments provenant du programme principal
    41  f3 $2 $3
 
    42  exit 0
$

Appel du script fonction04.sh avec 3 arguments :

$ ./fonction04.sh arg1 arg2 arg3
Arguments du programme principal :
$0 => ./fonction04.sh
$1 => arg1
$2 => arg2
$3 => arg3
$* => arg1 arg2 arg3
$# => 3
Arguments de la fonction f1 :
$0 => ./fonction04.sh
$1 => a
$2 => b
$3 => c
$* => a b c
$# => 3
Arguments de la fonction f2 :
$0 => ./fonction04.sh
$1 => file.c
$2 => 2000
$3 => 500
$* => file.c 2000 500
$# => 3
Arguments de la fonction f3 :
$0 => ./fonction04.sh
$1 => arg2
$2 => arg3
$3 =>
$* => arg2 arg3
$# => 2
$

Exploiter l'affichage d'une fonction

Comme n'importe quelle commande renvoyant un résultat, une fonction peut également être placée à l'intérieur de caractères de substitution de commande `` ou $( ).

Exemple :

$ nl fonction05.sh
     1  #!/bin/bash
 
     2  function getUid {
     3          grep "^$1:" /etc/passwd | cut -d':' -f3
     4  }
 
     5  # Initialisation de la variable globale uid
     6  uid=""
 
     7  # Appel de la fonction getUid avec l'argument du programme principal
     8  # Juste pour l'affichage
     9  getUid $1
 
    10  # Affectation du résultat de la fonction getUid à la variable uid
    11  uid=$(getUid $1)
 
    12  if [[ $uid != "" ]]
    13  then
    14          echo "L'utilisateur $1 a pour UID : $uid"
    15  else
    16          echo "L'utilisateur $1 n'existe pas"
    17  fi
 
    18  exit 0
$ ./fonction05.sh root
0
L'utilisateur root a pour UID : 0
$

Exemple complet

Exemple d'un script reprenant toutes les commandes et fonctions vues précédement

$ nl fonction06.sh
     1  #!/bin/bash
 
     2  # Pour faire une pause
     3  function pause {
     4          echo "Appuyer sur Entrée pour continuer"
     5          read x
     6  }
 
     7  # Pour savoir si un utilisateur existe
     8  function existUser {
     9          grep -qi "^$1:" /etc/passwd && return 0
    10          return 1
    11  }
 
    12  # Pour connaitre l'uid de l'utilisateur
    13  function getUid {
    14          grep -i "^$1:" /etc/passwd | cut -d':' -f3
    15  }
 
    16  # Initialisation des variables globales
    17  uid=""
    18  user=""
    19  choix=""
 
    20  while true
    21  do
    22          clear
    23          echo "- 1 - Savoir si un utilisateur existe"
    24          echo "- 2 - Connaitre l'UID d'un utilisateur"
    25          echo "- 3 - Fin"
    26          echo -e "Votre choix : \c"
    27          read choix
 
    28          if [[ $choix = @(1|2) ]] ; then
    29                  echo -e "Saisir le nom d'un utilisateur : \c"
    30                  read user
    31          fi
 
    32          case $choix in
 
    33                  1)      if existUser $user ; then
    34                                  echo "L'utilisateur $user existe"
    35                          else
    36                                  echo "l'utilisateur $user n'existe pas"
    37                          fi
    38                          ;;
 
    39                  2)      if existUser $user ; then
    40                                  uid=$(getUid $user)
    41                                  echo "l'UID de l'utilisateur $user est : $uid"
    42                          else
    43                                  echo "L'utilisateur $user n'existe pas"
    44