Bash

Raccourcis clavier

ctrl-a           aller au début de la ligne
ctrl-e           aller à la fin de la ligne
ctrl-d           quitter la session (équivaut à "exit" ou "logout")
ctrl-l           effacer tout affichage sur le terminal
ctrl-r<cmd>      les occurrences de <cmd> dans l'historique
ctrl-s           masquer la saisie (ctrl-q pour rétablir)
ctrl-t           intervertir deux caractères
ctrl-u           coupe tout ce qui précède le curseur
ctrl-v + <tab>   insère une "vraie" tabulation
ctrl-w           couper le mot précédent le curseur
ctrl-y           coller ce qui a été coupé
alt-c            effacer le mot précédent
alt-f            aller au mot suivant
alt-b            aller au mot précédent
alt-t            intervertir deux mots (devant et derrière le curseur) 
esc-.            insère le dernier argument de la commande précédente (fonctionne également avec alt-.)
~~               ré-exécute la dernière commande
!XX              ré-exécute la dernière commande commençant par XX
!XX:p            affiche la dernière commande commençant par XX mais ne l'exécute pas
<cmd> !*         exécute la commande <cmd> avec tous les paramètres de la dernière commande
<cmd> !$         exécute la commande <cmd> avec le dernier paramètre de la dernière commande

Astuces

Changer la forme et la couleur du prompt (invite de commande)

La forme du prompt shell est stockée dans une variable d’environnement : $PS1.

bash-3.2$ export PS1="\u@\h:\w% "
marc@magma:~%

Les différents switch sont énumérés dans le manuel de Bash (section PROMPTING) ; ici le prompt affiche l’utilisateur, le nom de la machine ainsi que le répertoire courant. Il est possible de coloriser le prompt avec la syntaxe suivante :

export PS1="\033[01;37m$PS1\033[00;00m "

…ce qui donnera ici un prompt blanc gras. Voici les différentes couleurs disponibles :

COULEUR   DÉFAUT   GRAS           
noir      0;30     1;30
rouge     0;31     1;31
vert      0;32     1;32
jaune     0;33     1;33
bleu      0;34     1;34
magenta   0;35     1;35
cyan      0;36     1;36
blanc     0;37     1;37

Augmenter la taille de l’historique du shell

La capacité de l’historique de Bash est définie par deux variables d’environnement : $HISTSIZE et $HISTFILESIZE ; la première indique combien de commandes sont mises en mémoire à l’ouverture de la session ainsi que combien seront enregistrées dans l’historique à sa fermeture, et la deuxième combien d’entrées l’historique peut contenir au maximum (par défaut : 500).

HISTSIZE=1000
HISTFILESIZE=50000

Horodater l’historique des commandes

La commande history vous permet d’afficher l’historique des commandes exécutées dans le shell, mais par défaut elle n’affiche pas la date et encore moins l’heure à laquelle ces commandes ont été exécutées. Pour horodater cet historique, utilisez la variable d’environnement $HISTTIMEFORMAT :

export HISTTIMEFORMAT="%d/%m/%Y %H:%M:%S "

À partir de maintenant, votre historique sera horodaté (les entrées dans le fichier .bash_history sont préfixées d’un “timestamp” UNIX), et l’output de la commande history ressemblera à ça :

$ history | tail -n 5
   27  08/05/2009 12:29:15 uname -a 
   28  09/05/2009 12:29:45 cd /tmp
   29  09/05/2009 12:29:46 ls 
   30  09/05/2009 12:30:05 cd ..
   31  09/05/2009 12:30:20 history | tail -n 5

Consultez la page de manuel strftime(3) pour toute référence aux options de formatage disponibles.

Réutilisation de commandes avec substitution

Pour ré-exécuter la commande précédente en changeant quelques paramètres, utilisez l'history expansion de Bash :

!!:gs/ancienparamètre/nouveauparamètre/

L'history expansion de Bash est très puissante, et la section du man qui la documente est à cette image : copieuse. Pour la petite histoire, !! signifie qu’on réfère à la ligne d’historique précédente — c’est à dire, la ligne de commande exécutée juste avant —, :g signifie qu’on applique la substitution à toute la ligne de commande (par défaut, seul la première occurrence est substituée) et enfin le mondialement connu s/foo/bar/ qui demande la substitution de “foo” par “bar”.

Il est également possible de combiner cette fonctionnalité avec celle qui permet de ré-exécuter une commande avec la forme !XXXX est le début de la commande. Par exemple, la commande suivante ré-exécute la dernière occurrence de scp en remplaçant “serveur01” par “serveur42” :

