User Tools

Site Tools


informatique:linux:programmation_shell

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
informatique:linux:programmation_shell [2017/02/08 12:51] – [Manipulation des variables] pteuinformatique:linux:programmation_shell [2023/10/02 13:35] (current) – [Lire depuis un fichier] pteu
Line 1: Line 1:
-{{tag>programmation langage bash shell}} 
  
-====== Programmation Shell =======+======Programmation Shell=======
  
 +{{ :informatique:linux:bash_logo.png?300|}}
 +
 +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 [[https://fr.wikipedia.org/wiki/Bourne-Again_shell|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 (mode "dry-run) : ''bash -n''.
 +
 +Les mots d'une ligne suivants un ''#'' sont considérés comme des commentaires (ils ne sont pas interprétés) ; les commentaires multi-lignes sont encadrés de ''<nowiki>:'</nowiki>'' et ''<nowiki>'</nowiki>''.
  
   * ''. <CONFIG_FILE>'' => permet de faire un //include// (notez bien le point + espace ". ")   * ''. <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)+  * ''!!'' => est remplacé par la dernière commande (différent de ''$_'' qui désigne uniquement le dernier paramètre de la dernière commande)
 <code bash> <code bash>
 $ touch toto $ touch toto
Line 14: Line 20:
  
  
-=====Les variables=====+======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. 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.
Line 22: Line 28:
 </code> </code>
 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''). 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'').
 +
 +Pour déclarer une variable non modifiable = en readonly (une constante) :
 +<code bash>
 +readonly CONST="constante"
 +# ou
 +declare -r CONST="constante"
 +</code>
  
 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 : 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 :
Line 27: Line 40:
 local VAR="pwet" local VAR="pwet"
 </code> </code>
 +<WRAP center round important 60%>
 +Les variables déclarées dans une fonction ont toujours une portée locale (elles sont détruites à la fin de la fonction), à moins qu'elles ne soient préalablement déclarées dans le script appelant.
 +</WRAP>
  
-==== Tests de définition====+ 
 +Pour déclarer une constante locale à une fonction : 
 +<code bash> 
 +local -r CONST="constante locale" 
 + 
 +# ou en 2 lignes 
 +local CONST="constante locale" 
 +readonly CONST 
 + 
 +# marche aussi, puisque lorsque declare est invoqué dans une fonction, 
 +#  la variable qui suit est déclarée localement 
 +function test() { 
 +  declare -r CONST="constante locale" 
 +  } 
 +</code> 
 +C'est assez touffu je suis d'accord. 
 + 
 + 
 + 
 + 
 + 
 +===== Tests de définition=====
  
 Pour tester si une variable est non définie : ''${VAR?message d'erreur}'' ou vide : ''${VAR:?message d'erreur}''. Pour tester si une variable est non définie : ''${VAR?message d'erreur}'' ou vide : ''${VAR:?message d'erreur}''.
Line 59: Line 96:
 </code> </code>
  
