Aller au contenu

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_co dé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 fichier trace.txt et l'associe au fd 3
>&3 ou 1>&3   redirige la sortie standard dans le fd 3

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< fichier1   ouvre le fichier1 en lecture et l'associe au fd 3
read ligne1 <&3   ou encore 0<&3 : redirige l'entrée standard sur le fd 3 ; donc read va lire une ligne de fichier1.
Le while read && read s'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 est xyz
    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 le trap
  • -   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