User Tools

Site Tools


informatique:linux:programmation_shell

Programmation Shell

Les différents shells peuvent avoir des fonctions sensiblement différentes voir incompatibles. Ce n'est pas le cas pour la majorité d'entre elles. Ici nous parlerons de bash car c'est le plus couramment utilisé. Pour afficher la liste des options de bash il faut saisir bash -c “help set”. Pour lire un script sans exécuter les commandes : bash -n.

  • . <CONFIG_FILE> ⇒ permet de faire un include (notez bien le point + espace “. ”)
  • !! ⇒ est remplacé par la dernière commande (différent de $_ qui contient uniquement le dernier paramètre de la dernière commande)
$ touch toto
$ echo !!
echo touch toto
touch toto

Les variables

En bash, chaque variable est précédée de $. On affecte un contenu ainsi : VAR=“toto” et on récupère le contenu en ajoutant $ devant.

NOM="robert"
echo "Salut, je suis le gros $NOM"

On peut les manipuler sans les déclarer proprement au préalable ; mais on peut être clean et les définir (declare VAR) ou les supprimer (unset VAR).

Par défaut une variable est globale et est vue dans le script (y compris ses fonctions) ; dans une fonction on peut déclarer des variables locales afin d'éviter les écrasements de variables globales non souhaités avec :

local VAR="pwet"

Tests de définition

Pour tester si une variable est non définie : ${VAR?message d'erreur} ou vide : ${VAR:?message d'erreur}.

unset VAR
${VAR?var non def}     # -bash: VAR: variable non def (sur stderr)
declare VAR
${VAR?var non def}     # -bash: VAR: variable non def (sur stderr)
VAR=
${VAR?var non def}     # (rien car VAR est définie)
${VAR:?vide}           # -bash: VAR: vide (sur stderr)

Pour utiliser une valeur par défaut au cas ou la variable n'est pas définie : ${VAR-non-def} ; on peut également vérifier si cette dernière est non nulle (non-vide) : ${VAR:-non-def}.

declare VAR
echo ${VAR-defaut}     # (vide car VAR est définie)
echo ${VAR:-defaut}    # vide
 
unset VAR
echo ${VAR-defaut}     # defaut
echo ${VAR:-defaut}    # defaut

Cela ne définit ni n'affecte VAR ; pour cela il faut utiliser la syntaxe ${VAR=defaut} et ${VAR:=defaut}.

declare VAR
echo ${VAR=defaut}     # (ne produit rien)
echo ${VAR:=defaut}    # defaut (VAR prend également cette valeur)

