Aide-mémoire Bash
J'ai beau utiliser le shell Bash tous les jours depuis des années, j'en découvre toujours, régulièrement.
-
Répéter le dernier paramètre tapé sur la ligne de commande (avec le mode par défaut, le mode Emacs) : Alt .
L'exemple hyper classiquemkdir foobar
Entrée suivi decd Alt .
va donnercd foobar
-
Pour éviter de faire
find ….-exec …
ou autre, il est possible de faireshopt -s globstar
puisfor fichier in **/*.xml; do
oufor fichier in truc/**; do
par exemple -
Lister les fichiers qui contiennent des chiffres entre foo et bar et uniquement des chiffres (une "wildcard" avec seulement des chiffres) :
ls foo+([0-9])bar
Il faut que l'option « extended globbing » soit active donc il faut d'abord faireshopt -s extglob
-
Pour savoir si des fichiers existent avec une wildcard, on ne peut pas faire
if [ -e foo* ]; then
mais on peut faire
if ls foo* 2>&1 >/dev/null; then
ouif compgen -f foo 2>&1 >/dev/null; then
(pour les autres utilisations, voirman bash
ouhelp compgen
parce que c'est une commande builtin de Bash) -
Effacer les fichiers qui commencent par bidule mais ne finissent pas par -small.jpg :
rm bidule!(*-small.jpg)
(ils peuvent avoir quelque chose entre bidule et -small.jpg, par exemple bidule_123456-small.jpg)
(même remarque pour l'option extglob) -
Passer une wildcard en argument d'un script bash : utiliser
$@
Par exemple, si j'appellemon_script.sh foo* bar*
dans mon_script.sh, j'ai$@
qui contientfoo123 foo456 bar1 bar2 bar3
-
"Matcher" avec une regex :
[[ $mavar =~ truc([0-9][0-9])bidule([0-9][0-9])machin ]] && echo coucou ${BASH_REMATCH[1]} et ${BASH_REMATCH[2]}
Exemple minimaliste. Bien sûr, il faut prendre des précautions et mettre des guillemets partout. -
Dans un script, afficher les commandes avant qu'elles s'exécutent, pour avoir du debug :
set -x
pour activer
set +x
pour désactiver
(on peut très bien l'activer pour une seule commande ou plusieurs, désactiver, réactiver plus bas) -
Dans un script, pour s'assurer qu'il n'y a pas déjà une instance qui tourne (typiquement, en cron) simplement :
if pidof -o %PPID -xq "$( basename $0 )"; then echo "ça tourne déjà" exit fi
(ce n'est pas strictement lié à Bash mais je range ça là parce que c'est dans Bash que je l'utilise) -
Inverser deux arguments de la ligne de commande :
Alt-t
(c'est Readline en fait, mais je le note ici) -
Déplacer un ou des arguments :
Ctrl-w
pour couper (autant que nécessaire) puisCtrl-y
pour coller
(idem, c'est Readline) -
Modifier la ligne de commande courante dans Vim :
Ctrl-x Ctrl-e
-
Plus généralement, modifier la ligne de commande précédente :
fc
-
Écrire sur la sortie d'erreur (stderr) :
echo truc >&2
(hé oui, même un truc aussi classique, parfois j'oublie) -
Inverser la sortie d'erreur et la sortie standard (stdout) :
commande_bidule 3>&1 1>&2 2>&3
C'est le bon vieux "swapping" (échange) de variable ou descripteur de fichier. C'est utile quand on veut envoyer le résultat d'une commande ou d'un script dans un fichier et afficher la sortie d'erreur dans le terminal pour suivre son fonctionnement par exemple. -
Un raccourci pour
cp /foo/bar/baz /foo/bar/baz.BAK
:cp /foo/bar/baz{,.BAK}
-
Instruction "no op" :
: Coucou ; echo "foobar"
. Utile par exemple dans une ligne de crontab, pour avoir "Coucou" dans le sujet du mail sans avoir à faireecho "foobar" | mail -s "Coucou" untel
. -
Séparer une variable en plusieurs avec l'Instruction "here string" :
read chose truc bidule <<< "ceci est le contenu de ma variable"
$chose va contenir "ceci", $truc va contenir "est" et $bidule va contenir le reste -
Lire une liste contenue dans un fichier, par exemple une liste à deux colonnes "date nom" :
while read date nom ; do echo "$nom --> $date" ; done < fichier
-
Idem pour une liste à deux colonnes séparées par point-virgule "date;nom" :
IFS=";" ; while read date nom ; do echo "$nom --> $date" ; done < fichier
(ne pas oublier de remettre IFS à sa valeur initiale parunset IFS
ensuite sinon risque de surprises) -
Une boucle for sur la sortie d'un ls trié par date, avec des fichiers dont les noms comportent des espaces :
IFS=$'\n' ; for f in $( ls -tr ) ; do echo [ $f ]; done
(ici ls trié par date de fichier, et […] pour visualiser)
(même remarque concernant IFS après)
On peut aussi remplacer la boucle for par un "find printf sort while read" comme ceci :
find . -printf "%TF_%TT\t%P\n" | sort | while read dateF nomF ; do echo "Fichier $nomF daté du $dateF" ; done
⚠️ Il ne faut pas faire ça avec n'importe quels fichiers car les noms de fichier peuvent aussi, dans l'absolu, comporter des retours chariot ! Exemple pour créer un fichier avec un nom qui comporte un retour chariot :echo "exemple en Bash" > nom_de_fichier_avec$'\n'retour_chariot
-
Ne pas confondre
getopts
, commande interne de Bash (cf.help getopts
) avecgetopt
, commande du paquet util-linux (cf.man getopt
) -
Un piège : à la fin de
bidule | while read line ; do foo=$line ; done
la variable $foo est vide ! Pourquoi ? Parce que le "tube" (pipe) provoque l'exécution d'un sous-shell. La variable n'existe que dans ce sous-shell, elle ne remonte pas dans le shell parent. Pour éviter ça, il faut utiliser la "substitution de processus" avec la syntaxewhile read line ; do foo=$line ; done < <(bidule)
-
Comparer des chiffres décimaux :
if [ 12.3 -lt 4.56 ]; then
affiche l'erreur "nombre entier attendu"
Il faut utiliser une évaluation arithmétique du résultat de l'opération avec bc :if (( $( bc <<< "12.3 > 4.56" ) )); then …
La "here-string" <<< permet d'alléger l'écriture en évitant un echo … | bc
Les variables de type tableau (array) dans Bash
C'est la syntaxe la plus horrible que je connaisse mais il faut faire avec… Voici l'essentiel :
Tableau simple
- Déclarer :
truc=( bidule1 bidule2 bidule3 )
- Ajouter un élément à ce tableau :
truc+=( "bidule4 avec des espaces" )
- Nombre d'éléments :
echo ${#truc[@]}
- Afficher les valeurs :
for bidule in "${truc[@]}" ; do echo $bidule ; done
(noter les guillemets) - Afficher les indices et accès par index :
for i in ${!truc[@]}; do echo "$i => ${truc[i]}" ; done
- Effacer un élément :
unset truc[1]
- Transformer une liste contenue dans une chaîne de caractères en tableau :
readarray -t truc <<< "$foobar"
oureadarray -t truc < <( ls )
mais pasls | readarray -t truc
qui lance un sous-shell à la fin duquel la variable truc n'existe plus
Tableau associatif
- Déclarer :
declare -A machin=( [foo]=123 [bar]=456 [bidule]=truc )
- Ajouter un élément à ce tableau :
machin[chose]="coucou avec des espaces"
- Nombre d'éléments idem :
echo ${#machin[@]}
- Afficher les valeurs idem :
for bidule in "${machin[@]}" ; do echo $bidule ; done
(noter les guillemets) - Afficher les clés et accès par clé :
for k in ${!machin[@]}; do echo "$k => ${machin[$k]}" ; done
- Effacer un élément :
unset machin[chose]
- Tester si un élément existe :
if [ ${machin[chose]+existe} ]; then echo "oui, il existe" ; fi
Ce "existe" après le caractère + c'est juste une chaine de caractère, on peut mettre n'importe quoi d'autre