Administration Unix - CM séance 07
8. Les expansions du shell bash
Pour interpréter une ligne de commande, bash la découpe en mots selon les
blancs (espace, tabulation) et les paires '' "" `` () {} []
, puis il réalise une
expansion pour chaque mot sur des caractères spéciaux.
Par exemple :
$ wc -l plan.txt tp*.txt cm??.txt
est découpé en 5 mots : wc
, -l
, plan.txt
, tp*.txt
, cm??.txt
puis les mots comportant des *?
(qui sont des motifs de fichiers) sont remplacés
par la liste des fichiers qui correspondent.
Lorsque des "" () {} []
sont imbriqués, les expansions sont faites du niveau le
plus interne vers le plus externe.
Les opérations ont lieu dans cet ordre, pour un même niveau :
- Expansion des accolades
{ga,bu}
- Développement du tilde
~ ~thiel
- Substitution des arguments
$1 $# $* "$@"
- Substitution des variables
$foo ${foo} ${t[]}
- Substitution des commandes
$()
- Évaluation arithmétique
$(())
- Découpage des mots sur
$IFS
- Développement des noms de fichiers
* ? []
8.1. Expansion des accolades
L'expansion des accolades permet de générer des chaînes de caractères.
Syntaxe :
préfixe{mot1,mot2,...,motn}suffixe
ceci est expansé en :
préfixemot1suffixe préfixemot2suffixe ... préfixemotnsuffixe
Précisions :
- le préfixe et le suffixe sont optionnels ;
- dans les
{}
, les mots sont séparés par des,
sans blancs ; - il faut un moins une virgule dans
{}
, donc au moins 2 mots.
Exemples :
$ echo {ga,bu,zo}
ga bu zo
$ echo A{ga,bu,zo}B
AgaB AbuB AzoB
Lorsqu'il y a plusieurs accolades, bash fait le produit cartésien des listes, c'est-à-dire toutes les combinaisons possibles :
$ echo {ga,bu}{zo,meu}
gazo gameu buzo bumeu
$ echo A{ga,bu}B{zo,meu}C
AgaBzoC AgaBmeuC AbuBzoC AbuBmeuC
On peut les imbriquer :
$ echo A{ga,B{bu,zo}C}D
AgaD ABbuCD ABzoCD
On peut protéger les caractères accolades, espace, virgule avec \
ou ''
ou ""
:
$ echo {\{bim\},bam,b\,o\ um}
{bim} bam b,o um
$ echo {"{bim}",bam,'b,o um'}
{bim} bam b,o um
L'expansion des {}
peut aussi générer des séquences :
$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
$ echo {1..10..3} # par pas de 3
1 4 7 10
$ echo {01..10} # avec des 0 à gauche
01 02 03 04 05 06 07 08 09 10
$ echo {e..k}
e f g h i j k
Exemple concret :
$ wc -l au{01..03}-{cm,tp}.md
sera expansé en
wc -l au01-cm.md au01-tp.md au02-cm.md au02-tp.md au03-cm.md au03-tp.md
C'est également utile pour créer des répertoires ou des fichiers :
$ mkdir -p /tmp/essai/{foo,bar}/tp{01..03}
$ tree /tmp/essai
/tmp/essai
├── bar
│ ├── tp01
│ ├── tp02
│ └── tp03
└── foo
├── tp01
├── tp02
└── tp03
$ touch /tmp/essai/{foo,bar}/tp{01..03}/{trace,erreurs}-{a..c}{1..3}.txt
Comme l'expansion des {}
a lieu en premier, elle va recopier les motifs éventuels :
$ du -sk /usr/share/icons/{ubuntu*,??color}/{??x??,256*}/apps
sera expansé en
du -sk /usr/share/icons/ubuntu*/??x??/apps /usr/share/icons/ubuntu*/256*/apps
/usr/share/icons/??color/??x??/apps /usr/share/icons/??color/256*/apps
dont les motifs seront expansés à leur tour.
8.2. Développement du tilde
Sur les systèmes Unix, le caractère ~
(tilde) désigne le répertoire $HOME
c'est-à-dire le répertoire principal de l'utilisateur.
Origine : les symbole ~
et home étaient sur la même touche sur des terminaux
très populaires au milieu des années 1970 (modèle ADM-3A de Lear-Siegler).
Ce terminal a aussi influencé certaines commandes de vi
, par ex. hjkl
.
Syntaxe :
~
répertoire principal$HOME
~user
répertoire principal deuser
; par exemple :~dupond
Plus rare :
~+
répertoire courant$PWD
~-
répertoire précédent$OLDPWD
Délicat à manipuler :
- ne doit pas être protégé par
""
ou''
- hors d'une affectation, doit figurer au début du mot
- dans une affectation, doit figurer après
=
ou:
$ PATH=.:~/bin:/bin:/usr/bin
$ echo $PATH
.:/home/thiel/bin:/bin:/usr/bin # ~ bien expansé
$ PATH=".:~/bin:/bin:/usr/bin"
$ echo $PATH
.:~/bin:/bin:/usr/bin # ~ non expansé à cause des ""
$ echo .:~/bin:/bin:/usr/bin
.:~/bin:/bin:/usr/bin # ~ non expansé, cas n°2
8.3. Substitution des arguments
La substitution des arguments a ensuite lieu :
$0
le script avec son chemin$1
l'argument 1${10}
l'argument 10$#
le nombre d'arguments$*
la liste des arguments"$@"
la liste des arguments, protégés par des""
Voir cours n°5 section 6.4.
Il est également possible de leur ajouter des opérateurs sur les variables, voir section suivante.
8.4. Substitution des variables
Les variables sous la forme $nomvar
ou ${nomvar}
sont substituées par leur valeur.
Une variable inexistante est substituée par la chaîne vide.
Dans une chaîne encadrée par des ""
ou des ''
:
- entre
""
, les$
sont expansés (partial or weak quoting) ; - entre
''
elles ne le sont pas (full or strong quoting).
$ boisson="café"
$ echo "Je bois du $boisson"
Je bois du café
$ echo 'Je bois du $boisson'
Je bois du $boisson
Remarque : une autre façon de ne pas expanser la variable est de la protéger
avec un \
:
$ echo "Je bois du \$boisson"
Je bois du $boisson
bash permet de faire certaines opérations lors de la substitution.
La syntaxe générale est : ${nomvarOPERATEUR}
Il existe de nombreux opérateurs , qui fonctionnent aussi sur les arguments ;
ils permettent d'éviter l'emploi de commandes externes (sed
, awk
, perl
,...).
On peut citer les opérateurs suivants :
Opérateurs d'existence
${nomvar-valeur}
$nomvar
si définie, sinonvaleur
${nomvar=valeur}
$nomvar
si définie, sinonvaleur
et affectation${nomvar+valeur}
valeur
sinomvar
définie, sinon vide
Exemples :
$ echo $a # (affiche : ligne vide)
$ echo ${a-pas glop} # pas glop
$ echo ${a+glop glop} # (ligne vide)
$ echo ${a=houba houba} # houba houba
$ echo $a # houba houba
$ echo ${a=houpa hop} # houba houba
$ echo ${a+glop glop} # glop glop
Opérateurs de sous-chaînes
${nomvar:i}
sous-chaîne depuis l'indicei
>= 0${nomvar:i:k}
sous-chaîne depuis l'indicei
>= 0 de longueurk
${nomvar:i:1}
caractère d'indicei
>= 0${#nomvar}
longueur courante de$nomvar
Exemples :
$ episode="la menace fantôme"
$ echo ${episode:3}
menace fantôme
$ echo ${episode:3:6}
menace
$ echo ${#episode}
17
Opérateurs de casse
${nomvar^^}
$nomvar
en majuscules${nomvar,,}
$nomvar
en minuscules
Exemple :
$ b="Le langage TeX"
$ echo ${b^^}
LE LANGAGE TEX
$ echo ${b,,}
le langage tex
Opérateurs début et fin
${nomvar#motif}
supprime le plus court début correspondant àmotif
${nomvar##motif}
idem pour le plus long${nomvar%motif}
supprime la plus courte fin correspondant àmotif
${nomvar%%motif}
idem pour la plus longue
Exemples :
$ c="/usr/local/share/icons.old.tar.gz"
$ echo ${c#/usr/local}
/share/icons.old.tar.gz
$ echo ${c#/usr/bin}
/usr/local/share/icons.old.tar.gz # motif non trouvé ⟶ $c
$ echo ${c#/*/}
local/share/icons.old.tar.gz
$ echo ${c##/*/}
icons.old.tar.gz
$ echo ${c%.*}
/usr/local/share/icons.old.tar
$ echo ${c%%.*}
/usr/local/share/icons
Application : tester si un nom de fichier $file
possède l'extension $ext
test "$file" = "${file%.$ext}.$ext"
Rechercher-remplacer
${nomvar/motif/txt}
remplace la plus longue correspondance demotif
partxt
${nomvar//motif/txt}
remplace toutes les correspondances demotif
partxt
Exemple :
$ d="le café décaféiné avec le sucre"
$ echo "${d/le/du}"
du café décaféiné avec le sucre
$ echo "${d//le/du}"
du café décaféiné avec du sucre
$ echo "${d/c*é/thé}"
le thé avec le sucre
Remarques :
- ces substitutions ne modifient pas la variable, sauf l'opérateur
=
-
elles ne peuvent pas être cumulées ; par exemple on ne peut pas écrire
${nomvar#debut%fin} # ERRONÉ
il faudra le faire en 2 étapes :
tmp=${nomvar#debut} ${tmp%fin}
Sur les arguments
-
comme déjà dit, tout ces opérateurs s'appliquent également aux arguments :
$ set abra ca dabra $ echo $3 dabra $ echo ${3%bra} da
-
avec
$*
et$@
, l'opérateur est appliqué à chaque argument :$ echo ${*%bra} a ca da $ echo "${@^^}" ABRA CA DABRA
-
ou appliqué à la liste pour l'opérateur
:
:$ echo ${*:2} ca dabra $ echo ${*:1:2} abra ca
8.5. Substitution des commandes
Il s'agit d'un mécanisme très puissant : une commande est substituée par ce qu'elle a affiché sur sa sortie standard.
Syntaxe :
`commande arguments` # ancienne syntaxe sh, avec des "back-quotes" --> À ÉVITER
$(commande arguments) # syntaxe bash moderne
Exemples :
echo "Fichier créé le : $(date)"
echo "ma machine s'appelle $(uname -n)"
nb_fichiers=$(ls *.txt | wc -l)
x=5; x=$(expr $x + 1) # une façon d'incrémenter x
La commande est exécutée dans un sous-shell :
$ echo $BASHPID
4506
$ echo $(echo $BASHPID)
5604
Les dernières lignes vides sont coupées :
$ echo "'$(echo ; echo " bonjour " ; echo ; echo)'"
'
bonjour '
Un usage courant est de mémoriser tout le contenu d'un fichier dans une variable (sauf les dernières lignes vides !) :
$ echo -e "bonjour\nles amis\n\n" >| essai.txt
$ texte=$(cat essai.txt)
$ echo "$texte"
bonjour
les amis
Il existe un raccourci : texte=$(< essai.txt)
Il est possible d'imbriquer les parenthèses :
msg="La taille est $(wc -c < $(which test)) octets"
La substitution de commande est particulièrement utile pour récupérer le résultat affiché par une fonction :
calculer_taille_fichier() # fichier
{
wc -c < "$1"
}
taille=$(calculer_taille_fichier ~/.bashrc)
echo "$taille"
recuperer_homedir() # user
{
grep "$1" /etc/passwd | cut -d: -f 6
}
echo "Le home de $USER est $(recuperer_homedir "$USER")"
Important :
Dans une fonction, return
fait sortir immédiatement de la fonction :
return
avec le code de terminaison de la dernière commande exécutée ;return n
avec le code de terminaisonn
(0 succès, 1..255 échec)
Pour renvoyer un résultat, n'utilisez pas return
, mais affichage et $()
;
on en reparlera au cours suivant.
8.6. Évaluation arithmétique
bash est capable d'effectuer des calculs, avec des entiers UNIQUEMENT.
La syntaxe est
((expression))
évalue l'expression ; réussit si elle est vraie$((expression))
évalue l'expression puis substitue par sa valeur
Dans expression :
- opérateurs et syntaxe du C
- pas de découpage sur les blancs
- on peut omettre les
$
si non-ambigu.
Exemples :
$ echo $((20+30))
50
$ x=10 ; y=20 ; echo $((x+y)) # ou echo $(($x+$y))
30
$ x=5 ; y=7 ; ((z=x+y)) ; echo $z # ou z=$((x+y))
12
$ ((x=3, y=4, z=x+y)) ; echo $z
7
Calculs :
-
les priorités sont respectées :
$ echo $((10-3*(4+5))) 17
-
la division est entière :
$ echo $((17/7)) 2 $ echo $((17.0/7)) bash: 17.0/7 : erreur de syntaxe ...
-
pour les nombres réels, on utilise la commande
bc
:$ echo "scale=8; 17/7" | bc 2.42857142 $ echo "la racine carrée de 2 est $(echo "scale=4; sqrt(2)" | bc)" la racine carrée de 2 est 1.4142
Expressions du C :
-
opérateurs logiques ⟶ 0 (faux) ou 1 (vrai)
$ echo $((10==10)) 1
-
opérateur ternaire :
$ x=3 ; y=5 $ echo $((x >= y ? x : y)) # si x >= y alors x sinon y 5
-
expression avec virgules : évaluée de gauche à droite, valeur à droite
$ echo $((x=y=4, y++, x+y)) 9
Ajouts par rapport au C :
-
calcul de puissances :
$ echo $((2**5)) 32
-
bases : 2 à 64
$ echo $((0100)) # base 8, idem en C 64 $ echo $((0x100)) # base 16, idem en C 256 $ echo $((2#100)) # base 2, en C GNU : 0b100 4 $ k=5 ; echo $(($k#100)) # $ obligatoire pour développer $k avant 25
- Réussite d'une évaluation :
-
((expression))
réussit si expression est vraie (c'est-à-dire non nulle).
Par exemple, ((4 == 5 || 3 <= 7))
réussit ($? est 0
).
On peut donc écrire :
((expression)) && commande
if ((expression)); then .. ; fi
while ((expression)); do .. ; done
Le shell bash autorise enfin l'emploi de la boucle for
du C :
for ((expression1; expression2; expression3)); do .. ; done
Par exemple :
for ((i=0; i<10; i++)); do echo "$i" ; done
8.7. Découpage des mots sur IFS
La variable spéciale IFS
(pour Internal Field Separator) contient la liste
des caractères de séparation pour découper les chaînes en mots.
Elle vaut par défaut ␣\t\n
(espace tabulation retour chariot).
Elle est utilisée par
- la commande
read
(vu plus tard) - et lors de l'expansion pour découper en mots
mais pas lors du premier découpage de la commande, toujours effectué sur les blancs.
On peut le voir par exemple avec for
, qui expanse puis découpe selon IFS
:
$ liste="ga:bu zo:meu"
$ for mot in $liste ; do echo "$mot" ; done
ga:bu # ça a découpé sur " "
zo:meu
$ OLDIFS=$IFS ; IFS=":" # on sauve IFS avant
$ for mot in $liste ; do echo "$mot" ; done
ga
bu zo # ça a découpé sur :
meu
$ IFS=$OLDIFS # rétablit IFS
8.8. Développement des noms de fichiers
Les motifs ont été abordés au cours n°6, section 7.4.2.
Les fichiers existants, correspondant au motifs, sont substitués au motif, séparés par un blanc, dans l'ordre lexicographique.
Exception : le motif *
ou ?
n'est pas substitué pour les fichiers cachés :
$ touch {ga,bu,.zo}.{txt,sh}
$ ls -a
./ ../ bu.sh bu.txt ga.sh ga.txt .zo.sh .zo.txt
$ ls *.txt
bu.txt ga.txt
$ ls ?*.txt
bu.txt ga.txt
$ ls *.txt .*.txt
bu.txt ga.txt .zo.txt
sauf si on active l'option dotglob
:
$ shopt -s dotglob
$ ls *.txt
bu.txt ga.txt .zo.txt
Si aucun fichier ne correspond, le motif est laissé inchangé, sauf si l'option
nullglob
est activée (cf TP n°3, exercice 2.a)).
Lorsqu'un motif est appliqué pour un chemin, les /
doivent être donnés
explicitement (c'est-à-dire qu'il n'y aura pas de correspondance avec * ? []
) :
$ touch foo.bar foolbaz ; mkdir -p foo/bam
$ ls foo*ba?
foo.bar foolbaz # foo/bam non trouvé
$ ls foo*ba? foo/ba?
foo.bar foolbaz
foo/bam: # foo/bam trouvé
$ ls foo[.l]ba?
foo.bar foolbaz # foo/bam non trouvé
$ ls foo[.l/]ba?
ls: impossible d'accéder à 'foo[.l/]ba?': Aucun fichier ou dossier de ce type
$ ls foo{*,/}ba? # expansé en : ls foo*ba? foo/ba?
foo.bar foolbaz
foo/bam: # foo/bam trouvé
Remarque finale sur l'ordre des expansions : {}
puis $
puis *?
$ a={.bar,lbaz} ; echo foo$a
foo{.bar,lbaz} # expansion de $a, mais pas {}
$ a="*ba?" ; echo foo$a
foo.bar foolbaz # expansion de $a puis *?