Programmation SHELL

De Wiki info-lab.fr
Aller à : Navigation, rechercher

Sommaire

Présentation

Le SHELL est un interpréteur de commandes servant d'interface utilisateur pour un système d'exploitation. Le SHELL peut être un programme externe, hors noyau (cas des SHELLs *NIX) ou en partie intégré au noyau et donc difficilement remplaçable.
Un SHELL peut être de type CLI (interface en ligne de commandes utilisant une syntaxe texte) ou GUI (interface graphique). Dans un SHELL de type GUI il est possible d'accéder au SHELL en mode CLI en utilisant un émulateur de terminal.
Tout shell tel que Bash attend les commandes d'un utilisateur en affichant un prompt. Dès que l'utilisateur valide par la touche ENTREE, le SHELL exécute les actions suivantes :

  1. Lecture de la chaîne de caractère saisie (afin d'en découper la structure de de détecter les variables, redirections, métacaractères...)
  2. Vérification (les commandes existent-elles ? cet utilisateur y a t-il accès ? les options et arguments sont ils corrects ?...)
  3. Interprétation (transformation des commandes en appels systèmes)
  4. Exécution séquentiellement (en respectant l'ordre des redirections ou communications inter processus)
  5. Affichage des résultats et des erreurs.
  6. Suggestions pour corriger les erreurs éventuelles (optionnel, uniquement sur les SHELLS développés à cet effet)
  7. Mise à disposition du prompt et attente de nouvelles instructions.

Shell01.png Intéraction entre utilisateurs, shell et applications.

La suite de cette article se concentrera sur les SHELLS *NIX de type BASH.

Conventions et généralités

Page Bash sur Wikipedia. A lire absolument !
BASH connaît 3 types de commandes :

  1. Les commandes internes (exemple umask, echo, pwd)
  2. Les commandes externes localisées par le PATH (exemple vim, grep)
  3. Les alias (définis dans le fichier général ou personnel bashrc)

Nota  : Les vieux unixiens avaient pour habitude de créer leurs scripts dans /home/bin et d'ajouter ce chemin au PATH afin que leur shell trouve automatiquement tous leurs scripts.

Métacaractères, évaluations d'expressions et caractères génériques

  • ` apostrophe inversée : un texte encadré par des apostrophes inversées est vu comme une commande ; l'apostrophe inversée ` peut être remplacée par des parenthèses ( ) bien plus lisibles et facile à saisir sur un clavier AZERTY.

Caractères de citation :

  • \ Protection du caractère suivant ((échappement = protection, c’est à dire conservation de son statut de caractère simple au détriment de ses qualités d'opérateur par exemple).
  • " apostrophe double : une chaîne de caractères encadrée par des apostrophes doubles est vue comme du texte les caractères spéciaux $, ` et / conservent leur usage.
  • ' apostrophe simple : une chaîne de caractères encadrée par des apostrophes simples est vue comme du texte. L'interprétation des caractères spéciaux $, ` et / est désactivée.

Caractères génériques de motifs de remplacement :

  • [ ] replacement de caractères par ceux explicitement contenus entre les crochets ou implicitement membres d'une liste. Exemples [aeiouy], [a-z] ou [A-Z] ou encore [0-9]
  • * N'importe quel caractère, présent entre 0 et n fois.
  • ? 1 caractère, quelqu'il soit
  • ^ ou ! Exclusions de caractères

Caractères d'échappement et de formatage

\b effacement du dernier caractère
\n saut de ligne
\r retour chariot (Entrée)
\t tabulation horizontale
\v tabulation verticale

Modification du séparateur de champs

L'espace, la tabulation (\t) et le saut de ligne (\n) sont les séparateurs de champs par défaut, mais sont modifiables grâce à la variable $IFS.

Options du shell

$ set -o                (permet de lister les actions possibles, et de voir celles qui sont activées)
allexport      	off
braceexpand    	on
emacs          	on
errexit        	off
errtrace       	off
.............................
verbose        	off
vi             	off
xtrace         	off
$ set -o vi             (active ou désactive l'option du shell correspondante)
allexport      	off
braceexpand    	on
emacs          	on
errexit        	off
errtrace       	off
.............................
verbose        	off
vi             	on
xtrace         	off

VARIABLES

La programmation en général et Bash en particulier fait appel aux variables, espaces mémoires identifiés par un nom unique, utilisables par les programmes et commandes, et dont leur valeur peut changer ou pas (constante).
A la connexion d'un utilisateur, un certain nombre de variables sont déjà prédéfinies, ce sont les variables d'environnement (gérées par la commande env). Par convention le nom des variables d'environnement n'est composé que de caractères MAJUSCULES. Les variables saisies par un utilisateur sont des noms sans espace composés de majuscules, minuscules, chiffres (sauf en premier caractère) et caractère souligné. PATH est une des variables d'environnement.
Pour définir une variable, c'est à dire lui affecter une valeur :

nomVariable=valeur

exemples

nomVariable=0    (création de la variable "nomVariable", dont la valeur est 0) 
dateHoraire=`date '+%T'`    (création de la variable "dateHoraire", dont la valeur est le résultat de la commande "date +%T")

Pour appeler une variable dans une commande ou un script : (Les accolades peuvent avoir une utilité et rendent l'appel de la variable plus lisible).

$nomVariable     ou en plus lisible      ${nomVariable}

Le nom de la variable ne peut pas comporter d'espaces, sa valeur non plus, sauf en utilisant guillemets ou un caractère d'échappement pour ignorer chaque espace. Exemples :

dateHoraire=`date '+%T'`      (Utilisations de guillemets inclinés)
totalFinal="122 abc"
totalFinal='112 abc'
totalFinal=122\ abc

Pour appeler le contenu, afficher une variable :

echo $nomVariable     ou mieux     echo ${nomVariable}
0
echo "il est ${dateHoraire}"
il est 11:55:16

Pour supprimer une variable :

unset nomVariable

Pour afficher toutes les variables déjà connues du système :

set | less

Une variable peut aussi être déclarée et affectée au moyen de la commande declare :

declare -i variableNumerique=15

De cette manière, la valeur affectée doit être une valeur arithmétique.

Une variable peut être figée, en lecture seule, non modifiable, on parle alors de constante :

variableNom=toto
declare -r variableNom   (transformation en constante)
echo $ variableNom
toto
variableNom=titi    (je tente de modifier cette variable)
echo $ variableNom
toto         (ma tentative de modification a échoué, cette variable est constante)

NOTA : Il est conseillé de ne pas créer de variables dont le nom est déjà utilisé par le système : variables d'environnement, noms des utilisateurs, commandes ou appels systèmes...

echo '$nomVariable'  
$nomVariable

Rappel : l'encadrement entre simple guillemet transforme toute chaîne de caractères en simple texte.

Variables spéciales

$0, $1, $2 .... $9 : $0 = Dans un script, $0 = nom de la commande lançant ce script, $1 = 1er argument derrière la commande, $2 = 2ème argument ...
$* tous les paramètres vus comme un seul mot.
$@ tous les paramètres vus comme des mots séparés (équivalent de "$1" "$2" "$3" ...).
$# nombre de paramètres sur la ligne de commande (c'est à dire nombre d'arguments derrière le nom du script)
$? code de retour de la dernière commande exécutée (erreur ou réussite).
$$ PID du shell.
$! PID du dernier processus (*) lancé en arrière plan.
$_ dernier argument de la commande précédente.

(*) Processus = Environnement d'exécution contenant toutes les tâches (threads) et les bibliothèques attenantes. Un processus peut être composé de 1 à n threads.

Expansions arithmétiques

Possibilité d'afficher une variable résultat d'un calcul, par défaut en base 10. Précéder les nombres par 0 pou compter en octal, ou 0x pour compter en héxadécimal.

$ echo $((2+3))       (l'ancienne syntaxe $[2+3] est obsolète)
5

Précisions : le signe = sert à définir une variable, pas à obtenir le résultat d'un calcul.
D'autres bases arithmétiques sont utilisables, de la base 2 à la base 36. Exemple base#30
L'opérateur == sert à comparer une égalité, l'opérateur != compare une inégalité.

Concaténation de variables

var1=nouveau
var2=dossier
var3=$v1-$v2
echo $var3
nouveau-dossier

var3=$v1_$v2
echo $var3
dossier

Le caractère souligné pose problème, c'est un opérateur au même titre que [ $ et \ il faut donc lui retirer cette qualité.

var3=${v1}_$v2      (autre solution : var3=$v1"_"$v2 )
echo $var3
nouveau_dossier

Extraction de sous-chaînes dans les variables

var1="JAN 2012 toto ok"
echo ${var1:9:4}       (je saute les 9 premiers caractères et j'affiche les 4 qui suivent)
toto
echo ${var1:6:2}       (je saute les 6 premiers caractères et j'affiche les 2 qui suivent)
12

Recherche de motifs au sein de variables

var2="toto est malade"
echo ${#var2}      (combien de caractères dans cette variable ?)
15
var3=2013
echo ${#var3}
4
echo ${var2#*to}   (élimine le plus court préfixe correspondant au motif to)
to est malade        
echo ${var2##*to}   (élimine le plus long préfixe correspondant au motif to)
est malade        
echo ${var2#*[tom]}   (élimine le plus court préfixe correspondant au motifs t,o ou m)
oto est malade
echo ${var2##*[tom]}   (élimine le plus court préfixe correspondant au motifs t,o ou m)
alade
echo ${var2%*[td]}   (élimine le plus court suffixe correspondant au motifs t ou d)
toto est mala

L'opérateur # Interprête la chaîne de caractères de gauche à droite, % l’interprète de droite à gauche.

var4=toto@courriel.org
echo ${var4%%@*}
toto
var5=toto@service.entité.domaine.net
echo ${var5%%.*}
service
echo ${var5|domaine|nouveaudomaine}      {remplace la 1ère occurence du motif "domaine" par "nouveaudomaine")
toto@service.entité.nouveaudomaine.net
echo ${var5||domaine|nouveaudomaine}     {remplace toutes les occurences du motif "domaine" par "nouveaudomaine")

Augmenter la portée d'une variable

Une variable peut être exportée afin d'en augmenter l'étendue

export -p         (liste toutes les variables exportées)
export var4       (la variable var4 est maintenant disponibles pour les shells du système)
export -f fonction1    (idem pour la fonction fonction1)'
export -f -n fonction1    (fin de l'export de cette fonction)

Fonctions

Ensemble de commandes réalisant une opération en plusieurs étapes. Il est intéressant de créer une fonction si son traaitement est appelé à être utilisé plusieurs fois au sein d'un script.

fonction()
{
   x=50
   local y=100
}

L'argument local contraint la porté de la variable y à l'intérieur de la fonction

Redirections de flux / Communication inter processus

Redirections

Tout shell permet d'utiliser la redirection de flux émanant d'un programme vers un autre programme ou un fichier.

  • La désignation du flux standard utilisé (0,1 ou 2) permet aussi de le rediriger : 0 désigne l'entrée standard (STDIN), 1 désigne la sortie standard (STDOUT) et 2 désigne la sortie d'erreur standard (STDERR). Par défaut tout ce qui est saisi au clavier est dirigé vers le canal d'entrée standard, et tout ce qui sort par le canal de sortie standard (les sorties normales et les erreurs) est dirigé vers l'écran primaire (si plusieurs sont présents). On peut modifier ce comportement par défaut :
ls /home/casimir/images 1> listeImages.txt  écrire dans le fichier "listeImages.txt" le résultat de la 1ère commande

Le fichier "listeImages.txt" est crée s'il n'existait pas, est écrasé s'il existait.

ls /home/casimir/images 1>> listeImages.txt

Le fichier "listeImages.txt" est crée s'il n'existait pas, le resultat est ajouté au contenu déjà présent si le fichier existait.

ls /root /etc /home 2>/dev/null   les erreurs de la commande ne seront pas affichées mais déversées dans un puit sans fond
grep anniversaire < listeImages.txt   le fichier "listeImages.txt" est utilisé en entrée de la commande grep

2>&1 redirige les erreurs au même endroit et de la même façon que la sortie standard.

pts1$ echo bonjour > /dev/pts/0
pts0$ bonjour

Redirection d'un terminal vers un autre.

Communication

  • La caractère | (pipe) permet d'utiliser le résultat d'une première commande comme entrée d'une seconde commande :
ps -eaf | grep terminal           lister les processus des utilisateurs (ps = affichage ligne par ligne, 1 processus = 1 ligne)
et affichage des lignes contenant la chaîne de caractères "terminal"

La caractère | (pipe) n'est pas à proprement parler une redirection de flux mais plutôt une voie de communication d'un processus vers un autre.
Certaines commandes ont leur propre redirection intégrée. Exemple find et sa redirection exec qui permet d'assigner une action sur le résultat de la recherche.

find -name "desktop.ini" -exec rm -f {} \;
find -name "Thumbs.db" -exec rm -f {} \;        

(suppression de la pollution qu'un certain système d'exploitation nauséabond dissémine dans les répertoires dans lequel il croit reconnaître certains types de fichiers ).

Conditions de succès ou d'erreur

On peut conditionner une action à la réussite ou à l'échec d'une action précédente au moyen de && et ||.
Si une action réussit alors grâce à && j’enchaîne sur une deuxième action ; ou si l'on préfère la deuxième action n'est possible que si la première réussit.

touch fichier1 && echo "réussite de l'horodatage du fichier1"
réussite de l'horodatage du fichier1

Si la commande ls échoue alors grace à || j’enchaîne sur une deuxième action ; ou si l'on préfère la deuxième action n'est possible que si la première échoue.

ls dossier2 || echo "Le dossier2 n'existe pas"
ls: Aucun fichier ou répertoire de ce type
Le dossier2 n'existe pas

Processus en arrière plan / en avant plan

Le signe & suivant immédiatement une commande la bascule en arrière plan : Le prompt redevient immédiatement accessible, sans attendre la fin de la commande.

cat /etc/toto/texte &

La combinaison de touches CTRL + Z a exactement le même effet.

jobs -l           (permet de lister les jobs en arrière plan)

La commande jobs affiche le PID des processus en arrière plan, accompagné du signe + pour le dernier processus dans cet état, et le signe - pour l'avant dernier.

fg %1             (relance le dernier processus basculé en arrière plan) 
fg %+             (idem)
fg %3             (relance l'avant avant dernier processus basculé en arrière plan) 
fg %-             (relance l'avant dernier processus basculé en arrière plan)

Petits exercices de manipulation de variables, redirections et pipelines

$ user=bob
$ echo 'hello $user'
hello $user
$ echo "hello $user"       (variante echo "hello ${user}"
hello bob
$ echo b{a,i,u}lle      (il est possible d'imbriquer les accolades dans des accolades)
balle bille bulle
$ date
mar sep 15 17:09:45 CEST 2009
$ echo "il est `date '+%T'`"
il est 17:12:37
$ NOW=`date '+%T'`
$ echo "il est $NOW"   
il est 17:13:54
$ echo "Bienvenue $LOGNAME sur la machine `hostname`"
$ head -2 /etc/fstab > output
$ tail -3 /etc/fstab >> output
$ echo -e "1ere ligne\n2eme ligne\n3eme ligne" >> output
$ less output
1ere ligne
2eme ligne
3eme ligne

De nombreux autres exemples se trouvent dans l'article Find - Grep - Sed.

Ecriture de scripts shell

Bash comme tout autre shell peut être employé dans un script, c'est à dire un fichier contenant un certain nombre de commandes, de variables, conditions ...
C'est donc un fichier texte, parfois pourvu par convention de l'extension .sh (mais rien ne l'y oblige). Par exemple il pourra être nommé script.sh.
Pour exécuter un script il faut :

  • Soit en commencer l'écriture par une ligne Shebang précisant le shell qui doit l'exécuter,
$ vim script.sh
#!/bin/sh
début des instructions
.................
fin des instructions
:wq
$ 

puis le rendre exécutable (par la commande chmod). Il pourra alors simplement être lancé dans le répertoire qui l'héberge :

$ ./chemin/script.sh
  • Soit cela reste un simple fichier texte qui servira d'argument appelé par un shell
$ bash chemin/chemin/script.sh    

variante

$ . script.sh
$ source script.sh

NOTA 1 : Si le script se situe dans un répertoire renseigné dans le PATH, son chemin d'accès n'aura pas besoin d'être renseigné.
NOTA 2 : Si le script est lancé en désignant un shell ($ bash chemin/chemin/script.sh) celui-ci lance un shell secondaire totalement indépendant pour exécuter le script, et ferme ce shell secondaire à la fin du script. Conséquence directe : Si le script lancé crée/modifie des variables ou modifie le répertoire courant (au moyen de la commande cd), tout est perdu quand son shell secondaire se ferme. A contrario, si un script est exécuté dans le shell qui l'invoque (au moyen des commandes source script.sh ou . script.sh) et qu'il modifie variables ou répertoire courant, le shell qui l'a lancé conservera ces modifications de l'environnement. Cette modification de l'environnement causée par l'exécution du script peut être souhaitable ou pas, il convient de choisir quelle méthode est à employer pour lancer ses scripts.

Conseils pour l'écriture de scripts

  • Un script doit être exempt d'erreur et doit être testé avant d'en programmer l'exécution (at, cron ...) surtout si on ne peut pas surveiller son exécution (hors heures ouvrables)
  • Un script ne doit faire qu'une tâche ; pour plusieurs tâches au besoin écrire plusieurs scripts
  • Un script doit être facile à lire, compréhensible : Arérer le code, structurer le code et commenter.
  • Un script doit être réutilisable en totalité ou en partie.

Les scripts acceptent les commandes (internes, externes, alias), les fonctions (définies par le mot clé function), les mots-clés conditionnels (if, else...), les variables ....

Expressions rationnelles

Les expressions servent à analyser du texte en fonction d'un motif, d'un modèle, dans le but d'effectuer un traitement sur le texte (recherche, sélection, modification, affichage, remplacement...)
Plus évoluées que les simples métacaractères ou caractères joker.

Expressions rationnelles simples

[a-z] depuis a jusqu'à z
[A-Z] ideme en majuscules
alpha lettres
digit chiffres
alnum lettres et chiffres
Xdigit chiffres héxadécimaux
lower upper inférieur ou supérieur
ces expressions rationnelles simples peuvent utiliser des opérateurs pour les combiner :
\| ou
\?' 0 ou 1 occurence de l'élément précédent

\(abc\)\{2\}

[abxy] liste de caractères

Expressions rationnelles complèxes

Outils personnels
Espaces de noms

Variantes
Actions
Navigation
Outils