!scp:gs/serveur01/serveur42/

Réutiliser certains paramètres de lignes de commande antérieures

Dans le même registre que l’astuce précédente il est possible de réutiliser uniquement certains paramètres de commandes précédemment exécutées, toujours grâce à la fonctionnalité d'history expansion. “Démonstration attention” :

$ mkdir a b c d

$ rmdir !:2-3   # on réutilise uniquement le 2ème et 3ème paramètre de la commande précédente, soit "b" et "c"
rmdir b c

$ ls
a  d

Là encore, je vous renvoie vers le man de Bash, section “HISTORY EXPANSION” pour une référence exhaustive de la syntaxe.

Basiquement, on déclare un descripteur de fichier supplémentaire qui pointe vers un fichier /tmp/monscript-erreurs.log et on redirige la sortie STDERR vers ce nouveau descripteur.

Utiliser une commande aliasée “nature”

Vous vous êtes mitonné un .bash_aliases aux petits oignons avec tout plein d’alias pour vous faciliter la vie, et c’est très bien. Mais comment faites-vous le jour où vous avez justement besoin d’utiliser une de ces commandes sans les paramètres de l’alias ? Devez-vous supprimer cet alias le temps d’exécuter votre commande et ensuite le recréer ? Noooonnnn, il suffit d’invoquer la commande précédée d’un antislash pour inhiber temporairement l’alias et ainsi appeler la commande “au naturel” :

$ du mon_fichier
6420    mon_fichier

$ alias du='du -h'

$ du mon_fichier 
6.3M    mon_fichier

$ \du mon_fichier 
6420    mon_fichier

Invoquer temporairement un éditeur de texte pour rédiger une ligne de commande longue/complexe

Si vous avez à écrire une ligne de commande particulièrement longue et/ou complexe, sachez qu’il est possible d’invoquer temporairement l’éditeur de texte de votre choix (Emacs, Vi[m], Joe…) pour écrire ladite ligne de commande avec tout le confort que peut vous apporter cet éditeur. Pour ce faire, assurez-vous que la variable d’environnement $EDITOR est bien définie au nom du binaire de votre éditeur favori, et exécutez la combinaison de touches suivante :

<Ctrl-x> <Ctrl-e>

Votre éditeur de texte ouvrira sur un document vierge dans lequel vous pouvez prendre le temps de confectionner votre ligne de commande, cette dernière sera exécutée par le shell après que vous ayez enregistré/quitté l’éditeur.

ℹ️ De la même manière, il est possible d’utiliser la commande fc pour ouvrir un éditeur, mais cette fois ci le contenu du fichier sera la dernière commande exécutée.

Empêcher Bash de logger les commandes exécutées dans l’historique

Dans le cas où vous auriez besoin de tester quelques lignes commandes faisant apparaître des informations sensibles (typiquement, un mot de passe), exécutez au préalable la commande suivante pour rendre Bash amnésique pour la durée de la session :

unset HISTFILE

Toutefois, il est également possible de définir une liste de commande ne devant jamais être prises en compte par l’historique de Bash. Il suffit de définir pour cela la variable d’environnement $HISTIGNORE (chaque entrée devant être séparée par deux points) :

export HISTIGNORE='ls:ls *'

