C'est quoi Bash ? Zsh ? Un Shell ? Terminal ? VTE ? Prompt ?
Pour commencer repartons de la base : de quoi qu'on cause ?
Un Shell en informatique c'est une interface utilisateur permettant d’interagir avec le système d'exploitation (OS). En général on parle de Shell plutôt pour des interfaces en ligne de commande et surtout sur des systèmes type Unix / Linux. Si vous êtes sous macOS, vous avez un terminal et un Bash standard. Si vous êtes sous Windows pas tout à fait, mais vous avez deux éléments qui s'en rapproche : Cmd et PowerShell (mais je ne détaillerai pas ici).
Note : Vous verrez parfois passer "Gnome Shell" qui est correspond à l'interface utilisateur proposée par la fondation Gnome. Ce n'est pas une erreur d'appeler ça un Shell mais c'est peu courant comme usage, mais juste pour dire qu'on peut trouver des "Shell" graphique.
Le Bash (Bourne-Again shell) ou Zsh (ou Z Shell) ou d'autres sont des exemples de Shell. Bash est je pense aujourd'hui le plus commun, mais on pourrait aussi citer sh
(ou bsh
ou Bourne Shell) qui est je pense le sh vraiment le plus présent partout et avec lequel la plupart des Shell sont compatibles, car plutôt une extension du langage sh
que quelque chose de différent. J'ai bien dit "langage" au sens langage de programmation, car un Shell fourni bien un langage pour interagir avec l'OS, et la majorité des gens ont tendances à l'oublier !
On entend aussi parler de Terminal ou émulateur de terminal ou VTE (Virtual Terminal Emulator), c'est ni plus ni moins que le logiciel qui va mettre une fenêtre autour de votre Shell pour que vous puisse l'utiliser depuis une interface graphique sur votre système. Personnellement, j'aime beaucoup Tilix et je l'utilise depuis déjà quelques années maintenant.
Si vous voyez passer le terme "prompt" c'est tout simplement la partie que vous voyez répétée à chaque fois que le shell vous donne la main pour saisir une commande. J'utilise depuis quelques années (et je peux donc vous recommander) Starship : écrit en Rust, cross shell (Bash, sh, Zsh, etc.), il contextualise automatiquement votre prompt à l'état courant de votre système et du dossier dans lequel vous vous trouvez. Bonus : il s'installe super facilement et est aussi cross plateforme en plus d'être très performant !