Manipulation des variables

  • ${#VAR} renvoie la longueur de la variable VAR. Si VAR est un tableau : ${#VAR[@]}
  • ${VAR} (avec les { } permet de délimiter le nom de variable ; c'est utile dans le cas suivant par exemple :
scp user@server:/tmp/fic.txt ${REP_LOCAL}_/

Sans les {}, l'interpréteur présumera que la variable se nomme $REP_LOCAL_ et non $REP_LOCAL.

  • on peut tronquer une variable : ${VAR:POS} ⇒ renvoie le contenu à partir de la position POS (comptabilisée à partir de 0). Si POS est négative (il faut la mettre entre parenthèses), on compte à partir de la fin de la chaine.
  • ${VAR:POS:LONG} : renvoie la sous-chaine de longueur LONG à partir de la position POS
VAR=undeuxtroisquatre
echo ${VAR:2}          # deuxtroisquatre
echo ${VAR:(-6)}       # quatre
echo ${VAR:(-6):2}     # qu

On peut utiliser des motifs (qui en ont l'aspect mais ne sont pas exactement des expressions régulières) pour substituer des bouts de chaine :

  • ${VAR#PAT} : supprime la plus courte chaine répondant à la pattern PAT en partant du début de celle-ci ; on peut partir de la fin de la chaine avec % à la place de #
  • ${VAR##PAT} : supprime la plus longue chaine répondant à la pattern PAT (idem : %% pour partir de la fin)
  • ${VAR/PAT/SUBST} pour remplacer par SUBST la première occurrence du contenu de VAR répondant à la pattern PAT ; respectivement // pour toutes les occurrences. On peut partir du début # (par défaut) ou de la fin de la chaine %, mais c'est dans tous les cas le motif le plus grand qui sera remplacer (pas de ## ni %%).
VAR=undeuxtroisquatre
echo ${VAR#u*e}        # uxtroisquatre
echo ${VAR##u*e}       # (rien !)
echo ${VAR%u*e}        # undeuxtroisq
echo ${VAR/trois/3}    # undeux3quatre
echo ${VAR//e/E}       # undEuxtroisquatrE

Exemple usuels :

  • sélectionner l'extension d'un fichier : ${FIC##*.}
  • sélectionner le nom d'un fichier sans son extension : ${FIC%.*}
  • supprimer le dernier caractère de la variable (trim) : ${FIC%?}

source : https://www.patpro.net/blog/index.php/2006/04/07/20-manipulations-sur-les-variables-dans-bash/

Variables spécifiques

Ce sont des variables liées au contexte du script.

  • $0 est le nom du script (équivalent à $BASH_SOURCE)
  • $1 $2 … $10 les arguments du script. On peut les décaler grâce à la commande shift ($2 devient $1, $3 devient $2, etc)
  • $_ renvoie le dernier argument de la dernière commande
  • $* liste de tous les arguments
  • $@ liste de tous les arguments (équivalent à $*)
  • $# renvoie le nombre d'argument du script
  • $$ renvoie le PID du script courant
  • $! renvoie le PID de la dernière commande
  • $? renvoie le retour (code d'erreur par exemple) de la dernière commande
  • IFS=“\n” (Internal Field Separator) le séparateur de champ est “ENTER” (utilisée par la commande read)
  • $PWD renvoie le chemin du shell depuis lequel est lancé le script (équivalent à pwd) ; c'est complémentaire de $0

Les tableaux

Les enregistrements commencent à l'index 0. Ainsi pour récupérer le contenu de la 5ème case on doit utiliser l'index 4.

  • TAB=( un deux trois ) : définition d'un tableau de 3 cases
  • TAB[0]=val : affectation du premier enregistrement du tableau TAB
  • ${TAB[0]} : contenu du premier enregistrement du tableau TAB
  • $TAB : équivalent de ${TAB[0]}
  • ${TAB[*]} : désigne l'ensemble des enregistrements du tableau TAB

Comme pour les variables classiques, on peut récupérer la longueur en le précèdent de # :

  • longueur de la 3e case : ${#TAB[2]}
  • taille du tableau (nombre de case de celui-ci) : ${#TAB[*]} (ou ${#TAB[@]})

Tableau à 2 dimensions

Cela n'existe pas sous bash. Mais on peut l'émuler en créant un tableau associatif (qui, à une clé fait correspondre un contenu), en codant la clé pour émuler 2 dimensions. Exemple : pour émuler un tableau de 2 x 3 cases :

# déclaration d'un tableau associatif T
declare -A T
# remplissage clé => valeur, par exemple la clé "0:0" => "homme"
# c'est un tableau à une dimension
T[0:0]=homme
T[0:1]=femme
T[1:0]=coq
T[1:1]=poule
T[2:0]=lion
T[2:1]=lionne
 
# calcul de l'index max du tableau : (HAUTEUR / 2) - 1 = 2
T_SIZE=$((${#T[@]}/2-1))
 
# en forgeant la clé, on émule un tableau bidimensionnel :
for ((i=0;i<=T_SIZE;i++)); do
   echo "ligne=$i ; case0=${T[$i:0]} ; case1=${T[$i:1]}"
done

getopts : analyse des arguments d'un script

Cette commande permet de récupérer facilement les options (paramètres) avec lesquels a été lancé un script. Une option est un caractère précédé d'un + ou d'un - (à la différence d'un argument qui est “juste” une chaîne de caractère) ; getopts analyse les options une par une, et doit donc être utilisée dans un boucle. Exemple d'utilisation :

while getopts "ab:" opt
do
	echo "Option traitée : $opt"
	case $opt in
		a)
			echo "-a trouvée !"
			echo "prochain indice a traiter : $OPTIND"
			;;
		b)
			echo "-b trouvée ! son paramètre est : $OPTARG"
			echo "prochain indice a traiter : $OPTIND"
			;;
	esac
done
  • $opt est le nom (arbitraire) de la variable recevant la valeur de l'option dans notre boucle while
  • “:” inscrit après une option indique que cette dernière est suivi d'un argument
  • OPTIND est la variable réservée qui contient l'indice de la prochaine option à analyser
  • OPTARG est la variable réservée qui contient l'argument suivant l'option courante

La même option peut apparaitre plusieurs fois sans générer d'erreur. Une option non attendue génèrera une erreur (non bloquante) et prendra la valeur “?”. Une option nécessitant un argument non présent génèrera aussi une erreur non bloquante, mais prendra la valeur “:”.

On peut choisir de traiter les erreurs dans le script en préfixant la liste des options par un “:” (while getopts “:ab:c:” opt) ; dans ce cas :

  • dans le cas d'une option non attendue :
    • $opt prend la valeur “?”
    • $OPTARG prendra le nom de l'option incorrecte
  • dans le cas d'une option nécessitant un argument non présent :
    • $opt prendra la valeur “:”
    • $OPTARG prend la valeur de l'option

Reprise de l'exemple avec gestion manuelle des erreurs :

echo "### Liste des arguments = $*"
while getopts ":ab:c:" opt
do
	echo "Option traitée : $opt"
	case $opt in
		a)
			echo "-a trouvée !"
			echo "prochain indice a traiter : $OPTIND"
			;;
		b)
			echo "-b trouvée ! son paramètre est : $OPTARG"
			echo "prochain indice a traiter : $OPTIND"
			;;
		c)
			echo "-c trouvée ! son paramètre est : $OPTARG"
			echo "prochain indice a traiter : $OPTIND"
			;;
		\?) # on doit échapper "?" car c'est un caractère interprété par le shell
			echo "$OPTARG : option incorrecte !!"
			;;
		:) # ceci n'est pas un smiley
			echo "argument manquant pour l'option $OPTARG !!"
			;;
	esac
done

Pour récupérer les éventuels arguments restants, on utilise la commande shift qui permet de décaler (supprimer) les options déjà traitées de la liste des paramètres du script. Pour finir d'analyser ces arguments, ajouter ces lignes à la fin du script précédent :

shift $(($OPTIND-1)) # on supprime les options déjà traitées
 
echo "### Liste des arguments restants : $*"
while [ -n "$1" ]
do
	echo "encore un argument ! : $1"
	shift 1
done

Les fonctions

read

read toto permet de demander une saisie clavier à l'utilisateur qui sera enregistrée dans la variable $toto.

Si aucune variable n'est précisée, la saisie sera enregistrée dans la variable $REPLY par défaut.

Pour afficher un texte avant la saisie on utilise -p ; on peut récupérer plusieurs saisie d'un seul coup :

read -p "Quel est le nombre indique sur votre CB ? Et le cryptogramme visuel de derriere ?" CARD_NUMBER CRYPTO

Pour lire depuis un fichier :

read < fichier.txt

Pour lire depuis une variable :

read <<< $var

Expressions arithmétiques

Pour faire un calcul il faut l'encadrer de $((calcul)) ou $[calcul]

echo 1+1=$((1+1))

La fonction puissance s'écrit ** :

echo 2^5=$((2**5))

Pour incrémenter une variable : plusieurs possibilités :

  • z=`expr $z + 1`
  • z=$(($z+1))
  • z=$((z+1)) (entre doubles parenthèses, le $ est optionnel)
  • (( z += 1 ))

Conversions

Convertir $VAR_HEXA de l'hexadecimal en décimal :

VAR_DECIMAL=$((16#$VAR_HEXA))
# ou, avec la commande let
let VAR_DECIMAL=0x$VAR_HEXA

Instructions conditionnelles

if

test expr

ou

if [ expr ]
  then
  else
fi
  • Exemple sur une seule ligne (-n renvoie TRUE si la chaine n'est pas vide) :
if [ -n "" ]; then echo "1"; else echo "0"; fi
  0

Conditions multiples

Pour tester des conditions multiples, on doit encadrer les tests avec un double crochet :

if [[ TRUE && TRUE || FALSE ]]
then
  echo "TRUE"
else
  echo "TRUE QUAND MÊME !"
fi

On peut également procéder ainsi :

if [ TRUE ] && [ TRUE ] || [ FALSE ]

Les conditions multiples sans parenthèses sont lues dans l'ordre d'apparition (de gauche à droite), ici cela équivaut à : (TRUE ET TRUE) OU FALSE. Donc TRUE quand même.

  • Structure avec “elif” (“elseif” dans d'autres langages) qui permet de faire des tests à la suite :
if [ expr ]
  then
elif
  then
elif
  then
[..]
else
fi

Expression sur les fichiers

  • -d file : existe et est un répertoire
  • -e file : existe
  • -f file : existe et non répertoire
  • -h file : si le fichier est un lien symbolique
  • -s file : existe et est de taille non nulle
  • -r file : existe et droit en lecture
  • -w file : existe et droit en écriture
  • -x file : existe et droit d'exécution
  • -O file : si le fichier nous appartient
  • -G file : si le fichier appartient à notre groupe

Chaînes de caractères

  • str1 = str2
  • str1 != str2
  • -z str : vrai si chaîne de longueur nulle
  • -n str : vrai si chaîne de longueur non nulle

Nombres, comparaison

  • n1 -eq n2 égaux
  • n1 -ne n2 non égaux
  • n1 -gt n2 plus grand que
  • n1 -ge n2 plus grand ou égal
  • n1 -lt n2 plus petit que
  • n1 -le n2 plus petit ou égal

Opérateurs

  • ! unaire de négation
  • -a et logique
  • -o ou logique
  • ( expr ) paranthésage
  • \( et \) sinon interprétées par le shell

case

Test plusieurs valeurs de <chaîne> (~ifs imbriqués).

case <chaîne> in
 motif1)
    echo "motif 1"
    echo "C'est le premier !!"
    ;;
 motif2|motif3)
    echo "motif 2 ou motif 3"
    ;;
 motif*)
    echo "tous les autres motifs"
    ;;
 *)
    echo "Tout le reste"
    ;;
esac

for

Permet de créer une boucle déterministe : pour chaque valeur de i, on exécute la boucle.

Exemples :

for (( i = 1; i <= 5; i++ ))
  do
  echo -n "$i "
done

Affichera : 1 2 3 4 5 .

Cela équivaut à : for i in {1..5} qui est une autre façon d'incrémenter i de 1 à 5.

On peut aussi lui fournir une liste de mots :

for i in serveur1 serveur2 serveur3
do
   ssh $i "(uname -r)"
done

On peut l'exécuter sur une seule ligne de commande en respectant cette syntaxe :

for i in serveur1 serveur2 serveur3; do ssh $i "(uname -r)"; done

break

Pour sortir prématurément d'une boucle for, while ou repeat, on utilise le mot-clé break :

for i in {1..5}
do
   if [ $i -eq 3 ]
   then
      echo "Huston nous avons un probleme !"
      break
   fi
   echo $i
done

Ce qui donne :

1
2
Huston nous avons un probleme !

continue

Pour sortir de l'occurrence courante de la boucle, on utilise continue. On quitte l'occurrence courante et on passe à la suivante :

for i in {1..5}
do
   if [ $i -eq 3 ]
   then
      echo "Huston nous avons un probleme !"
      continue
   fi
   echo $i
done

Ce qui donne :

1
2
Huston nous avons un probleme !
4
5

while

Exécute la boucle tant que la condition suivant l'instruction while est remplie :

exemple : compter de 1 à 5…

cpt=1
while [ $cpt -le 5 ]; do
   echo $cpt
   (( cpt++ ))
done

exemple2 : lire un fichier ligne par ligne

while read line
do
  echo $line
done < fic.txt

La même mais avec la sortie d'une commande :

cat fic.txt | while read line
do
  echo $line
done

ou sur une seule ligne :

while read n; do echo $line; done < fic.txt

until

Exécute la boucle jusqu'à ce que la condition soit remplie :

until <condition>
do
   echo "Toujours pas !"
done

select

Permet de créer un menu interactif, c'est à dire une saisie utilisateur ;

  • $PS3 permet de définir le prompt
  • $REPLY contient l'index de la réponse
PS3="Prompt du menu"
select i in {2..5}
do
    # $REPLY contient l'index de la réponse ;
    # $i contient la valeur correspondante
    echo $REPLY-$i
 
    # pour sortir du menu (car il s'agit d'une boucle)
    break
done

Divers

debug

Pour débugger un script, on insère en début de script la commande set -x.

en vrac

  • commande rsh
rsh -4 -n $serveur $commande >/dev/null 2>&1
  • commande ping limitée à 3 envois avec un timeout de 0.05s
ping -c3 -w 0.05 soekris-${cpt} >/dev/null 2>&1
retour=$?
  • attente d'un SIGUSR1 (30) pour terminer le script :
trap "echo 'Exiting..' ; exit 0" 30

Substitution de chaine

Pour n'afficher qu'une partie d'une chaine (une sous-chaine donc), on utilise la syntaxe ${tst:<offset>:<length>}. L'offset, c'est l'origine (elle commence à 0 pour le premier caractère, comme souvent en informatique), et length c'est le longueur de la chaine affichée (facultative si on veut afficher jusqu'à la fin).

Par exemple :

TEST="Je suis chanceux"
 
# on affiche la sous-chaine à partir du caractère n°3 (soit le 4ème caractère) :
echo ${TEST:3}
suis chanceux
 
# idem, mais on se limite à 4 caractères affichés :
echo ${TEST:3:4}
suis
 
# on peut spécifier un offset négatif pour compter l'offset à partir de la fin de la chaîne
# cependant il faudra ajouter un espace avant le "-" (ou l'encadrer de parenthèses)
echo ${TEST: -8}
chanceux
echo ${TEST:(-8)}
chanceux

Remplacement de chaine

Syntaxe : ${<parameter>/<pattern>/<string>}

<parameter> c'est la chaine d'origine, <pattern> l'expression de la chaine recherchée, et <string> la chaine de remplacement.

<pattern> peut commencer par ces caractères spéciaux (à défaut, il ne remplace que la première occurrence rencontrée) :

  • / pour rechercher toute les occurrences
  • # pour rechercher une chaîne qui commence par
  • % pour rechercher une chaîne qui se termine par
TEST="toto titi tata"
 
# remplacer tous les "a" par des "i"
echo ${TEST//a/i}
toto titi titi
 
# ne remplacer que "a" de fin de chaîne
echo ${TEST/%a/i}
toto titi tati
 
# ne remplacer que le premier "t", par un "p"
echo ${TEST/#t/p}
poto titi tata

Modifier la casse

Pour modifier la casse, on utilise la syntaxe :

  • ${parameter^pattern} pour passer de minuscules à majuscules
  • ou ${parameter,pattern} pour passer de majuscules à minuscules

<pattern> étant optionnel, auquel cas seule la première lettre sera prise en compte :

TEST="toto TITI tata"
 
echo ${TEST^}
Toto TITI tata

On peut doubler les lettres pour traiter toute la chaine de caractère :

echo ${TEST^^}
TOTO TITI TATA
 
echo ${TEST,,}
toto titi tata

Enfin, on peut spécifier la pattern à la suite :

# on UPPERCASE tous les "t"
echo ${TEST^^t}
ToTo TITI TaTa
 
# on UPPERCASE les "t" et les "a"
echo ${TEST^^[ta]}
ToTo TITI TATA

Préfixes et suffixes

Syntaxe : ${<parameter>#<word>} pour les préfixes, ${<parameter>%<word>} pour les suffixes, ce qui va supprimer la plus courte occurrence de la chaîne. Pour supprimer la plus longue occurrence, on utilisera respectivement ## et %%.

On s'en sert souvent pour manipuler les chemins et les noms de fichier :

FIC=/chemin/ve.rs/mon/fichier.ext
 
# afficher uniquement son extension
# (on supprime le plus long préfixe se terminant par ".")
echo ${FIC##*.}
fichier.ext
 
# afficher le chemin du fichier :
# (on supprime le suffixe le plus court contenant un "/")
echo ${FIC%/*}
/chemin/ve.rs/mon

Liens

informatique/linux/programmation_shell.txt · Last modified: 2018/07/13 14:35 by pteu