Bash

Table of contents

  1. Bash
    1. Raccourcis clavier
    2. Astuces
      1. Changer la forme et la couleur du prompt (invite de commande)
      2. Augmenter la taille de l'historique du shell
      3. Horodater l'historique des commandes
      4. Réutilisation de commandes avec substitution
      5. Réutiliser certains paramètres de lignes de commande antérieures
      6. Utiliser une commande aliasée "nature"
      7. Invoquer temporairement un éditeur de texte pour rédiger une ligne de commande longue/complexe
      8. Empêcher Bash de logger les commandes exécutées dans l'historique
      9. Faire des maths avec Bash
      10. Lire un fichier ligne par ligne
      11. Exclure certains fichiers lors du listage d'un répertoire
    3. Développement
      1. Logger (élégamment) les messages d'erreur d'un script Bash
      2. Utiliser des variables "tableau"
    4. Ressources en ligne

Raccourcis clavier

ctrl-a			aller au début de la ligne
ctrl-e			aller à la fin de la ligne
ctrl-d			quitter la session (equivaut à "exit" ou "logout")
ctrl-l			effacer tout affichage sur le terminal
ctrl-r<cmd>		recherche les occurrences de <cmd> dans l'historique
ctrl-s			masquer la saisie (ctrl-q pour rétablir)
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 :
noir		0;30	gris foncé gras		1;30
rouge		0;31	rouge gras		1;31
vert		0;32	vert gras		1;32
jaune		0;33	jaune gras		1;33
bleu		0;34	bleu gras		1;34
magenta		0;35	magenta	gras		1;35
cyan		0;36	cyan gras		1;36
blanc		0;37	blanc gras		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)) :
$ 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.

Développement

Logger (é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>&3

...
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

Ressources en ligne