Administration Unix - Annexe 1
10. Compléments
10.1. La grammaire du shell
La ligne de commande peut être un assemblage de différents éléments :
(légende : [] signifie optionnel, ... signifie 1 ou plusieurs)
-
Commande : par priorité décroissante
- une fonction bash
- une commande interne de bash
- un fichier exécutable (binaire ou script)
-
Commande simple :
[variable=valeur]... commande [argument]... [redirection]... -
Pipeline de commandes :
[!] commande_simple [ | commande_simple]... -
Liste de commandes :
pipeline [SEP pipeline]... [; ou &] # séparateur SEP parmi ; & && || -
Commande composée : (
list_codésigne une liste de commande)(list_co) # liste_co exécutée dans un sous-shell { list_co ;} # liste_co exécutée dans le même shell ((expression)) [[ expression ]] if list_co ; then list_co ; [elif list_co ; then list_co ;]... [else list_co ;] fi case nom in [motif) list_co ;;]... esac select nom [in mots] ; do list_co ; done for nom [in mots] ; do list_co ; done for ((expr1;expr2;expr3)) ; do list_co ; done while list_co ; do list_co ; done until list_co ; do list_co ; done
Le code de terminaison $? d'une commande composée est celui de la dernière
commande simple exécutée.
Une commande composée peut être redirigée ; la rediction s'applique alors à tous les éléments qui la composent.
Exemples :
{ echo "Répertoire courant :" ; pwd ; echo "date :" ; date ;} >| trace.txt
while read ligne ; do echo "J'ai lu la ligne : $ligne" ; done < trace.txt
À noter : la définition d'une fonction étant
nom_fonction() commande_composée [redirection]...
elle autorise les déclarations suivantes :
toto() { echo "Bonjour" ;} >> trace.txt # avec redirection
titi() (cd .. ; pwd) # sans {}
10.2. Redirections et descripteurs
On a vu la notion de descripteur de fichier (fd) :
- 0 : entrée standard
- 1 : sortie standard
- 2 : sortie d'erreurs
On peut utiliser d'autres fd (<= 9, voir plus loin).
$ { echo "foo" >&3 ; echo "bar" ;} 3>| trace.txt | tr a-z A-Z
BAR
$ cat trace.txt
foo
- Explications :
3>| trace.txtécrase le fichiertrace.txtet l'associe aufd3
>&3ou1>&3redirige la sortie standard dans lefd3
ceci permet au premier echo de "contourner" le tube.
Application : lecture en parallèle de fichiers
$ while read ligne1 <&3 && read ligne2 <&4
do
echo "Lu '$ligne1' et '$ligne2'"
done 3< fichier1 4< fichier2
- Explications :
3< fichier1ouvre lefichier1en lecture et l'associe aufd3
read ligne1 <&3ou encore0<&3: redirige l'entrée standard sur lefd3; doncreadva lire une ligne defichier1.- Le
while read && reads'arrête dès que la fin d'un des deux fichiers est atteinte.
On peut rediriger le shell de façon permanente avec la commande exec :
$ exec 4>| spam.txt
$ echo "eggs" >&4
$ cat spam.txt
eggs
$ exec 4>&- # ferme le fichier, supprime le fd
$ echo "bacon" >&4
bash: 4: Mauvais descripteur de fichier
On peut demander à bash d'attribuer automatiquement un fd
(qui sera alors >= 10) ; notez la syntaxe {nom} à gauche et $nom à droite :
$ exec {myfd}>| gloubi.txt # crée le fd myfd et lui associe le fichier
$ echo $myfd
10
$ ls -l /proc/$$/fd # Affiche la liste des fd du shell
total 0
lrwx------ 1 ... 0 -> /dev/pts/37
lrwx------ 1 ... 1 -> /dev/pts/37
lrwx------ 1 ... 2 -> /dev/pts/37
l-wx------ 1 ... 10 -> /tmp/gloubi.txt # ouvert en écriture
lrwx------ 1 ... 255 -> /dev/pts/37 # contrôle du tty par bash
$ echo "boulga" >&$myfd # écrit dans le fd myfd
$ cat gloubi.txt
boulga
$ exec {myfd}>&- # ferme le fichier, supprime le fd
$ echo "Casimir" >&$myfd
bash: $myfd: Mauvais descripteur de fichier
10.3. Diverses choses utiles
10.3.1. Le PID $$
Le PID du shell principal est obtenu avec $$ (alors que $BASHPID est le PID du
shell courant, qui peut être un fils, et changer à chaque appel).
Usage fréquent : pour créer des fichiers temporaires dans un script :
tmp1="temp-$$.txt"
commande >| "$tmp1"
rm -f "$tmp1"
Chaque fois qu'on lance le script, il a son propre $$.
10.3.2. Substitution $'\sequence'
Est substitué par le caractère décrit par \sequence :
$'\n'retour chariot$'\t'tabulation$'\xyz'caractère dont le code octal estxyz
par exemple$'\41'⟶!,$'\61'⟶1,$'\101'⟶A
$'...' doit être situé en dehors des "" pour expanser.
10.3.3. Hasard avec $RANDOM
À chaque substitution, produit un nombre au hasard entre 0 et 32767 (c'est un peu short !).
Utile par exemple pour générer des chaînes aléatoires :
generer_chaine() # tableau_caractères longueur
{
local -n tabcar=$1 # par référence
local longueur=$2
local tablen=${#tabcar[*]} i res=
for ((i=0; i<longueur; i++)); do
res+=${tabcar[RANDOM % tablen]} # mode arithmétique dans les []
done
echo "$res"
}
$ char64=({a..z} {A..Z} {0..9} - +)
$ generer_chaine char64 40
PWJUMUrnAS-KoE9WciP9HHg+A9daAx1+aScOMMOR
$ generer_chaine char64 60
npjA+Gt+BF7aNKcZVVswRggKKEmoIV7RUyjuLF5Skwejure5obwS5IHH2JPl
10.3.4. Commande printf
Permet d'afficher avec les mêmes arguments que printf en C :
$ printf "%05d\n" 12 # affichage décimal précédé de zéros
00012
$ printf "%x\n" 63 # en base 16
3f
$ printf "%o\n" 63 # en octal
77
Attention à la locale pour les réels :
$ printf "%.2f\n" "3.14159"
bash: printf: 3.14159: nombre non valable
0,00
$ printf "%.2f\n" "3,14159" # en français il faut une ","
3,14
$ LANG=C printf "%.2f\n" "3.14159" # locale "C" = en anglais : "."
3.14
10.3.5. Flags true et false
Comme true et false sont des commandes, on peut écrire :
flag1=true
flag2=false
if $flag1 ; then ... ; fi # ça fera : if true ; then ..
while ! $flag2 ; do ... ; done
C'est plus court que d'écrire
if test "$flag1" = true ; then ... ; fi
while ! test "$flag2" = "true" ; do ... ; done
10.3.6. Commande trap
La commande trap permet de capter un signal Unix :
trap CMD liste_signaux
Chaque fois qu'un signal de la liste est reçu, la commande CMD est exécutée.
Exemple :
$ trap 'echo "signal reçu"' TERM QUIT
$ kill -QUIT $$
signal reçu
Ceci permet par exemple de rétablir un fichier modifié par un script s'il est interrompu.
Cas particuliers :
trap CMD 0 # la commande CMD est exécutée à la sortie du shell ;
# très utile pour supprimer les fichiers temporaires
trap CMD DEBUG # commande CMD executée après chaque commande simple
Exemple :
$ trap 'echo "[$BASHPID] $BASH_COMMAND"' DEBUG
$ echo salut
[15666] echo salut
salut
Valeurs spéciales pour CMD :
""supprime letrap-rétablit le comportement par défaut
10.4. Quelques pièges à éviter
a) Le $ en trop :
$var1=truc
Cela affecte la variable dont le nom est dans var1, mais pas var1 :
$ var1=toto
$ $var1=truc
$ echo "$var1"
toto
$ echo "$toto"
truc
De plus, si var1 n'existe pas, cela produit une erreur :
$ unset var1
$ $var1=truc
=truc : commande introuvable
b) Le = non accolé :
var1 = truc
Pour bash ce n'est pas une affectation, mais une commande :
$ var1 = truc
var1 : commande introuvable
c) Oubli de la substitution de commandes $() :
var1=fonction $*
Ceci affecte var1 comme variable d'environnement puis exécute $1 comme une
commande. Très dangereux !
$ set echo je t\'ai eu
$ hello(){ echo "bonjour" ;}
$ foo=hello $*
je t'ai eu
d) echo avec des !!
bash réalise l'expansion des ! lorsqu'il est suivi d'un symbole, avec
l'historique des commandes (!! ⟶ dernière commande, etc).
$ echo "coucou !"
coucou !
$ echo "bye bye !!"
echo "bye bye echo "coucou !""
bye bye echo coucou !
Pour afficher !! il faut l'isoler avec des '' :
$ echo "bye bye "'!!'
bye bye !!
e) Indirections multiples en mode arithmétique
$ a=b ; b=c ; c=d ; d=42
$ echo $((a))
42
En mode arithmétique, lorsqu'une variable contient une chaîne, bash fait une indirection, et continue jusqu'à ce qu'il trouve une valeur entière.
Dans le cas où la variable n'est pas définie, bash l'expanse en 0 :
$ unset bidon
$ echo $((bidon))
0
f) Oubli de protection contre la découpe avec des ""
Les arguments et variables contiennent souvent des chemins, dans lesquels il
peut y avoir des blancs. Il est donc crucial de les protéger systématiquement
contre la découpe avec des "".
g) Boucler sur ls
Lorsqu'on veut parcourir les fichiers d'un répertoire dir, on est parfois
tenté de boucler sur ls :
for f in $(ls "$dir") ; do # MAUVAIS
echo "$f"
done
C'est une mauvaise pratique car ls ne préserve pas les noms qui
contiennent des blancs.
La bonne approche est de boucler sur un motif, en plaçant les wildcards
en dehors des "" pour qu'ils soient expansés :
for f in "$dir"/* ; do
echo "$f"
done