ℹ️ Les caractères faisant office de jokers dans Bash ("*", “?") peuvent êtres utilisés pour définir des règles d’exclusion plus fines.

Faire des maths avec Bash

Il est possible de procéder dans un shell Bash à quelques opérations arithmétiques inline à l’aide de la syntaxe $\![expression arithmétique](/expression arithmétique) :

$ echo $((2 + 2))
4

$ echo $((((2 * 20) + 3 - 1)))
42

⚠️ Ne fonctionne qu’avec des nombres entiers.

ℹ️ Ne pas confondre avec la syntaxe $(commande) qui a la même fonction que `commande`.

Lire un fichier ligne par ligne

Bash propose une syntaxe pratique pour lire le contenu d’un fichier ligne par ligne :

while read ligne
do
  echo $ligne
done <fichier.txt

Exemple qui préfixe toutes les lignes d’un fichier avec la chaîne “PREFIX” :

$ ls -l / > /tmp/fichier.txt

$ while read line; do echo "PREFIX $line"; done < /tmp/fichier.txt
PREFIX total 3906357
PREFIX drwxr-xr-x   2 root root       4096  9 août  17:59 bin
PREFIX drwxr-xr-x   4 root root       1024  5 août  08:53 boot
PREFIX drwxr-xr-x  16 root root       5500  5 août  09:20 dev
PREFIX drwxr-xr-x  60 root root       4096  9 août  17:59 etc
PREFIX drwxr-xr-x   3 root root       4096  6 avril 12:34 home
PREFIX drwxr-xr-x   8 root root       4096  9 août  17:59 lib
…
PREFIX drwxrwxrwt  10 root root      28672 10 août  10:04 tmp
PREFIX drwxr-xr-x  10 root root       4096  9 août  17:59 usr
PREFIX drwxr-xr-x  14 root root       4096  9 août  17:59 var

Exclure certains fichiers lors du listage d’un répertoire

Il est parfois nécessaire de pouvoir boucler sur le contenu d’un répertoire en excluant certains fichiers ou répertoires, et pour ce faire Bash propose une alternative pratique au workaround habituel impliquant un filtrage via grep/awk/sed.

Tout d’abord, il faut activer l’option extglob de Bash si ce n’est pas déjà fait :

$ shopt extglob
extglob        	off

$ shopt -s extglob

$ shopt extglob
extglob        	on

Il est désormais possible d’exclure des fichiers ou des répertoires en correspondant à un motif donné. Considérons l’exemple d’un répertoire contenant des images de différents formats, pour lequel on souhaite lister toutes les images n’étant pas au format GIF :

$ ls
a.jpg b.jpg c.jpg d.gif e.gif f.png g.png h.png

$ ls !(*gif)
a.jpg b.jpg c.jpg f.png g.png h.png

Ou encore, ne pas lister les images dont le nom de fichier commence par une lettre comprise entre “a” et “c” :

$ ls !([a-c]*)
d.gif e.gif f.png g.png h.png

Il est également possible de déclarer plusieurs motifs à filtrer, par exemple ici on ne souhaite lister que les fichiers dont le nom ne commence pas par une lettre comprise entre “a” et “c” et dont l’extension n’est pas .png :

$ ls !([a-c]*|*png)
d.gif e.gif

ℹ️ Ce mécanisme est récursif par défaut, c’est-à-dire que Bash listera également le contenu des répertoires contenu dans le répertoire listé, puis des répertoires que ces répertoires pourraient contenir etc. Pour éviter ce comportement, ajoutez -d aux options de votre ls.

Rediriger simplement la sortie d’erreur vers sortie standard dans un pipe

Lors d’un enchaînement de commandes via pipe, il est possible rediriger simplement stderr vers stdout en utilisant |& au lieu de | – il s’agit d’un raccourci pour le plus cabalistique 2>&1. Note : il l’équivalent pour rediriger vers un descripteur de fichiers existe avec >&.

Modifier la casse d’une chaîne de caractères variable

Bash est capable d’effectuer des modifications élémentaires au niveau de la casse de chaînes de caractères, par exemple transformer des majuscules en minuscules et inversement :

$ export VAR=coucou
$ echo ${VAR^}
Coucou
$ echo ${VAR^^}
COUCOU

$ export VAR=COUCOU
$ echo ${VAR,}
cOUCOU
$ echo ${VAR,,}
coucou

Développement

Logger (plus élégamment) les messages d’erreur d’un script Bash

Bien qu’il soit possible de rediriger les messages d’erreur au moment de l’exécution d’un script (du genre script.sh 2>erreurs.log par exemple), il est possible de faire cela plus proprement depuis le script :

#!/bin/bash

exec 3>/tmp/monscript-erreurs.log
exec 2>&3exit 0

À noter que pour effectuer du debugging il existe une variante utilisant la variable BASH_XTRACEFD permettant de rediriger dans un fichier la sortie de l’option -x:

#!/bin/bash

exec 99>/tmp/debug.log
BASH_XTRACEFD=99
set -x

…
exit 0

Utiliser des variables “tableau”

Bash permet de grouper des variables en “tableaux” (arrays) comme dans les langages de programmation évolués, indexés à l’aide d’indices numériques ou par des clés de type alphanumériques chaîne de caractères. Voici quelques exemples d’utilisation :

# Déclaration + assignation d'éléments à un tableau "ARRAY2" (indexés par
# indices numériques) :
declare -a ARRAY1
ARRAY1[0]="toto"
ARRAY1[1]="titi"
ARRAY1[2]="tata"
ARRAY1[3]="tutu"

# Variante :
ARRAY1=(toto titi tata tutu)

# Déclaration + assignation d'éléments à un tableau "ARRAY1" (indexés par clés
# alphanumériques) :
declare -A ARRAY2
ARRAY2["toto"]="TOTO"
ARRAY2["titi"]="TITI"
ARRAY2["tata"]="TATA"
ARRAY2["tutu"]="TUTU"

# Récupérer le nombre d'éléments contenus dans le tableau :
echo ${#ARRAY1[@]} ${#ARRAY2[@]}
4 4

# Récupérer l'intégralité des éléments contenus dans le tableau :
echo ${ARRAY1[@]}
toto titi tata tutu

echo ${ARRAY2[@]}
TUTU TATA TITI TOTO

# Dans le cas des tableaux associatifs, vous pouvez lister les clés
# correspondant aux valeurs comme ceci :
echo ${!ARRAY2[@]}
tutu tata titi toto

Utiliser des valeurs par défaut lors de la déclaration de variables

Lors de l’assignation d’une valeur à une variable, il est possible d’utiliser une valeur par défaut qui sera utilisée si une variable est indéfinie :

# Assigne la valeur 42 à la variable $INDEX si la variable $TOP n'est pas
# définie :
INDEX=${TOP:-42}
echo $INDEX
42

1. Même chose avec la variable $TOP définie :
unset INDEX
TOP=1024
INDEX=${TOP:-42}
echo $INDEX
1024

Modifier une variable à la volée

Si vous avez besoin de modifier temporairement une chaine de caractères contenue dans une variable, Bash propose une syntaxe pratique pour cela :

Exemple :

FICHIER="repertoire/fichier.txt"

# Renommer le fichier "repertoire/fichier.txt" en "repertoire/fichier.html"
mv $FICHIER ${FICHIER%txt}html

# Déplacer le fichier "repertoire/fichier.txt" dans le répertoire "backup"
mv $FICHIER backup${FICHIER#repertoire}  

Découper une chaîne en 2 parties

Petite astuce pour “découper” une chaîne en 2 parties selon un délimiteur connu :

export VAR="hello:world"
export what=${VAR%:*} who=${VAR#*:}
echo "what=$what who=$who"
what=hello who=world

Source : BASH: Split a string without ‘cut’ or ‘awk’

Récupérer l’extension d’un fichier

$ export blah=toto.gz
$ echo ${blah##*.}
gz

Inclure un bloc de texte “tel quel” dans un script

Bash permet d’inclure dans un script un bloc de texte destiné à être affiché “tel quel” :

#!/bin/bash

echo "Voici un bloc Here document :"

cat <<README
==================================================================
Ceci est un bloc de texte simple.

Bash l'affichera tel qu'il est rédigé entre la ligne "cat <<README"
et la dernière ligne ne contenant que le mot suivant le signe "<<"
(ici, "README").

D'ici la fin de bloc de texte (appelé "heredoc", pour "Here
document"), il est possible d'afficher tout ce que l'on veut, de
sauter des lignes et d'indenter son texte librement sans avoir recours
à des caractères de formatage:

    * hop
    * blah

Les "here documents" sont souvent utilisés pour afficher le bloc
d'aide d'un script (la fameuse option "--help"), ou encore une
licence d'utilisation (GPL, BSD…).
==================================================================
README
# fin du bloc heredoc

exit 0

Étant donné que Bash affiche le heredoc tel qu’il est contenu dans le script, si vous déclarez un heredoc en plein milieu de code indenté et respectez le niveau d’indentation courant le texte du heredoc contiendra les tabulations ou espaces préfixant le texte. Pour éviter ceci, placez le signe “-” (moins) juste entre les << et le mot-stop (dans l’exemple ci-dessus, cela donnerait <<-README).

Lister les fonctions définies

Lister les noms des fonctions définies

typeset -F

Lister et afficher le code des fonctions définies

typeset -f

Générer une liste de nombres ou de caractères dynamiquement

Bash sait générer des listes alphabétiques dynamiquement avec la syntaxe {début..fin}, ce qui permet par exemple d’éviter d’avoir recours à la commande seq pour itérer sur des listes numériques basiques. Exemple :

$ echo {1..10}
1 2 3 4 5 6 7 8 9 10

$ echo {1..100..5}
1 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 86 91 96

Des listes alphabétiques sont également possibles :

$ echo {A..Z}
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z

Ressources en ligne