Difference between revisions of "HelloBash Ep3"
(→Gestion des arguments et des options) |
(→Les boucles) |
||
(19 intermediate revisions by the same user not shown) | |||
Line 4: | Line 4: | ||
=== Un premier script === | === Un premier script === | ||
− | echo | + | echo '#!/bin/bash' > /tmp/myscript.sh |
echo "echo helloBash" >> /tmp/myscript.sh | echo "echo helloBash" >> /tmp/myscript.sh | ||
cat /tmp/myscript.sh | bash | cat /tmp/myscript.sh | bash | ||
Line 67: | Line 67: | ||
./myscript.sh Epstein did not kill himself | ./myscript.sh Epstein did not kill himself | ||
− | |||
− | |||
− | |||
C'est la première manière de fournir des entrées utilisateurs : passer des arguments à la ligne de commande pour moduler le fonctionnement du script. | C'est la première manière de fournir des entrées utilisateurs : passer des arguments à la ligne de commande pour moduler le fonctionnement du script. | ||
Line 79: | Line 76: | ||
# ./nocomment.sh /etc/adduser.conf | # ./nocomment.sh /etc/adduser.conf | ||
− | == | + | Notez que pour avoir des options i.e. des <code>--this</code> ou <code>-a</code> et donc avoir une ligne de commande plus riche et plus adaptable, on utilise <code>getopts</code> (voir plus loin). |
− | == | + | |
− | + | == Les entrées utilisateurs== | |
+ | |||
+ | On utilise des variables pour stocker les valeurs saisies par l'utilisateur. | ||
+ | |||
+ | #!/bin/bash | ||
+ | user_input=$1 | ||
+ | echo "You typed: $user_input" | ||
+ | |||
+ | myscript.sh hello world | ||
+ | |||
+ | On peut également utiliser stdin comme moyen de passer des variables | ||
+ | |||
+ | #!/bin/bash | ||
+ | std_in=$( cat - ) | ||
+ | echo ${std_in} | ||
+ | |||
+ | echo "hello bash" | myscript.sh | ||
+ | |||
+ | == Les branches conditionnelles== | ||
+ | |||
+ | On utilise des structures logiques pour changer l'exécution du code en fonction du contexte. | ||
+ | |||
+ | '''IF / ELIF / FI''' | ||
+ | |||
+ | file=/etc | ||
+ | if [[ -d "$file" ]] ; then | ||
+ | echo "the file exists and is a directory" | ||
+ | elif [[ -e "$file" ]] ; then | ||
+ | echo "the file exists" | ||
+ | else | ||
+ | echo "no such file" | ||
+ | fi | ||
+ | |||
+ | La directive conditionnelle suit le premier embranchement dont le résultat est valide, ici en utilisant un test (voir suite) mais if supporte l'exécution en direct | ||
+ | |||
+ | if COMMANDE; then ... ; elif COMMANDE; then ...; else ...; fi | ||
+ | |||
+ | if grep "#" /etc/adduser.conf &>/dev/null; then echo ok; fi | ||
+ | |||
+ | |||
+ | ""test"" | ||
+ | |||
+ | Arrêtons nous sur les tests qui ont été fait dans l'exemple précédent. Les 3 formulations suivantes sont équivalentes | ||
+ | |||
+ | [ -d /etc ] | ||
+ | [[ -d /etc ]] | ||
+ | test -d /etc | ||
+ | |||
+ | '''Il est conseillé de nos jours d'utiliser <code>[[</code> plutôt que le simple crochet car le premier est plus sûr et plus complet, [http://mywiki.wooledge.org/BashFAQ/031 voir ici pour une explication étendue].''' | ||
+ | |||
+ | Les tests standards sont | ||
+ | |||
+ | -x fichier Vrai si le fichier existe et est exécutable. | ||
+ | -b fichier Vrai si le fichier existe et est spécial bloc. | ||
+ | -c fichier Vrai si le fichier existe et est spécial caractère. | ||
+ | -d fichier Vrai si le fichier existe et est un répertoire. | ||
+ | -e fichier Vrai si le fichier existe. | ||
+ | -f fichier Vrai si le fichier existe et est un fichier ordi naire. | ||
+ | -g fichier Vrai si le fichier existe et a son bit Set-GID positionné. | ||
+ | -k fichier Vrai si le fichier existe et a son bit Sticky posi tionné. | ||
+ | -L fichier Vrai si le fichier existe et est un lien symbol ique. | ||
+ | -p fichier Vrai si le fichier existe et est un tube nommé. | ||
+ | -r fichier Vrai si le fichier existe et est lisible. | ||
+ | -s fichier Vrai si le fichier existe et a une taille supérieure à zéro. | ||
+ | -S fichier Vrai si le fichier existe et est une socket. | ||
+ | |||
+ | -z chaîne Vrai si la longueur de la chaîne est nulle. | ||
+ | -n chaîne chaîne Vrai si la longueur de la string n'est pas nulle. | ||
+ | |||
+ | |||
+ | -t [fd] Vrai si fd est ouvert sur un terminal. Si fd est omis, la valeur par défaut est 1 (sortie standard). | ||
+ | -u fichier Vrai si le fichier existe et a son bit Set-UID positionné. | ||
+ | -w fichier Vrai si le fichier existe et est accessible en écriture. | ||
+ | -O fichier Vrai si le fichier existe et appartient à l'UID effectif de l'appelant. | ||
+ | -G fichier Vrai si le fichier existe et appartient au GID effectif de l'appelant. | ||
+ | fichier1 -nt fichier2 Vrai si fichier1 est plus récent (d'après les dates de modification) que fichier2. | ||
+ | fichier1 -ot fichier2 Vrai si fichier1 est plus ancien que fichier2 | ||
+ | fichier1 -ef fichier2 Vrai si fichier1 et fichier2 ont les mêmes numéros de périphérique et d'i-noeud. | ||
+ | chaîne1 = chaîne2 Vrai si les deux chaînes sont égales. | ||
+ | chaîne1 != chaîne2 Vrai si les deux chaînes sont différentes. | ||
+ | ! expr Vrai si expr est fausse. | ||
+ | expr1 -a expr2 Vrai si expr1 et expr2 sont toutes les deux vraies. | ||
+ | expr1 -o expr2 Vrai si expr1 ou expr2 est vraie. | ||
+ | arg1 OP arg2 OP est dans la liste -eq, -ne, -lt, -le, -gt, ou -ge. Ces opérateurs arithmétiques renvoient vrai si arg1 est égal, différent, inférieur, inférieur ou égal, supérieur, ou supérieur ou égal à arg2, respectivement. arg1 et arg2 doivent être des entiers (positifs, ou négatifs) ou l'expression spéciale -l chaîne, qui évalue la longueur de la chaîne. | ||
+ | |||
+ | On peut utiliser les tests pour effectuer directement des actions avec les opérateurs <code>&&</code> et <code>||</code> | ||
+ | |||
+ | [[ -f ~/.ssh ]] && echo "The SSH folder exists" || ssh-keygen | ||
+ | |||
+ | '''CASE''' | ||
+ | |||
+ | Case est un peu plus compliqué | ||
+ | |||
+ | read -p "say something (hello|bye|...) : " input | ||
+ | |||
+ | case $input in | ||
+ | hello) | ||
+ | echo "You said hello" | ||
+ | ;; | ||
+ | bye) | ||
+ | echo "You said bye" | ||
+ | ;; | ||
+ | *) | ||
+ | echo "You said something else..." | ||
+ | ;; | ||
+ | esac | ||
+ | |||
+ | '''Combiner les structures''' | ||
+ | |||
+ | #! /bin/bash | ||
+ | while getopts "ha:" option ; do | ||
+ | case "$option" in | ||
+ | h) | ||
+ | echo "this is help" | ||
+ | ;; | ||
+ | a) | ||
+ | echo "this is the -a option and its parameter '$OPTARG'" | ||
+ | ;; | ||
+ | *) | ||
+ | echo "unknown option" | ||
+ | ;; | ||
+ | esac | ||
+ | done | ||
+ | |||
+ | myscript.sh -h -a yo | ||
+ | |||
+ | '''SELECT''' | ||
+ | |||
+ | Moins courant mais pratique pour générer des menus | ||
+ | |||
+ | select fname in *; | ||
+ | do | ||
+ | echo you picked $fname \($REPLY\) | ||
+ | break; | ||
+ | done | ||
+ | |||
+ | == Les boucles== | ||
+ | |||
+ | Elles sont utiles pour itérer sur une liste composée d'espaces, de tabulation ou de sauts de ligne servant de séparateur | ||
+ | |||
+ | PROTIP: pourquoi modifier $IFS ? | ||
+ | |||
+ | '''FOR''' | ||
+ | |||
+ | for f in /etc/*; do | ||
+ | [[ -d "$f" ]] && echo "$f is a directory" | ||
+ | done | ||
+ | |||
+ | '''WHILE''' | ||
+ | |||
+ | sudo ls -d /etc/* | while read f; do | ||
+ | [[ -d "$f" ]] && echo "$f is a directory" | ||
+ | done | ||
+ | |||
+ | |||
+ | Note: continue et break | ||
+ | |||
+ | == La structure logique== | ||
+ | |||
+ | Votre s | ||
+ | |||
+ | |||
+ | == Le contrôle des statuts de sortie== |
Latest revision as of 23:14, 28 November 2019
Retour à la liste des épisodes
Contents
Ecrire et exécuter des scripts
Un premier script
echo '#!/bin/bash' > /tmp/myscript.sh echo "echo helloBash" >> /tmp/myscript.sh cat /tmp/myscript.sh | bash
L'entête #! ("Shebang" ou "Crunchbang") indique quel est l'interpréteur à utiliser (ici Bash, mais on pourrait en utiliser d'autre)
L'extension .sh est une convention pour les scripts shell en général.
On voit qu'on peut utiliser le contenu brut du script comme STDIN de bash pour l'exécuter.
Exécuter un script
Pour l'exécuter il faut ajouter le droit d'exécution sur le fichier
chmod 755 /tmp/myscript.sh bash -c /tmp/myscript.sh /tmp/myscript.sh
Qualités et concepts inhérents aux scripts
Toutes les commandes disponibles interactivement sont ajoutables dans un script.
Un script doit :
- être réutilisable - automatiquement (cron) ou avec des entrées utilisateurs différentes
- ne faire que le nécessaire - usage des structures logiques et des tests
- être programmé de manière logique et compréhensible - une structure claire et lisible
- s'exécuter sans erreurs - détection des échecs et abandon éventuel
Les concepts suivants sont à maîtriser pour rédiger de bons scripts :
- Les entrées utilisateurs
- Les branches conditionnelles
- Les boucles
- La structure logique
- Le contrôle des statuts de sortie
Gestion des arguments et des options
Avant d'aborder les concepts essentiels, petit détour par la gestion des éléments envoyés au script depuis la ligne de commande. Un exemple.
cat ./myscript.sh #!/bin/bash echo "My name is $0" echo "My first argument is $1"
./myscript.sh foo
On voit donc que la commande est stockée dans la variable $0
et le premier argument dans $1
.
Bash ne sait pas nativement reconnaître des options, le script ne voit que des arguments les uns derrière les autres, accessibles dans des variables numérotées.
Une variable spéciale $@
contient tous les arguments passés à la commande, donc de $1
à $9
.
cat ./myscript.sh #!/bin/bash echo "Arg1: $1 - Arg2: $2 - Arg3: $3 - Arg4: $4 - Arg5: $5 " echo "All: $@ "
./myscript.sh Epstein did not kill himself
C'est la première manière de fournir des entrées utilisateurs : passer des arguments à la ligne de commande pour moduler le fonctionnement du script.
cat ./nocomment.sh #!/bin/bash grep -Ev '^\s*([;#].*)?$' $@
# ./nocomment.sh /etc/adduser.conf
Notez que pour avoir des options i.e. des --this
ou -a
et donc avoir une ligne de commande plus riche et plus adaptable, on utilise getopts
(voir plus loin).
Les entrées utilisateurs
On utilise des variables pour stocker les valeurs saisies par l'utilisateur.
#!/bin/bash user_input=$1 echo "You typed: $user_input"
myscript.sh hello world
On peut également utiliser stdin comme moyen de passer des variables
#!/bin/bash std_in=$( cat - ) echo ${std_in} echo "hello bash" | myscript.sh
Les branches conditionnelles
On utilise des structures logiques pour changer l'exécution du code en fonction du contexte.
IF / ELIF / FI
file=/etc if [[ -d "$file" ]] ; then echo "the file exists and is a directory" elif [[ -e "$file" ]] ; then echo "the file exists" else echo "no such file" fi
La directive conditionnelle suit le premier embranchement dont le résultat est valide, ici en utilisant un test (voir suite) mais if supporte l'exécution en direct
if COMMANDE; then ... ; elif COMMANDE; then ...; else ...; fi
if grep "#" /etc/adduser.conf &>/dev/null; then echo ok; fi
""test""
Arrêtons nous sur les tests qui ont été fait dans l'exemple précédent. Les 3 formulations suivantes sont équivalentes
[ -d /etc ] [[ -d /etc ]] test -d /etc
Il est conseillé de nos jours d'utiliser [[
plutôt que le simple crochet car le premier est plus sûr et plus complet, voir ici pour une explication étendue.
Les tests standards sont
-x fichier Vrai si le fichier existe et est exécutable. -b fichier Vrai si le fichier existe et est spécial bloc. -c fichier Vrai si le fichier existe et est spécial caractère. -d fichier Vrai si le fichier existe et est un répertoire. -e fichier Vrai si le fichier existe. -f fichier Vrai si le fichier existe et est un fichier ordi naire. -g fichier Vrai si le fichier existe et a son bit Set-GID positionné. -k fichier Vrai si le fichier existe et a son bit Sticky posi tionné. -L fichier Vrai si le fichier existe et est un lien symbol ique. -p fichier Vrai si le fichier existe et est un tube nommé. -r fichier Vrai si le fichier existe et est lisible. -s fichier Vrai si le fichier existe et a une taille supérieure à zéro. -S fichier Vrai si le fichier existe et est une socket.
-z chaîne Vrai si la longueur de la chaîne est nulle. -n chaîne chaîne Vrai si la longueur de la string n'est pas nulle.
-t [fd] Vrai si fd est ouvert sur un terminal. Si fd est omis, la valeur par défaut est 1 (sortie standard). -u fichier Vrai si le fichier existe et a son bit Set-UID positionné. -w fichier Vrai si le fichier existe et est accessible en écriture. -O fichier Vrai si le fichier existe et appartient à l'UID effectif de l'appelant. -G fichier Vrai si le fichier existe et appartient au GID effectif de l'appelant. fichier1 -nt fichier2 Vrai si fichier1 est plus récent (d'après les dates de modification) que fichier2. fichier1 -ot fichier2 Vrai si fichier1 est plus ancien que fichier2 fichier1 -ef fichier2 Vrai si fichier1 et fichier2 ont les mêmes numéros de périphérique et d'i-noeud. chaîne1 = chaîne2 Vrai si les deux chaînes sont égales. chaîne1 != chaîne2 Vrai si les deux chaînes sont différentes. ! expr Vrai si expr est fausse. expr1 -a expr2 Vrai si expr1 et expr2 sont toutes les deux vraies. expr1 -o expr2 Vrai si expr1 ou expr2 est vraie. arg1 OP arg2 OP est dans la liste -eq, -ne, -lt, -le, -gt, ou -ge. Ces opérateurs arithmétiques renvoient vrai si arg1 est égal, différent, inférieur, inférieur ou égal, supérieur, ou supérieur ou égal à arg2, respectivement. arg1 et arg2 doivent être des entiers (positifs, ou négatifs) ou l'expression spéciale -l chaîne, qui évalue la longueur de la chaîne.
On peut utiliser les tests pour effectuer directement des actions avec les opérateurs &&
et ||
[[ -f ~/.ssh ]] && echo "The SSH folder exists" || ssh-keygen
CASE
Case est un peu plus compliqué
read -p "say something (hello|bye|...) : " input
case $input in hello) echo "You said hello" ;; bye) echo "You said bye" ;; *) echo "You said something else..." ;; esac
Combiner les structures
#! /bin/bash while getopts "ha:" option ; do case "$option" in h) echo "this is help" ;; a) echo "this is the -a option and its parameter '$OPTARG'" ;; *) echo "unknown option" ;; esac done
myscript.sh -h -a yo
SELECT
Moins courant mais pratique pour générer des menus
select fname in *; do echo you picked $fname \($REPLY\) break; done
Les boucles
Elles sont utiles pour itérer sur une liste composée d'espaces, de tabulation ou de sauts de ligne servant de séparateur
PROTIP: pourquoi modifier $IFS ?
FOR
for f in /etc/*; do [[ -d "$f" ]] && echo "$f is a directory" done
WHILE
sudo ls -d /etc/* | while read f; do [[ -d "$f" ]] && echo "$f is a directory" done
Note: continue et break
La structure logique
Votre s