Dans cet article je vais essayer de vous montrer des choses qui fonctionneront dans n'importe quel Shell dérivé de sh
, car on peut déjà faire beaucoup de chose avec un simple sh
pour se faciliter la vie !
Les raccourcis clavier de base
Il y a beaucoup de raccourci clavier important à connaître en Shell pour rendre l'utilisation du Shell efficace. Je vous partage ceux que j'utilise vraiment le plus !
Flèche vers le haut
etFlèche vers le bas
pour naviguer dans l'historique de commande directement (haut pour aller plus loin dans l'historique, bas pour aller vers les commandes plus récentes). Raccourci ultra important pour éviter de toujours ressaisir les mêmes commandes pour une typo par exemple ou rejouer une commande ;Ctrl+r
pour lancer une recherche dans l'historique. L'idée est simple : vous faitesCtrl+r
, puis vous commencez à saisir une partie de commande et automatiquement vous allez voir s'afficher la commande la plus récente qui contient ce que vous avez saisi, en sachant que vous pouvez à tout moment naviguer dans l'historique filtré avec les touchesFlèche vers le haut
etFlèche vers le bas
;Ctrl+c
pour couper une commande ;Ctrl+z
pour passer la commande en cours en arrière-plan pour retrouver un prompt et saisir une autre commande (surtout pratique quand on est en ssh) ;Ctrl+a
pour déplacer le curseur au début de la commande en cours ;Ctrl+e
déplacer le curseur à la fin de la commande en cours ;Ctrl+w
effacer du curseur jusqu'au début du bloc (premier espace trouvé) ;
Il existe beaucoup d'autres raccourcis clavier, mais déjà avec ceux-là vous devez couvrir 90% des cas du quotidien ! 🤓
Jouer avec l'historique
Chaque commande qu'on exécute sera enregistrée dans l'historique du Shell. Il y a deux historiques : un pour la session en cours et un sous forme de fichier qui reste même après redémarrage.
La configuration de l'historique se fait via trois variables d'environnements : HISTFILE
, HISTSIZE
et SAVEHIST
. HISTFILE
va définir le chemin du fichier qui sera utilisé pour enregistrer l'historique, HISTSIZE
la taille de l'historique de votre session, et SAVEHIST
la taille de l'historique qui doit être enregistré dans le fichier d'historique.
Sur un Bash ou sh, vous aurez ça par défaut :
[fake-account@Lust2 blog]$ echo $HISTFILE
/home/fake-account/.bash_history
[fake-account@Lust2 blog]$ echo $HISTSIZE
500
[fake-account@Lust2 blog]$ echo $SAVEHIST
Pas d'erreur sur la dernière commande, la variable SAVEHIST
n'est pas définie. Donc par défaut : vous n'avez pas d'historique persistant sur votre machine, ce qui est une bonne et une mauvaise chose. C'est une bonne chose dans le sens où vous ne risquez pas de garder en clair des données taper par erreur dans votre terminal, c'est une mauvaise chose au sens où vous ne profitez pas d'un historique long termes (personnellement j'aime pouvoir retrouver une commande que j'ai tapée il y a plusieurs semaines avec un Ctrl=r
). Par défaut vous avez donc un historique de 500 commandes sur la session courante mais quand vous quittez le shell tout est perdu.
Sur ma machine je configure comme suit :
HISTFILE=~/.histfile
HISTSIZE=100000
SAVEHIST=1000000
Comment ça je suis un gros utilisateur de l'historique ? 😶🌫️
Pour afficher l'historique, on a pas besoin de manipuler le fichier directement :
history
1 ls
2 cd /tmp
3 ls
4 mkdir foo
5 history
À noter qu'on peut toujours faire du ménage dans son historique à la main :
history -d 4 # va supprimer l'entrée 4
history -c # va vider entièrement l'historique
Faire du substring
Pour supprimer une partie en comptant les caractères :
path=features/my-card.component.ts
without_dir=${path:9}
without_ext=${without_dir::-3}
without_kind=${without_ext:9:-13}
Pour supprimer une partie en la nommant :
path=features/my-card.component.ts
without_dir=${path#features/}
without_ext=${without_dir%.ts}
without_kind=${without_ext%.component}
À noter qu'on ne peut pas mixer #
et %
dans la même expression, mais on peut imbriquer les expressions !
without_kind=${${${path#features/}%.ts}%.component}
Pas forcément lisible mais ça fonctionne, et si c'est pour une commande rapide, c'est pas gênant. Pour un script plutôt à éviter !
Inline une commande ou un ensemble de commandes
Ça veut dire quoi "inline" une commande ? C'est quelque chose qu'on entend souvent mais si vous n'êtes pas familier avec le terme, il désigne simplement le fait de passer sur une seule ligne une commande.
Le cas de base c'est qu'on veut enchaîner des commandes en exécutant à la suite les unes des autres :
cd ./my/super/dir; ls *.ts
# est équivalent à
cd ./my/super/dir
ls *.ts
La subtilité ici c'est que si ./my/super/dir
n'existe pas, la commande ls *.ts
sera quand même exécuté. Pour éviter ça, on va utiliser &&
cd ./my/super/dir && ls *.ts
. À noter aussi que si vous ne mettez qu'un seul &
, vous passerez en mode parallèle : les deux commandes seront exécutées en parallèle ce qui peut être pratique ou illogique en fonction des cas à vous de voir. Personnellement j'utilise très peu (voir jamais) l'exécution parallèle.
Faire des for sur des paths
Je sais que pas mal de gens savent faire des for
même en Shell mais beaucoup ne savent pas qu'on peut directement passer un ou plusieurs motifs de chemin :
for file in path/to/a/directory/*
do
echo $file
done
Note : ces dernières années, je vois beaucoup plus ce formatage qui est une influence directe d'autres langage :
for file in path/to/a/directory/*; do echo $file done
On peut l'inline aussi
for file in path/to/a/directory/*;do echo $file; done
Évidement, la partie à droite du in est un ensemble de globbing pattern, donc on écrit un peu ce qu'on veut !
# va aller chercher
# - tous les fichiers .gif dans le dossier Images de l'utilisateur et tous les sous dossiers de Image
# - puis tous les fichiers .png contenu dans /tmp
# et itérer dessus pour appliquer un traitement
for gif in $HOME/Images/**/*.gif /tmp/*.png;do echo $gif; done
renommer en ajoutant/enlevant une partie avec {before,after}
mv fichier{,.min}.jpg
# équivalent à
mv fichier.jpg fichier.min.jpg
ça marche dans les deux sens
mv fichier{.min,}.jpg
# équivalent à
mv fichier.min.jpg fichier.jpg
Définir des variables d'environnement pour une commande en particulier
Souvent je vois des gens faire ça :
APP_DB_SECRET='the-BEST-P@ssw0rd'
# puis dans une autre commande
java -jar ./app.jar
voir
export APP_DB_SECRET='the-BEST-P@ssw0rd'
# puis dans une autre commande
java -jar ./app.jar
Ce n'est pas idéal car ça va définir la variable pour toutes les commandes qu'on va lancer ensuite. Pour certains trucs c'est plutôt dangereux car ça veut dire qu'un script malicieux (ou pas d'ailleurs ça peut être à cause d'un log qui pouvait paraitre légitime) pourrait faire fuiter des secrets.
La bonne manière de faire c'est celle-là :
APP_DB_SECRET='the-BEST-P@ssw0rd' java -jar ./app.jar
Et si on a plusieurs variables à passer, on peut le faire aussi !
APP_DB_SECRET='the-BEST-P@ssw0rd' APP_DB_PORT=3333 java -jar ./app.jar
Si vraiment le contenu des variables est sensible : pensez à nettoyer votre historique de shell !
Micro guide de survie avec vi
Pour commencer : je ne suis pas de la team Vim
ou Emacs
, utilisez l'éditeur que vous voulez, quand ça vous arrange vous, et adaptez avec votre besoin. J'ai appris quelques bases de vi
avec les années car force est de constater que vi
est présent partout (par défaut c'est l'éditeur utilisé par git), donc avoir quelques bases me fait gagner du temps.
Pourquoi vi
et pas vim
/ emacs
/ nano
/ etc. : comme j'ai dit il est présent partout, donc pas de question à se poser sur ce qui est installé.
Pourquoi vi
et pas VSCode / Gedit / Kate / Notepad / IntelliJ / etc. : ouvrir un éditeur graphique prend du temps, parfois plusieurs secondes (voir dizaine·s de secondes), pour certains cas c'est plus de temps pour ouvrir un fichier que pour moi faire la modification dans le-dit fichier, donc je ne vois pas l'intérêt.
Pour les fans de Vim : désolé mais non je n'adore pas votre éditeur préféré… Par contre ça ne veut pas dire que je vous embêterai parce que vous l'utilisez ! Ne m'en voulez pas si je prends des raccourcis ou si je dis des trucs un peu (beaucoup ?) faux, je ne maitrise pas l'outil, je l'utilise un peu pour des petites choses, je partage ce que j'ai compris rien de plus ! Peace ! ☮️
Le minimum à connaitre pour moi :
vi
est un éditeur modal, donc il faut entrer dans un mode pour faire des choses particulières ;- quand on ouvre un fichier, on est en mode lecture par défaut ;
- en mode lecture on peut naviguer dans le fichier avec les flèches pour déplacer le curseur ;
- si on sait ce qu'on cherche : on peut lancer une recherche avec
/ce que l'on cherche
puisEnter
, le curseur ira alors à la première occurrence trouvée de la recherche après la position actuelle du curseur ; - on peut appuyer sur
x
pour supprimer le caractère sur lequel le curseur est positionné ; - on peut appuyer sur
dd
pour supprimer la ligne sur laquelle le curseur est positionné ; - on peut passer en mode insertion en appuyant sur
i
; - en mode insertion on peut saisir du texte mais attention, l'écriture se fera avant le curseur et on ne peut pas utiliser les flèches pour déplacer le curseur ;
- en faisant
Esc
on peut revenir au mode lecture ; - en faisant en mode lecture
:q
on va pouvoir quitter simplement (:q!
si vous voulez ignorer les changements en cours) ; - en faisant en mode lecture
:w
on va pouvoir enregistrer le fichier ; - en faisant en mode lecture
:x
on va pouvoir enregistrer le fichier et quitter ;
Voilà à peu près tout ce que j'utilise de vi
et que je trouve utile quand c'est purement de l'appoint comme dans mon usage !
Exemples de la vie réelle
Changer une arborescence de fichier
Exemple qui mélange pas mal de chose : j'avais besoin de changer la manière dont sont imbriqués des fichiers.
Passer de ça :
.
├── 00-compiling-typescript.ts
├── 01-about-js-types.correction.ts
├── 01-about-js-types.spec.ts
├── 02-about-ts-types.correction.ts
├── 02-about-ts-types.spec.ts
├── 03-variable-declaration.correction.ts
├── 03-variable-declaration.spec.ts
...
À ça :
.
├── 00-compiling-typescript
│ └── 00-compiling-typescript.ts
├── 01-about-js-types
│ ├── 01-about-js-types.correction.ts
│ └── 01-about-js-types.spec.ts
├── 02-about-ts-types
│ ├── 02-about-ts-types.correction.ts
│ └── 02-about-ts-types.spec.ts
├── 03-variable-declaration
│ ├── 03-variable-declaration.correction.ts
│ └── 03-variable-declaration.spec.ts
...
On peut faire ça facilement en bash avec un for, avec un peu de substring et un peu d'enchaînement de commandes.
for f in *typescript.ts *.spec.ts;do exercice=${${f%.ts}%.spec}; echo $exercice; mkdir $exercice; mv ${exercice}*.ts $exercice; done
Si on met en forme pour que ça se lise un peu plus facilement ?
for f in *typescript.ts *.spec.ts
do
exercice=${${f%.ts}%.spec}
echo $exercice
mkdir $exercice
mv ${exercice}*.ts $exercice
done
Réduire la taille d'un lot d'image
for i in path/to/a/directory/with/some/images/*.jpg;do imgmin $i{,.min.jpg};done
for i in path/to/a/directory/with/some/images/*.jpg.min.jpg;do cp "$i" "${i/.jpg.min/.min}";done
L'idée est simple : parcourir les images .jpg d'un dossier (l'outil que j'utilise ne fonctionne que sur les jpg 🤷), ajouter .min.jpg
à la fin du nom de fichier, puis refaire une boucle sur les fichiers qui se termine par .jpg.min.jpg
pour supprimer le .jpg
avant le .min
. C'est quelque chose qu'il m'arrive souvent de faire pour le blog.
À noter que je ne tape pas deux fois la commande entière, après l'exécution de la première, je vais appuyer sur la touche "flèche vers le haut" pour récupérer la dernière commande saisie, puis juste modifier ce qui doit l'être (moins de la moitié de la commande donc).
Mettre à jour une dépendance pour chaque sous dossier d'un monorepo
git pull && cd ./front && npm-bump vite@x.y.z vitest@x.y.z && npm run lint:fix -- package.json && npm run test && cd ../e2e && npm-bump @playwright/test@1.51.1 && npm run lint:fix -- package.json && npm run test && git add -u && git commit -m "chore(front+e2e): update deps" && git pull -r && git push
C'est quelque chose que je fais vraiment au quotidien ça, et si vous avez lu mon dernier article, elle doit vous parler !
Éditer manuellement un ensemble de fichier rapidement
for xml in **/pom.xml; do vi $xml;done; git add -u && git commit -m "chore(all): update swagger-v3" && git push
Je suis peut-être mauvais en maven et je ne connais pas une commande magique pour mettre à jour une dépendance automatiquement sur un pom.xml en respectant les properties, mais personnellement je fais comme ça : je parcours tous les pom.xml
un a un, puis pour chacun j'ouvre vi
, je cherche la dépendance que je veux mettre à jour, j'édite la version, je sauvegarde, la commande m'ouvre le pom.xml
suivant et ainsi de suite, jusqu'à commit.
Simple et efficace, ça me prend peu de temps/effort de faire ça, et ça donne le résultat voulu.
Conclusion
Vous n'avez pas besoin d'être un·e expert·e du Shell / Bash ou de Linux pour utiliser un Shell au quotidien et y trouver votre compte à mon avis. Par contre c'est vrai qu'il y a des trucs à connaître qui ne s'inventent pas, et qui peuvent vous faire perdre un temps fou !
J'ai essayé de compiler simplement une partie des choses qui devrait pour moi être connu d'un peu tout le monde pour être à l'aise avec l'outil et se sentir efficace !
Source:
Crédit photo : Générée via Mistral AI avec le prompt suivant
Crée une image d'un développeur intensément concentré devant un ordinateur portable futuriste, avec un terminal affichant des commandes shell en cours d'exécution, entouré d'icônes flottantes rétro-éclairées symbolisant des raccourcis clavier, des fichiers de code ouverts, et des dossiers parfaitement organisés. La scène est conçue dans un style rétro-futuriste, avec des éléments néon lumineux et des couleurs vives contrastées, évoquant une ambiance à la fois nostalgique et avant-gardiste. L'ensemble reflète l'efficacité et la maîtrise technique, avec des détails inspirés des années 80, comme des lignes de grille et des interfaces holographiques.