Shell scripting

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) :

  1. faire une seule chose et sans blabla
  2. prendre et rendre du texte pas de fichiers binaires
  3. utiliser les entrés/sorties standard
  4. vos entrées et vos sorties doivent avoir le même format
  5. "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é.

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 :

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 :

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 :

Supposons que vous ayez un fichier exécutable pid.sh contenant :

#! /bin/sh

echo $$
  1. Le pid du shell courant est accessible avec : echo $$
  2. Le pid précédent est différent du pid du shell exécutant le script : ./pid.sh
  3. 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

TBD https://labex.io/fr/tutorials/shell-bash-getopt-391993

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 trecho "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 trecho "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).