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 !

Comparaison très rapide du prompt entre Starship (avec Zsh mais ça ne change rien), sh et Bash

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 et Flè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 faites Ctrl+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 touches Flèche vers le haut et Flè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 puis Enter, 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.