Un shell est un programme qui te permet de saisir des commandes pour interagir avec le système d'exploitation. Les shells peuvent être graphiques ; l'environnement de bureau que tu utilises à la souris ou au trackpad est un shell. On va se concentrer sur les interfaces en ligne de commande, où tu saisis des commandes et où les résultats sont (généralement) affichés dans le terminal.
Il existe de nombreux shells en ligne de commande différents. Windows propose cmd et powershell. Mac et Linux proposent des « shells Unix », dont il existe de nombreuses variantes. Le shell par défaut sur Mac est zsh. csh est un shell ancien à la syntaxe proche du C. bash est l'un des shells les plus populaires, en particulier sous Linux.
L'un des tout premiers shells est le shell Bourne, sh. Avec le temps, ce shell a été normalisé pour être fourni sur tout système de type Unix. De nombreux shells sont basés sur le shell Bourne, en apportant souvent des fonctionnalités interactives ou de programmation supplémentaires.
Bash est basé sur le shell Bourne. C'est pour cela qu'on l'appelle le « Bourne-again shell ». C'est le shell par défaut sur de nombreux systèmes Linux. Bash offre de nombreuses fonctionnalités qui en font un shell interactif agréable, notamment :
Ce parcours ne porte pas sur l'utilisation de bash comme shell interactif. Il s'agit d'apprendre à écrire des programmes avec bash. L'accent sera mis sur l'utilisation d'uniquement bash pour résoudre des problèmes, bien qu'il n'y ait aucune restriction quant à l'appel à des outils externes.
Bash lit les commandes depuis son entrée (qui est généralement soit un terminal, soit un fichier). Chaque ligne d'entrée qu'il lit est traitée comme une commande, c'est-à-dire une instruction à exécuter. (Il existe quelques cas avancés, comme les commandes qui s'étalent sur plusieurs lignes, dont nous parlerons plus tard.)
Bash découpe chaque ligne en mots délimités par un caractère d'espacement (espaces et tabulations). Le premier mot de la ligne est le nom de la commande à exécuter. Tous les mots restants deviennent des arguments de cette commande (options, noms de fichiers, etc.).
Dans les exemples ci-dessous, on te montre quelques commandes interactives que tu peux exécuter.
# commence un commentaire.
Tous les mots qui suivent ce commentaire sont ignorés par le shell ; le commentaire n'est là que pour la lecture.$ indique une invite de shell.
Si tu copies la commande, n'inclus pas l'invite.# Note: It is always a good idea to run `ls` before `rm`
# to make sure you know what you are deleting.
$ rm * # Remove all files in the current directory.
# Make sure you run this in a temp directory or you can lose files!
$ ls # List files in the current directory (no output, no files).
$ touch a b c # Create files 'a', 'b' and 'c'.
$ ls # List files again; this time the outputs: 'a', 'b' and 'c'.
a b c
rm est une application qui supprime les fichiers qu'on lui donne.
* est un caractère glob, un motif « joker » qui correspond à tous les fichiers du répertoire courant.
On parlera plus en détail des globs plus tard.
As-tu remarqué qu'il y a plusieurs espaces entre a et b, et un seul entre b et c ?
La quantité d'espaces entre les arguments n'a pas d'importance !
Par exemple :
$ echo This is a test.
This is a test.
$ echo This is a test.
This is a test.
echo est une commande qui affiche ses arguments sur la sortie standard (qui, dans notre cas, est le terminal).
Bash découpe la commande en cinq mots et invoque la commande echo avec quatre arguments.
Les espaces supplémentaires du deuxième exemple ne changent rien.
echo ne sait pas comment les espaces étaient répartis à l'origine entre les mots.
Si on veut conserver les espaces supplémentaires, il faut passer la phrase comme un seul argument. On peut le faire en utilisant des guillemets :
$ echo "This is a test."
This is a test.
Les guillemets regroupent tout ce qu'ils contiennent en un seul argument.
L'argument est This is a test., espaces spécifiques compris.
À noter que les guillemets ne font pas partie de l'argument : bash les retire avant de transmettre cet argument unique à echo.
Les arguments sont séparés du nom de la commande et les uns des autres par un espace. C'est un point important à retenir. Par exemple, ce qui suit est incorrect :
$ [-f file]
bash: [-f: command not found
L'intention est de tester l'existence d'un fichier nommé file.
On pourrait raisonnablement penser que les espaces autour de [ et ] n'ont pas d'importance, mais [ est en réalité une commande, et elle exige que son dernier argument soit ].
(On verra la commande [ plus en détail plus tard.)
Par conséquent, il faut séparer [ de -f et ] de file.
Sinon, bash croira qu'on essaie d'exécuter une commande nommée [-f avec un seul argument file].
La commande correcte sépare tous les arguments par un espace :
$ [ -f file ]
(On voit beaucoup de personnes déroutées par ce comportement ; elles pensent que [...] n'est « qu'une syntaxe » et qu'elles peuvent omettre les espaces ; [ est une commande et les espaces sont obligatoires.)
Si le nom de fichier contient des espaces ou d'autres caractères spéciaux, il faut aussi le mettre entre guillemets :
$ [ -f "my file" ]
Bash connaît plusieurs types de commandes différents : les alias, les fonctions, les commandes internes, les mots-clés et les exécutables. Ce sont tous des commandes, et elles se comportent toutes de manière très similaire.
Par défaut, les alias ne sont activés que dans les shells interactifs, pas dans les scripts shell. On ne les abordera pas ici.
Une fonction contient des commandes shell et se comporte un peu comme un petit script ; elle peut même prendre des arguments et créer des variables locales. Quand une fonction est appelée, les commandes qu'elle contient sont exécutées. Les fonctions seront traitées en détail plus tard.
Bash intègre certaines commandes de base, comme : cd (changer de répertoire), echo (écrire une sortie), etc.
On peut les considérer comme des fonctions déjà fournies.
Les mots-clés ressemblent aux commandes internes, la principale différence étant qu'un mot-clé fait en réalité partie de la syntaxe de bash et peut être analysé selon des règles spéciales.
Par exemple, [ est une commande interne de bash, tandis que [[ est un mot-clé de bash ; les deux servent à tester diverses conditions.
Mais le mot-clé [[ offre des fonctionnalités supplémentaires.
Par exemple, il fournit l'opérateur =~ qui effectue des tests d'expressions régulières.
$ [[ "hello world" =~ e.*o ]] # returns a "success" status
$ [ "hello world" =~ e.*o ]
bash: [: =~: binary operator expected
Les opérateurs de comparaison de chaînes de caractères < et > sont un autre exemple de la façon dont les mots-clés analysent le code différemment.
À l'intérieur de [[...]], ces opérateurs ne demandent aucun traitement particulier.
$ if [[ "$USER" < "n" ]]; then
echo "$USER is in the first half of the alphabet"
fi
# => glennj is in the first half of the alphabet
Mais à l'intérieur de [...], tu dois les mettre entre guillemets ou les échapper pour éviter que le shell ne les interprète comme des opérateurs de redirection.
$ if [ "$USER" < "n" ]; then
echo "$USER is in the first half of the alphabet"
fi
# => bash: n: No such file or directory
$ if [ "$USER" \< "n" ]; then
echo "$USER is in the first half of the alphabet"
fi
# => glennj is in the first half of the alphabet
Le dernier type de commande que bash peut exécuter est un exécutable.
Ce sont des fichiers exécutables (des programmes) présents sur le système de fichiers de ton ordinateur, que tu peux lancer.
(Les exécutables sont aussi parfois appelés commandes externes ou applications.)
On invoque généralement un exécutable en tapant uniquement son nom ; le chemin complet du fichier n'est en principe pas nécessaire.
C'est possible parce que bash utilise une variable prédéfinie qui l'aide à trouver l'emplacement des fichiers exécutables courants.
Cette variable s'appelle PATH.
C'est un ensemble de noms de répertoires séparés par des deux-points (par exemple /usr/bin:/bin).
Quand une commande est indiquée (par exemple myprogram ou ls) sans chemin de fichier (et qu'il ne s'agit ni d'un alias, ni d'une fonction, ni d'une commande interne, ni d'un mot-clé), bash parcourt les répertoires de PATH.
Les répertoires sont parcourus dans l'ordre, de gauche à droite, pour voir s'ils contiennent un exécutable portant le nom donné.
Si l'exécutable se trouve en dehors d'un chemin connu, il faut préciser le chemin du fichier de l'exécutable.
Pour l'exécutable myprogram situé dans le répertoire courant, utilise ./myprogram ; s'il se trouve dans le répertoire /opt/somedirectory, utilise /opt/somedirectory/myprogram.
Astuce : la commande type permet d'obtenir des détails sur une commande :
$ type rm
rm is hashed (/bin/rm)
# or it might return: `rm is /bin/rm`
$ type cd
cd is a shell builtin
Un script est une suite de commandes stockées dans un fichier. Bash lit le fichier et traite les commandes dans l'ordre. Il ne passe à la commande suivante qu'une fois la commande courante terminée. L'exception étant qu'une commande peut être spécifiée pour s'exécuter de façon asynchrone (en arrière-plan). Ne t'inquiète pas trop de ce cas pour l'instant ; on verra comment cela fonctionne plus tard.
Tout exemple de ligne de commande de ce guide peut aussi être utilisé dans un script.
Créer un script est simple. Commence par créer un nouveau fichier et place ceci sur la première ligne :
#!/bin/bash
Cette ligne est appelée une directive d'interprète (on l'appelle aussi hashbang ou shebang).
Elle indique que /bin/bash doit être utilisé comme interprète lorsque le fichier est utilisé comme exécutable dans une commande.
Quand le noyau du système d'exploitation exécute un fichier non binaire, il lit la première ligne du fichier.
Si la ligne commence par #!, le noyau s'en sert pour déterminer l'interprète auquel transmettre le fichier.
(Il existe d'autres façons valables de faire cela, voir ci-dessous.)
Le #! doit se trouver tout au début du fichier, sans espace ni ligne vide avant.
Les commandes de notre script apparaîtront sur des lignes séparées en dessous.
Astuce : au lieu de #!/bin/bash, tu peux utiliser
#!/usr/bin/env bash
env cherche dans $PATH l'exécutable nommé par son premier argument (ici, « bash »).
Pour une explication plus détaillée de cette technique et de ses différences avec le bon vieux #!/bin/bash, voir cette question sur StackOverflow.
Ne te laisse pas tromper par les scripts ou les exemples sur Internet qui utilisent /bin/sh comme interprète.
sh n'est pas bash !
Bash est lui-même un shell « compatible sh », ce qui signifie qu'il peut exécuter la plupart des scripts sh et reprend une grande partie de la même syntaxe.
En revanche, l'inverse n'est pas vrai ; certaines fonctionnalités de bash se cassent ou provoquent un comportement inattendu dans sh.
En fait, il n'existe pas un shell sh unique.
Il existe de nombreuses implémentations de shell qui prennent en charge la syntaxe POSIX-sh.
Selon ton système d'exploitation, quand tu lances sh, tu exécutes peut-être en réalité bash, ash, dash ou un autre shell.
Une fois le fichier de script créé, on peut l'exécuter ainsi :
$ bash myscript
Dans cet exemple, on exécute la commande bash et on lui demande de lire le script qu'on a fourni comme argument.
Quand on procède ainsi, la ligne #! n'est qu'un commentaire.
Autrement, on peut donner à nos scripts les permissions d'exécution. Avec cette méthode, on peut exécuter le script comme n'importe quel autre exécutable au lieu d'appeler bash manuellement :
$ chmod +x myscript # Mark the file as executable.
$ ./myscript # Now, myscript can be executed directly
# instead of having to specify bash.
Quand on lance la commande de cette façon, le système d'exploitation utilise la ligne #! pour déterminer quel interprète utiliser pour exécuter ton script.
Le contenu de cette leçon est tiré de Commands and Arguments du Bash Guide.