-==== Manipulation des variables====+=====Typage===== 
 + 
 +Il n'y a pas de typage strict en bash, toutes les variables sont considérées comme des chaînes de caractères, sur lesquelles on peut toutefois réaliser des opérations arithmétiques par exemple, si elles ne contiennent que des chiffres. 
 + 
 +Pour "émuler" une variable booléenne par exemple, il convient de tester son contenu puisque c'est en réalité une string : 
 +<code bash> 
 +is_ready=true   # is_ready est une string qui vaut "true" 
 +if [ "$is_ready" = true ] 
 +   then echo "I'm ready" 
 +   else echo "I'm not" 
 +fi 
 +</code> 
 + 
 +Pour plus de détails sur le pseudo-typage en bash, voir la fonction [[informatique:linux:commandes_linux#declare|declare]]. 
 + 
 +===== Manipulation des variables=====
  
   * ''${#VAR}'' renvoie la longueur de la variable VAR. Si VAR est un tableau : ''${#VAR[@]}''   * ''${#VAR}'' renvoie la longueur de la variable VAR. Si VAR est un tableau : ''${#VAR[@]}''
-  * ''${VAR}'' permet de délimiter le nom de variable ; c'est utile dans le cas suivant par exemple :+  * ''${VAR}'' (avec les ''{'' ''}'' permet de délimiter le nom de variable ; c'est utile dans le cas suivant par exemple :
 <code bash> <code bash>
 scp user@server:/tmp/fic.txt ${REP_LOCAL}_/ scp user@server:/tmp/fic.txt ${REP_LOCAL}_/
 </code> </code>
 Sans les ''{}'', l'interpréteur présumera que la variable se nomme ''$REP_LOCAL_'' et non ''$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.+  * on peut tronquer une variable : ''${VAR:POS}'' => renvoie le contenu de VAR à 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:POS:LONG}'' : renvoie la sous-chaine de longueur LONG à partir de la position POS
 +  * Si on omet POS : ''${VAR::LONG}'' permet de supprimer les caractères suivants la position LONG ; si LONG est négatif, on part de la la fin de la chaine.
 <code bash> <code bash>
 VAR=undeuxtroisquatre VAR=undeuxtroisquatre
Line 74: Line 127:
 echo ${VAR:(-6)}       # quatre echo ${VAR:(-6)}       # quatre
 echo ${VAR:(-6):2}     # qu echo ${VAR:(-6):2}     # qu
 +echo ${VAR::2}         # un
 +echo ${VAR::(-6)}      # undeuxtrois
 </code> </code>
  
Line 79: Line 134:
   * ''${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 __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 : ''<nowiki>%%</nowiki>'' pour partir de la fin)   * ''${VAR##PAT}'' : supprime la plus __longue__ chaine répondant à la pattern PAT (idem : ''<nowiki>%%</nowiki>'' 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 ''<nowiki>//</nowiki>'' 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 ''<nowiki>%%</nowiki>'').+  * ''${VAR/PAT/SUBST}'' pour remplacer par SUBST la première occurrence du contenu de VAR répondant à la pattern PAT ; respectivement ''<nowiki>//</nowiki>'' pour toutes les occurrences. 
 +  * On peut limiter le remplacement aux seuls préfixe ''${VAR/#PAT/SUBST}'' ou suffixe ''${VAR/%PAT/SUBST}'', mais c'est dans tous les cas le motif le plus grand qui sera remplacé (pas de ''##'' ni ''<nowiki>%%</nowiki>'').
 <code bash> <code bash>
 VAR=undeuxtroisquatre VAR=undeuxtroisquatre
Line 87: Line 143:
 echo ${VAR/trois/3}    # undeux3quatre echo ${VAR/trois/3}    # undeux3quatre
 echo ${VAR//e/E}       # undEuxtroisquatrE echo ${VAR//e/E}       # undEuxtroisquatrE
 +echo ${VAR/#un/1}      # 1deuxtroisquatre
 +echo ${VAR/%q*/4}      # 1deuxtroisquatre
 </code> </code>
  
Line 92: Line 150:
   * sélectionner l'extension d'un fichier : ''${FIC##*.}''   * sélectionner l'extension d'un fichier : ''${FIC##*.}''
   * sélectionner le nom d'un fichier sans son extension : ''${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/ source : https://www.patpro.net/blog/index.php/2006/04/07/20-manipulations-sur-les-variables-dans-bash/
  
-====Variables spécifiques====+=====Variables spécifiques=====
  
 Ce sont des variables liées au contexte du script. Ce sont des variables liées au contexte du script.
-  * ''$0'' est le nom 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)   * ''$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   * ''$_'' renvoie le dernier argument de la dernière commande
Line 107: Line 166:
   * ''$!'' renvoie le PID de la dernière commande   * ''$!'' renvoie le PID de la dernière commande
   * ''$?'' renvoie le retour (code d'erreur par exemple) de la dernière commande   * ''$?'' renvoie le retour (code d'erreur par exemple) de la dernière commande
 +  * ''$-'' renvoie la liste des paramètres courts du shell. Pour les interpréter, saisir ''help set'' dans le shell.
  
   * ''IFS="\n"'' (Internal Field Separator) le séparateur de champ est "ENTER" (utilisée par la commande read)   * ''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''
  
-====getopts : analyse des arguments d'un script====+ 
 +=====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 et remplissage d'un tableau de 3 cases 
 +  * ''TAB[0]=UN'' : affectation du premier enregistrement du tableau TAB 
 +  * ''${TAB[0]}'' ou ''$TAB'' : contenu du premier enregistrement du tableau TAB ("UN"
 +  * ''${!TAB[@]}'' liste les indices des cases du tableau ("0 1 2") 
 +  * ''${TAB[-1]}'' dernier élément du tableau ("trois"
 +  * ''${TAB[*]}'' ou ''${TAB[@]}'' : désigne l'ensemble des enregistrements du tableau TAB ("un deux trois"
 +  * ''${TAB[@]:1:2}'' : désigne un intervalle de case ; entre celle d'indice 1 (TAB[1]) jusqu'à la 2nde lue ("deux trois"
 +  * ''TAB=("${TAB[@]}" "quatre")'' ou ''TAB+=("quatre")'' : push (ajout d'un élément à la fin du tableau) 
 +  * ''unset TAB[3]'' : supprimant la 4ème case 
 +  * ''TAB=("${TAB[@]/trois/}'' : suppression par expression régulière de la 2ème case ${TAB[2]} 
 +  * ''TAB=($(cat FILENAME))'' : remplir le tableau avec chaque mot du fichier 
 + 
 +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[@]}''
 + 
 +Pour parcourir facilement un tableau dans une boucle : 
 +<code bash> 
 +ARRAY=( un deux trois ) 
 +for i in "${ARRAY[@]}"; do echo $i; done 
 +un 
 +deux 
 +trois 
 +</code> 
 + 
 +Exemple de découpage d'une chaîne dans un tableau, avec comme séparateur ";"
 +<code bash> 
 +CHAINE='17;8 23;9' 
 +IFS=";"                       # définition du séparateur 
 +read -a TAB <<<"$CHAINE" 
 +echo "${#TAB[@]}"              # affiche de la taille du tableau (nombre de cellule) 
 + 3 
 +printf '%s\n' "${TAB[@]}"      # affichage du contenu du tableau cellule par cellule 
 + 17 
 + 8 23 
 + 9 
 +</code> 
 + 
 +====Tableau à 2 dimensions==== 
 + 
 +Ça n'eût pas existé 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 : 
 +<code bash> 
 +# 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 
 +</code> 
 + 
 +====Tableau associatif==== 
 + 
 +Créer et utiliser un vrai tableau associatif : 
 +<code bash> 
 +declare -A TA=([cle1]=valeur1 ["cle 2"]="valeur 2" [cle3]=valeur3) 
 +echo "${TA[cle1]}" 
 +# valeur1 
 + 
 +# Parcourir le tableau 
 +for cle in "${!TA[@]}"; do 
 +   echo "cle=$cle ; val=${TA[$cle]}" 
 +done 
 +#cle=cle 2 ; val=valeur 2 
 +#cle=cle3 ; val=valeur3 
 +#cle=cle1 ; val=valeur1 
 +</code> 
 + 
 +Pour connaitre la taille de ce tableau : ''${#TA[@]}'' (3 dans cet exemple). 
 +=====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. 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.
Line 187: Line 333:
 done done
 </code> </code>
-====Les tableaux==== 
  
-Les enregistrements commencent à l'index 0. Ainsi pour récupérer le contenu de la 5ème case on doit taper dans l'index 4. 
  
-  * ''TAB=( un deux trois )'' : définition d'un tableau de 3 cases +======Descripteurs de fichier======
-  * ''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 classiqueson peut récupérer la longueur en le précèdent de ''#''+Il existe 3 descripteurs de fichier standards, utilisés par toutes les commandes : 01 et correspondants à STDIN, STDOUT et STDERR (input, output et erreur). 0 pointe par défaut sur le clavier, 1 et 2 sont affichés par défaut à l'écran.
-  * longueur de la 3e case : ''${#TAB[2]}'' +
-  * taille du tableau (nombre de case de celui-ci''${#TAB[*]}'' (ou ''${#TAB[@]}'')+
  
 +Pour lire depuis un fichier plutôt que depuis le clavier :
 +<code bash>
 +echo toto > /tmp/fichier.txt
 +read VAR < /tmp/fichier.txt
 +echo $VAR
 + toto
 +</code>
  
-=====Les fonctions=====+====Les redirecteurs====
  
-==== read ====+  * ''>'' redirige la sortie vers un fichier ; ''> fichier'' a pour effet de créer un fichier vide ou de supprimer le contenu du fichier s'il existe déjà (équivaut à ''touch fichier''). Pour des raisons de portabilité il vaut mieux utiliser '': > fichier'' qui a le même effet. 
 +  * ''<nowiki>>></nowiki>'' possède le même comportement mais écrit à la fin du fichier existant, sans écraser son contenu 
 +  * ''&>fichier'' a pour effet de rediriger les flux STDOUT et STDERR dans le fichier. 
 +  * ''<'' permet de rediriger un fichier dans l'entrée standard d'une commande, par ex : ''read TOTO < fichier.txt'' (équivaut à ''read TOTO 0< fichier.txt'')
  
-''read toto'' permet de demander une saisie clavier à l'utilisateur.+Ces redirecteurs sont restaurés à leur valeur par défaut à chaque nouvelle ligne ; en revanche tous les processus fils héritent des descripteurs de leur processus père, à moins que celui-ci ne les ferme (voir plus bas).
  
-Pour afficher un texte avant la saisie on utilise -p ; et on peut récupérer plusieurs saisie d'un seul coup +''|'' est aux flux ce que ''>'' est aux fichiers : il permet de rediriger le flux de sortie d'une commande vers l'entrée d'une autre commande. 
-<code> + 
-read -p "Quel est le nombre indique sur votre CB ? Et le cryptogramme visuel de derriere ?" CARD_NUMBER CRYPTO+Pour rediriger le flux d'erreur dans un fichier plutôt que l'afficher à l'écran, on réassigne 2 
 +<code bash
 +./prog 2>/tmp/prog_erreur.log
 </code> </code>
  
 +Pour rediriger le flux d'erreur sur STDOUT et donc l'envoyer dans le pipe :
 +<code bash>
 +./prog 2>&1 | ./prog2  # prog2 va recevoir en entrée les flux STDOUT et STDERR de prog1
 +# équivaut à
 +./prog |& ./prog2
 +# (depuis bash4, "|&" est reconnue comme abréviation de "2>&1 |")
 +</code>
  
-===== Expressions arithmétiques =====+ 
 +Par défaut ''>'' écrase le fichier existant ; pour ajouter les erreurs à la fin du fichier, on utilise ''>>''
 +<code bash> 
 +./prog 2>>/tmp/prog_erreur.log 
 +</code> 
 +Ainsi on conserve les erreurs de chaque exécution du programme. 
 + 
 +====Heredoc==== 
 + 
 +Un document "ici-même" (traduction française de qualitaÿ) est une manière de saisir une chaîne de caractères en conservant l'indentation et les sauts de ligne dans un interpréteur de commande. C'est utile pour les longs textes sur plusieurs lignes qui sont alors beaucoup plus lisibles dans le code. On précède ledit texte par un double chevron ''<nowiki><<</nowiki>'' suivi d'un délimiteur arbitraire puis par un saut de ligne ; la fin de la saisie du texte sera marquée par une ligne contenant uniquement le délimiteur (donc suivi d'un saut de ligne). Plus concrètement, si on choisit le délimiteur "EOF"
 +<code bash> 
 +cat << EOF 
 +Ce   texte 
 + sera affiché tel quel 
 +EOF 
 + 
 +</code> 
 + 
 +On peut utiliser la variante ''<nowiki><<-</nowiki>'' pour ignorer les tabulations de début de ligne (cela permet d'intenter le code). 
 + 
 +====Herestring==== 
 + 
 +Dans la même veine que le heredoc, la herestring permet d'envoyer une chaîne de caractères dans une commande ; il peut d'agir d'une variable : 
 +<code bash> 
 +VAR="dad text" 
 +cat <<< $VAR 
 + dad text 
 + 
 +bc <<< 2*2 
 + 4 
 +</code> 
 +====Descripteurs de fichier additionnels==== 
 + 
 +On peut utiliser les descripteurs 3 et plus également, en les initialisant pour ne pas générer d'erreur (ils ne le sont pas par défaut) : 
 +<code bash> 
 +echo "toto" >&      # erreur "Bad file descriptor" 
 +echo "toto" 3>&1 >& # OK, va afficher le résultat sur 3 qui pointe sur 1 qui est la STDOUT 
 + toto 
 +</code> 
 + 
 +Pour fermer un descripteur de fichier (le 3 par ex) : ''3>&-'' 
 + 
 +On peut faire pointer un descripteur vers un fichier du système : 
 +<code bash> 
 +exec 3<> /tmp/foo   # ouverture du fd 3 
 +echo toto >&
 +exec 3>&          # fermeture du fd 
 +</code> 
 + 
 +Exemple d'utilisation d'un descripteur de fichier sur un fichier : écrire un caractère au milieu de ce dernier (src : [[http://tldp.org/LDP/abs/html/io-redirection.html|tldp.org]]). 
 +<code bash> 
 +#  An application of this is writing at a specified place in a file.  
 +echo 1234567890 > File    # Write string to "File"
 +exec 3<> File             # Open "File" and assign fd 3 to it. 
 +read -n 4 <&            # Read only 4 characters. 
 +echo -n . >&            # Write a decimal point there. 
 +exec 3>&                # Close fd 3. 
 +cat File                  # ==> 1234.67890 
 +</code>       
 + 
 +======Les fonctions====== 
 + 
 +=====function===== 
 + 
 +Elles permettent de mutualiser le code afin qu'il soit appeler plusieurs fois sans avoir à le ré-écrire. 
 +<code bash> 
 +function mafonction() { 
 +  echo "exécution de mafonction" 
 +
 + 
 +# appelle de la fonction, plus bas dans le code 
 +mafonction 
 +</code> 
 +A l'intérieur d'une fonction on peut utiliser des variables locales en les déclarant ''local VAR=""''. Pour envoyer un code de retour on utilise ''return'' (pas ''exit'' comme dans le script principal, sinon on quitte le script !). 
 + 
 +La variable bash ''$FUNCNAME'' permet d'afficher le nom de la fonction courante. 
 + 
 +=====pipe ("|")===== 
 + 
 +Le pipe (tube) est une fonction qui permet de chainer la sortie d'une première commande sur l'entrée de la seconde (c'est un tube/canal inter-processus). 
 +<code bash> 
 +echo "toto et tata" | awk '{$3}' 
 + tata 
 +</code> 
 + 
 +Le soucis c'est qu'on ne récupère que le code de retour de la dernière commande du pipe ; et dans l'exemple précédent, awk renvoie toujours 0. Pour résoudre ce soucis, on peut : 
 +  * récupérer les codes de retour de chaque commande dans le tableau ''$PIPESTATUS'' (spécifique à bash) : 
 +<code bash> 
 +false | true 
 +echo "${PIPESTATUS[@]}" 
 + 1 0 
 +</code> 
 +  * utiliser la variable ''$pipefail'' (ksh, zsh ou bash) qui va enregistrer le code de retour de la dernière commande qui se terminera en erreur (retour != 0) ; ou à défaut, vaudra 0 si toutes se terminent correctement : 
 +<code bash> 
 +$ false | true; echo $? 
 +
 +$ set -o pipefail 
 +$ false | true; echo $? 
 +
 +</code> 
 +===== 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 : 
 +<code bash> 
 +read -p "Quel est ton nom ?" NOM 
 +</code> 
 +Pour masquer la saisie, s'il s'agit d'un mot de passe par exemple, on utilise ''-s''
 +<code bash> 
 +read -s -p "Et ton numéro de CB ?" CARD_NUMBER 
 +</code> 
 + 
 +====Lire depuis un fichier==== 
 +<code bash> 
 +read < fichier.txt 
 + 
 +# c'est plutôt utilisé dans une boucle while, pour le lire ligne par ligne : 
 +while read line; do 
 +  echo $line 
 +done < fichier.txt 
 +</code> 
 + 
 +Pour lire mot par mot, si le fichier est formaté (par exemple s'il contient une liste de "nom prénom") : 
 +<code bash> 
 +while read nom prenom; do 
 +  echo "nom=$nom ; prenom=$prenom" 
 +done < fichier.txt 
 +</code> 
 + 
 +Pour lire depuis une liste de fichiers : 
 +<code bash> 
 +while read line; do 
 +  echo $line 
 +done < <(find . -name foo* -print0) 
 + 
 +# équivalent à : 
 +find . -name foo* -print0 | while read line; do 
 +  echo $line 
 +done 
 +</code> 
 +Le premier exemple utilise une [[https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html#Process-Substitution|process substitution]], qui permet de référencer l'entrée (''>(list)'') ou la sortie (''<(list)'') d'une commande comme un fichier* et non plus un flux ; le second un pipe, mais les 2 se valent. 
 + 
 +* : en réalité bash utilise réellement un fichier temporaire, la preuve : 
 +<code bash> 
 +$ echo <(echo foo) 
 +/dev/fd/63 
 + 
 +$ cat <(echo foo) 
 +foo 
 + 
 +$ xargs echo < <(echo foo) 
 +foo 
 +</code> 
 +====Lire depuis une variable==== 
 + 
 +<code bash> 
 +read <<< $var 
 +</code> 
 + 
 +Seconde façon de faire, et plus adéquate si la variable contient des sauts de ligne : 
 +<code bash> 
 +ECHO="toto1 titi1\ntoto2 titi2"; echo -e "$ECHO" | while read a b; do echo "a=$a ; b=$b"; done 
 + a=toto1 ; b=titi1 
 + a=toto2 ; b=titi2 
 +</code> 
 +====== Expressions arithmétiques ======
  
 Pour faire un calcul il faut l'encadrer de ''<nowiki>$((calcul))</nowiki>'' ou ''$[calcul]'' Pour faire un calcul il faut l'encadrer de ''<nowiki>$((calcul))</nowiki>'' ou ''$[calcul]''
Line 224: Line 549:
  
 Pour incrémenter une variable : plusieurs possibilités : Pour incrémenter une variable : plusieurs possibilités :
-  * ''<nowiki>z=`expr $z + 1`</nowiki>'' +  * ''z=`expr $z + 1`'' ou ''z=$(expr $z + 1)'' 
-  * ''<nowiki>z=$(($z+1))</nowiki>'' +  * ''<nowiki>z=$(($z+1))</nowiki>'' ou ''<nowiki>z=$((z+1))</nowiki>'' (entre doubles parenthèses, le $ est optionnel)
-  * ''<nowiki>z=$((z+1))</nowiki>'' (entre doubles parenthèses, le $ est optionnel)+
   * ''<nowiki>(( z += 1 ))</nowiki>''   * ''<nowiki>(( z += 1 ))</nowiki>''
  
  
-=====Conversions=====+======Conversions======
  
 Convertir $VAR_HEXA de l'hexadecimal en décimal : Convertir $VAR_HEXA de l'hexadecimal en décimal :
Line 238: Line 562:
 let VAR_DECIMAL=0x$VAR_HEXA let VAR_DECIMAL=0x$VAR_HEXA
 </code> </code>
-===== Instructions conditionnelles ===== 
  
-====if==== 
  
-<code>+====== Instructions conditionnelles ====== 
 + 
 +=====if===== 
 + 
 +<code bash>
 test expr test expr
 </code> </code>
  
 ou ou
-<code>+<code bash>
 if [ expr ] if [ expr ]
   then   then
Line 255: Line 581:
  
   * Exemple sur une seule ligne (//-n// renvoie TRUE si la chaine n'est pas vide) :   * Exemple sur une seule ligne (//-n// renvoie TRUE si la chaine n'est pas vide) :
-<code>+<code bash>
 if [ -n "" ]; then echo "1"; else echo "0"; fi if [ -n "" ]; then echo "1"; else echo "0"; fi
   0   0
 </code> </code>
  
-===Conditions multiples===+====Conditions multiples====
 Pour tester des conditions multiples, on doit encadrer les tests avec un double crochet : Pour tester des conditions multiples, on doit encadrer les tests avec un double crochet :
-<code>+<code bash>
 if [[ TRUE && TRUE || FALSE ]] if [[ TRUE && TRUE || FALSE ]]
 then then
Line 272: Line 598:
  
 On peut également procéder ainsi : On peut également procéder ainsi :
-<code>+<code bash>
 if [ TRUE ] && [ TRUE ] || [ FALSE ] if [ TRUE ] && [ TRUE ] || [ FALSE ]
 </code> </code>
Line 279: Line 605:
  
   * Structure avec "elif" ("elseif" dans d'autres langages) qui permet de faire des tests à la suite :   * Structure avec "elif" ("elseif" dans d'autres langages) qui permet de faire des tests à la suite :
-<code>+<code bash>
 if [ expr ] if [ expr ]
   then   then
Line 291: Line 617:
 </code> </code>
  
-=== Expression sur les fichiers ===+====Expression sur les fichiers====
  
   * ''-d file'' : existe et est un répertoire   * ''-d file'' : existe et est un répertoire
Line 304: Line 630:
   * ''-G file'' : si le fichier appartient à notre groupe   * ''-G file'' : si le fichier appartient à notre groupe
  
-=== Chaînes de caractères === +  * ''-t fd'' : vrai si //filedescriptor// fd est ouvert et se réfère à un terminal 
-  * ''str1 = str2'' + 
-  * ''str1 != str2''+====Chaînes de caractères==== 
 +  * ''"str1"str2"'' : teste l'égalité des 2 chaînes 
 +  * ''"str1!= "str2"'' : teste la différence des 2 chaînes 
 +  * ''"str1" =~ regex'' : teste si str1 matche l'expression régulière regex (au format extended, comme ''grep -E'') ;  le test doit être inclus dans une condition ''<nowiki>[[ .. ]]</nowiki>'' et la chaine str1 doit être double-quotée. Si on utilise des groupes capturants (entre ''(..)'') dans la REGEX, on peut rappeler les matchs avec la variable tableau ''${BASH_REMATCH[x]}'', comme on le ferait avec \1, \2, \3 pour sed. Par exemple : 
 +<code bash> 
 +[[ "la réponse est 42" =~ ^.*([0-9]{2}).*$ ]] && echo "${BASH_REMATCH[1]} est la réponse" 
 +42 est la réponse 
 +</code>
   * ''-z str'' : vrai si chaîne de longueur nulle   * ''-z str'' : vrai si chaîne de longueur nulle
   * ''-n str'' : vrai si chaîne de longueur non nulle    * ''-n str'' : vrai si chaîne de longueur non nulle 
  
-=== Nombres, comparaison ===+==== Nombres, comparaison ====
   * ''n1 -eq n2'' égaux   * ''n1 -eq n2'' égaux
   * ''n1 -ne n2'' non égaux   * ''n1 -ne n2'' non égaux
Line 318: Line 651:
   * ''n1 -le n2'' plus petit ou égal   * ''n1 -le n2'' plus petit ou égal
  
-=== Opérateurs ===+Comme il n'y a pas de typage en bash, pour tester si la valeur d'une variable est un nombre (entier) il faut réaliser le tester : 
 +<code bash> 
 +# Solution à base de REGEX 
 +[[ $VAR =~ ^[0-9]+$ ]] || echo "VAR n'est pas un entier" 
 +# NB : l'expression régulière ne doit pas être quotée, 
 +#      ou doit être enregistrée dans une variable sinon ça ne matche pas 
 + 
 +# "auto-test" numérique :) 
 +[ $VAR -eq $VAR ] 2>/dev/null || echo "VAR n'est pas un entier" 
 +</code> 
 +==== Opérateurs ====
   * ''!'' unaire de négation   * ''!'' unaire de négation
   * ''-a'' et logique   * ''-a'' et logique
Line 326: Line 669:
  
  
-==== case ====+===== case =====
  
 Test plusieurs valeurs de <chaîne> (~ifs imbriqués). Test plusieurs valeurs de <chaîne> (~ifs imbriqués).
- +<code bash> 
-  case <chaîne> in +case <chaîne> in 
-   motif1) + motif1) 
-      echo "motif 1" +    echo "motif 1" 
-      echo "C'est le premier !!" +    echo "C'est le premier !!" 
-      ;; +    ;; 
-   motif2|motif3) + motif2|motif3) 
-      echo "motif 2 ou motif 3" +    echo "motif 2 ou motif 3" 
-      ;; +    ;; 
-   motif*) + motif*) 
-      echo "tous les autres motifs" +    echo "tous les autres motifs" 
-      ;; +    ;; 
-   *) + *) 
-      echo "Tout le reste" +    echo "Tout le reste" 
-      ;; +    ;; 
-  esac+esac 
 +</code>
  
  
-==== for ====+===== for =====
  
 Permet de créer une boucle déterministe : pour chaque valeur de i, on exécute la boucle. Permet de créer une boucle déterministe : pour chaque valeur de i, on exécute la boucle.
  
 Exemples : Exemples :
-<code>+<code bash>
 for (( i = 1; i <= 5; i++ )) for (( i = 1; i <= 5; i++ ))
   do   do
Line 360: Line 704:
 Affichera : ''1 2 3 4 5 ''. 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.+Cela équivaut à : ''for i in {1..5}'' qui est une autre façon simplifiée d'incrémenter i de 1 à 5. Cette méthode ne permet pas d'utiliser des variables ; pour cela il faut utiliser l'expression longue : 
 +<code bash> 
 +DEBUT=1 
 +FIN=5 
 +# ne fonctionne pas : for i in {$DEBUT..$FIN} 
 +# fonctionne : 
 +for (( i = $DEBUT; i <= $FIN; i++ )) 
 +</code> 
 + 
 +On peut spécifier l'incrément qui vaut par défaut 1, avec : ''for i in {1..5..2}'' (ici incrément de 2).
  
 On peut aussi lui fournir une liste de mots : On peut aussi lui fournir une liste de mots :
-<code>+<code bash>
 for i in serveur1 serveur2 serveur3 for i in serveur1 serveur2 serveur3
 do do
Line 371: Line 724:
  
 On peut l'exécuter sur une seule ligne de commande en respectant cette syntaxe : On peut l'exécuter sur une seule ligne de commande en respectant cette syntaxe :
-<code>+<code bash>
 for i in serveur1 serveur2 serveur3; do ssh $i "(uname -r)"; done for i in serveur1 serveur2 serveur3; do ssh $i "(uname -r)"; done
 </code> </code>
  
-===break===+====break====
 Pour sortir prématurément d'une boucle for, **while** ou **repeat**, on utilise le mot-clé **break** : Pour sortir prématurément d'une boucle for, **while** ou **repeat**, on utilise le mot-clé **break** :
-<code>+<code bash>
 for i in {1..5} for i in {1..5}
 do do
Line 390: Line 743:
  
 Ce qui donne : Ce qui donne :
-<code>+<code bash>
 1 1
 2 2
Line 396: Line 749:
 </code> </code>
  
-===continue===+====continue====
 Pour sortir de l'occurrence courante de la boucle, on utilise **continue**. On quitte l'occurrence courante et on passe à la suivante : Pour sortir de l'occurrence courante de la boucle, on utilise **continue**. On quitte l'occurrence courante et on passe à la suivante :
-<code>+<code bash>
 for i in {1..5} for i in {1..5}
 do do
Line 411: Line 764:
  
 Ce qui donne : Ce qui donne :
-<code>+<code bash>
 1 1
 2 2
Line 419: Line 772:
 </code> </code>
  
-====while====+=====while=====
  
 Exécute la boucle tant que la condition suivant l'instruction **while** est remplie : Exécute la boucle tant que la condition suivant l'instruction **while** est remplie :
  
-exemple : lire un fichier ligne par ligne +exemple : compter de 1 à 5... 
-<code>+<code bash> 
 +cpt=1 
 +while [ $cpt -le 5 ]; do 
 +   echo $cpt 
 +   (( cpt++ )) 
 +done 
 +</code> 
 + 
 +exemple2 : lire un fichier ligne par ligne 
 +<code bash>
 while read line while read line
 do do
   echo $line   echo $line
 done < fic.txt done < fic.txt
 +</code>
 +
 +La même mais avec la sortie d'une commande :
 +<code bash>
 +cat fic.txt | while read line
 +do
 +  echo $line
 +done
 </code> </code>
  
 ou sur une seule ligne : ou sur une seule ligne :
-<code>while read n; do echo $line; done < fic.txt</code>+<code bash>while read n; do echo $line; done < fic.txt</code>
  
-====until====+=====until=====
  
 Exécute la boucle jusqu'à ce que la condition soit remplie : Exécute la boucle jusqu'à ce que la condition soit remplie :
-<code>+<code bash>
 until <condition> until <condition>
 do do
Line 444: Line 814:
 </code> </code>
  
-====select====+=====select=====
  
 Permet de créer un menu interactif, c'est à dire une saisie utilisateur ; Permet de créer un menu interactif, c'est à dire une saisie utilisateur ;
Line 450: Line 820:
   * $REPLY contient l'index de la réponse   * $REPLY contient l'index de la réponse
  
-<code>+<code bash>
 PS3="Prompt du menu" PS3="Prompt du menu"
 select i in {2..5} select i in {2..5}
Line 464: Line 834:
  
  
-=====Divers=====+======Divers======
  
-====debug====+=====debug=====
  
 Pour débugger un script, on insère en début de script la commande ''set -x''. Pour débugger un script, on insère en début de script la commande ''set -x''.
Line 487: Line 857:
 </code> </code>
  
-==== Substitution de chaine ====+===== Substitution de chaine =====
  
 Pour n'afficher qu'une partie d'une chaine (une sous-chaine donc), on utilise la syntaxe ''${tst:<offset>:<length>}''. Pour n'afficher qu'une partie d'une chaine (une sous-chaine donc), on utilise la syntaxe ''${tst:<offset>:<length>}''.
Line 511: Line 881:
 chanceux chanceux
 </code> </code>
-==== Remplacement de chaine ====+ 
 +===== Remplacement de chaine =====
  
 Syntaxe : ''${<parameter>/<pattern>/<string>}'' Syntaxe : ''${<parameter>/<pattern>/<string>}''
Line 538: Line 909:
 </code> </code>
  
-====Modifier la casse====+=====Modifier la casse=====
  
 Pour modifier la casse, on utilise la syntaxe : Pour modifier la casse, on utilise la syntaxe :
Line 572: Line 943:
 </code> </code>
  
-====Préfixes et suffixes====+=====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 ''%%''. 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 ''%%''.
Line 590: Line 961:
 /chemin/ve.rs/mon /chemin/ve.rs/mon
 </code> </code>
-===== Liens =====+ 
 +=====trim (suppression des espaces dans une chaîne)===== 
 + 
 +bash ne possède pas de fonction prédéfinie pour "trimmer" les chaînes de caractère. **trim** est une fonction, habituellement incluse dans les langages de programmation, permettant de supprimer les espaces parasites avant et après une chaîne : par exemple " cette chaîne" ou " celle-ci       "
 + 
 +Pour faire cette office il existe plusieurs possibilités d'afficher les cellules du tableau suivant sans les espaces : 
 +<code bash> 
 +arr=('foo bar' 'test@domain.com ' \ 
 +' test@domain.com ' '    test@domain.com         '
 +</code> 
 + 
 +  * en utilisant la substitution de caractère dans les variables, en activant le //globbing étendu// (des motifs d'expressions régulières prédéfinis) (source: [[https://www.cyberciti.biz/faq/how-to-trim-leading-and-trailing-white-space-in-bash/|cyberciti.biz]]) : 
 +<code bash> 
 +shopt -s extglob                     # activation des extended glob 
 +# (pour reconnaître [[:blank:]] qui définit l'ensemble des espaces et tabulations) 
 +arr=( "${arr[@]/#+([[:blank:]])/}" ) # suppression des espaces de début de chaîne 
 +arr=( "${arr[@]/%+([[:blank:]])/}" ) # suppression des espaces de fin de chaîne 
 +</code> 
 + 
 +  * avec ''mapfile'' et ''<nowiki>set --</nowiki>'' (source: [[https://stackoverflow.com/questions/61081608/bash-remove-leading-and-trailing-spaces-in-array/61087835#61087835|stackoverflow]]) 
 +<code bash> 
 +function trim() { 
 + mapfile -t t_input<<<"$*"    # place chaque paramètre dans une cellule du tableau (un par ligne) 
 + [[ ${#t_input[@]} -eq 0 ]] && { echo "Aucun texte à afficher"; exit 1; } 
 + set -- ${t_input[@]}         # affecte chaque case du tableau à un paramètre ($1, $2, etc) 
 + printf '%s\n' "$*"           # affiche chaque paramètre suivi d'un saut de ligne 
 +
 +trim "${arr[@]}" 
 +</code> 
 +====== Ressources ======
  
   * http://abs.traduc.org/abs-5.0-fr/index.html   * http://abs.traduc.org/abs-5.0-fr/index.html
   * http://www.gnu.org/software/bash/manual/bashref.html   * http://www.gnu.org/software/bash/manual/bashref.html
   * [[http://tldp.org/LDP/abs/html/index.html|Advanced Bash-Scripting Guide]]   * [[http://tldp.org/LDP/abs/html/index.html|Advanced Bash-Scripting Guide]]
 +  * https://www.shellcheck.net : super moulinette en ligne pour analyser un script et recommander des améliorations (exsite aussi en package Linux : ''shellcheck''). A utiliser sans modération !
 +  * [[https://google.github.io/styleguide/shellguide.html|Shell Style Guide]] by Google
 +  * [[https://betterdev.blog/minimal-safe-bash-script-template/|Minimal safe Bash script template]] (betterdev.blog)
 +  * [[https://github.com/pforret/bashew/blob/master/README.md|bashew]] / [[https://blog.forret.com/portfolio/bashful/|bashful]] : un outil de création de script bash et le blog de son auteur, Peter Forret
 +  * [[https://github.com/ralish/bash-script-template|bash-script-template]]
informatique/linux/programmation_shell.1486558301.txt.gz · Last modified: 2017/02/08 12:51 by pteu