Shell scripting
- histoire : https://www.in-ulm.de/~mascheck/bourne/
- parameter expansion : https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02
TBD args et getopts :
sh cheat sheet : <joedog.org/articles-cheat-sheet/> https://stackoverflow.com/questions/5725296/difference-between-sh-and-bash https://stackoverflow.com/a/5725402 https://www.commandlinux.com/man-page/man1/sh.1.html
Les principes d'un bon script sont (voir classic shell scripting) :
- faire une seule chose et sans blabla
- prendre et rendre du texte pas de fichiers binaires
- utiliser les entrés/sorties standard
- vos entrées et vos sorties doivent avoir le même format
- "laisser quelqu'un d'autre faire les choses compliquées" : utiliser d'autres commandes unix
À retenir
Essayer d'être portable. On fait du du sh pour pouvoir être exécuté partout.
TBD faire exemple du rot13 tout au long de ce cours
Taper des commandes = script. Comme python. Il faut trouver un moyen de faire des bouts de commandes sans les executer a=à la fin d'une ligne. Python fait des blocs. shell fait autrement. De plus, tout est orienté commandes sans pratiquement aucune surcouche du shell (on le verra avec les if/then/else qui fonctionnent bien différemment du reste des langages de programmation)
#! /bin/sh -
Variables d'environnement
python
import os
print(os.environ["PATH"])
print(os.environ.get("PORT", 3000))
# print(type(os.environ.get("PORT", 3000))) # attention
Puis :
$ python variables.py
$ PORT=1456 python variables.py
node
console.log(process.env.PATH)
console.log(process.env.PORT || 3000)
env file
Il n'est pas commité.
- https://pypi.org/project/python-dotenv/
node --env-file-if-exists=.env app.js- https://github.com/motdotla/dotenv
set -a && source .env && set +avoir : shell
Gestion des paramètres
Fonctions
Les fonctions ne peuvent rendre qu'un entier, c'est leur code de sortie. Pour le reste effectuez vos sorties sur la sortie standard (avec echo).
Structures de contrôle
if/then
if
commandes
then
commandes
fi
Avec un else :
if
commandes
then
commandes
else
commandes
fi
C'est le retour de la dernière commande avant la ligne avec then qui décide du branchement :
- s'il vaut 0 on fait le bloc allant de
thenàelse(oufis'il n'y a pas de blocelse) - sinon on fait le bloc allant de
elseàfi
Par exemple, en utilisant les commandes true et false :
if
true
then
echo oui
else
echo non
fi
Ou encore :
if
false
true
then
echo oui
else
echo non
fi
On peut aussi avoir la forme où la première commande peut être placée après le if :
if true
then
echo oui
else
echo non
fi
La forme précédente qui est très pratique lorsque l'on a qu'une seule commande.
Une commande doit précéder l'instruction then si on peut avoir sur une même ligne le if et le then, il faut terminer la commande de test avec un ; :
if true; then
echo oui
else
echo non
fi
La commande test qui est aussi la commande [ (oui, [ est un fichier de /usr/bin) permet de faire de nombreux test courant :
Bash permet également d'utiliser une construction utilisant [[ expression ]] qui rend certains tests plus clair mais est spécifique à bash et ne fonctionnera pas avec d'autres shell ([[ n'est pas une commande, c'est une instruction interne à bash). Préférez donc les constructions avec [ ou test, plus portable.
Il est possible de tester de nombreuses choses, allant de conditions logique à l'existence de fichiers :
boucles for/while/until
Les constructions sont identiques à la construction du if. La seule particularité est la boucle for qui itère sur une suite de mot séparé par des espaces :
for x in salut les gars
do
echo $x
done
Ou, de façon équivalente :
for x in salut les gars; do
echo $x
done
On utilise parfois la boucle while pour lire l'entrée standard, en combinaison avec la commande read :
while read line
do
echo "$line"
done < /dev/stdin
Ou le redoutable :
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
Qui lit l'entrée standard si le premier paramètre ($1) n'est pas positionné. Cela utilise une spécificité de sh pour gérer les variables.
Variables
Métacaractères
On a déjà vu les métacaractères du shell, ils commencent par $ et peuvent être utilisés dans les scripts :
$?: le code de sortie de la dernière commande$$: le PID du shell courant$(expression): pour exécuter l'expression et donner son affichage comme argument. Par exempleecho $(expr 3 + 4). C'est bien ce qui est affiché qui est rendu, pas son code de sortie.$((arithmétique)): pour exécuter des opérations arithmétiques, par exempleecho $((3+4))${variable}: pour afficher le contenu d'une variable, par exempleecho ${PAH}
Variables internes
Des variables créés par le shell et pouvant être utilisé dans les scripts. A utiliser avec parcimonie car cela rend vos scripts spécifiques à bash.
Process et shell
TBD mieux faire
Le script est par défaut exécuté dans un nouveau shell. Mais ce n'est pas toujours ce que l'on veut :
./truc.shexécution dans un nouveau shell enfantsource ./truc.shexécution ligne à ligne dans le shell actuel. ./truc.shexécution ligne à ligne dans le shell actuel (identique àsource)exec ./truc.sh` nouveau shell qui remplace le shell existant
Supposons que vous ayez un fichier exécutable pid.sh contenant :
#! /bin/sh
echo $$
- Le pid du shell courant est accessible avec :
echo $$ - Le pid précédent est différent du pid du shell exécutant le script :
./pid.sh - On devrait retrouver le même pid en tapant :
source ./pid.sh
C'est très utile lorsque l'on exécute un fichier de configuration qui doit s'appliquer au shell courant.
Attention, si vous exécutez exec ./pid.sh le shell faisant le echo vq remplacer le shell courant et donc fermer la fenêtre. Pour l'exécuter sans soucis faite le dans un sous-shell :
$ echo $$
704757
$ bash
$ echo $$
705824
$ exec ./pid.sh
705824
$ echo $$
704757
Getopt
Bash scripting
TBD étoffer et vérifier que je ne raconte pas de bêtises. TBD dans les systèmes actuels sh = bash
on s'amuse avec rot13
❯ echo "coucou" | tr "[:lower:]" "[:upper:]"
COUCOU
❯ echo "coécou" | tr "[:lower:]" "[:upper:]"
COÉCOU
❯ man tr
❯ echo "coécou" | tr "[:lower:][=e=]" "[:upper:]e"
COeCOU
❯ echo "coécou" | tr "[:lower:][=ea=]" "[:upper:]ea"
tr: misplaced equivalence equals sign
❯ echo "coécou" | tr "[:lower:][=e=]" "[:upper:]e"
COeCOU
❯ echo "coécou" | tr "[:lower:]" "[:upper:]" | tr "[=E=]" "e"
COeCOU
❯ echo "coécou" | tr "[:lower:]" "[:upper:]" | tr "[=E=]" "E"
COECOU
Attention :
❯ echo "coécouê" | tr "[=e=]" "e" | tr "[:lower:]" "[:upper:]"
COECOUE
mais :
❯ echo "coécouå" | tr "[=e=][=a=]" "ea" | tr "[:lower:]" "[:upper:]"
COACOUA
[=e=] correspond à des listes :
❯ man tr
❯ echo "coécouå" | tr "[=e=][=a=]" "eeeeeeea" | tr "[:lower:]" "[:upper:]"
COACOUA
❯ echo "coécouå" | tr "[=e=][=a=]" "eeeeeeeeeeeeea" | tr "[:lower:]" "[:upper:]"
COECOUA
Mieux vaut tout chaîner :
❯ echo "coécouå" | tr "[=e=]" "e" | tr "[=a=]" "a" | tr "[:lower:]" "[:upper:]"
COECOUA
mais en vrai il vaut mieux utiliser iconv :
echo "coécouå" | iconv -c -f utf8 -t ascii//translit
sous Linux. Marche pas sous macos (mauvaise lib de libconf).