Le shell en ligne de commande

Créer des processus

Le shell a la faculté de lancer de nombreux programmes, sinon tous. C'est une des raisons pour lesquelles on peut dire que c'est un outil extrêmement puissant.

Qu'est-ce qu'un processus ?

Lancer un programme, c'est créer un processus. Tautologisons : un processus est une instance d'exécution d'un programme, c'est-à-dire l'exécution particulière d'un programme universel (i.e qui n'est pas réductible à l'une de ses exécutions).

Par exemple, si je lance Mozilla, un processus se crée, du nom de mozilla. Ensuite, sans fermer ce programme, je lance une deuxième fois Mozilla ; un nouveau processus se crée, du nom de mozilla également. J'ai donc deux processus distincts, même s'ils concernent le même programme. Et à chaque fois que je lance un programme, un processus se crée.

Généalogie des processus

Dans notre exemple, nous avons deux processus mozilla. Ils sont indépendants l'un de l'autre : quoi qu'il puisse se passer dans l'un, cela ne devrait pas influer sur le comportement de l'autre.

Toutefois, s'ils sont indépendants l'un de l'autre, tous deux dépendent du processus qui les a lancés. Eh oui, le shell est un programme comme un autre, donc chacune de ses exécutions correspond également à la création d'un processus. Si vous lancez trois shells en même temps, par exemple dans trois terminaux différents, vous aurez trois processus shell distincts.

Les processus entretiennent entre eux des relations de parenté. Mais comme les processus ont tous le même sexe, chaque processus n'a qu'un parent. Un processus peut avoir plusieurs enfants.

Le shell est, comme nous venons de le voir, un processus qui peut enfanter d'autres processus. Par exemple, à chaque fois que je tape mozilla, mon shell crée un processus mozilla.

Lancer des processus en arrière-plan

Par défaut, le shell met le processus enfant en avant-plan. Cela veut dire que si je tape simplement :

chaland ~ mozilla

le processus mozilla prend la main, et le shell ne réagit plus (il n'affiche même plus l'invite de commande) avant la fin du processus enfant. Ce n'est que lorsque le processus mozilla prend fin (si je ferme ce programme, par exemple) que le shell reprend la main.

Ce comportement par défaut n'est parfois pas très pratique. Par exemple, si je veux lancer Mozilla, mais aussi Emacs et d'autres programmes qui s'ouvrent dans leur propre fenêtre, je suis obligé d'ouvrir à chaque fois un nouveau terminal avec un nouveau shell, et ce uniquement pour y taper une commande, après quoi le shell se retrouve inutilisable jusqu'à la fin de chacun de ces programmes.

Il faudrait donc que l'on puisse lancer des processus enfants en arrière-plan, de telle sorte que ces processus se lancent dans leur propre fenêtre et me permettent toutefois de dialoguer avec le shell sans avoir à :

Pour cela, il faut ajouter le signe « & » à la fin d'une ligne de commande. On peut ainsi taper :

chaland ~ mozilla &
chaland ~ mozilla &
chaland ~

et de la sorte, un même shell peut engendrer autant de processus que vous voulez.

Émanciper un processus enfant

La vie est déjà plus pratique comme cela. Mais parfois, on aimerait que le processus enfant survive au processus parent. Car par défaut, la mort d'un processus parent entraîne celle de tous ses enfants. Par exemple, si le shell qui a enfanté les deux processus mozilla vient à décéder (si je le ferme brutalement, ou s'il plante), les deux processus mozilla le suivront dans la mort.

Il faudrait donc qu'un processus parent puisse émanciper des processus enfants, pour que sa propre mort n'entraîne pas les leurs. Pour cela, il faut utiliser les symboles « &! ». Par exemple, si je tape :

chaland ~ mozilla &!
chaland ~ mozilla &!
chaland ~

puis que je ferme brutalement le shell en tapant Ctrl-D et en ignorant d'éventuels avertissements, le shell disparaîtra, mais pas les processus mozilla.

Arguments et options

Jusqu'ici, l'exemple de processus enfant que nous avons pris était relativement simple : il s'agissait de la commande mozilla, que l'on peut lancer en tapant simplement son nom.

Jusqu'ici, on peut se dire que la ligne de commande, c'est comme quand on clique sur une icône, sauf que c'est plus fatigant. Effectivement, pourquoi se fatiguer à taper une suite de caractères quand il suffit d'appuyer une ou deux fois sur le bouton d'une souris ?

C'est dans l'usage des arguments et des options que la ligne de commande commence à révéler une partie de sa puissance, et de sa nette supériorité sur les interfaces graphiques.

Les arguments

Un argument est une suite de caractères que l'on donne à une commande et qui lui indique comment se comporter. Les options sont un type d'argument.

Exemples

Par exemple, si je veux ouvrir le fichier hermocrate.txt avec l'éditeur de texte nano, j'ai le choix entre deux possibilités :

Ou encore, pour reprendre l'exemple de Mozilla, si je veux aller voir la page des tuteurs et que ce n'est pas ma page d'accueil par défaut (ce qui est un tort !), je peux taper :

chaland ~ mozilla http://www.tuteurs.ens.fr/

Les options

Les options sont un cas particulier d'arguments, qui donnent au programme que l'on exécute toutes sortes d'information, souvent plus ou moins périphériques, en général moins importantes que les arguments.

Sous Unix, les options commencent généralement par un tiret. Certaines options peuvent à leur tour prendre des arguments.

Par exemple, si l'on veut ouvrir un fichier au format MS-DOS, on peut taper :

chaland ~ nano -D hermocrate.txt

Options à arguments

L'option -r de nano prend un argument ; ainsi, si l'on veut que Nano coupe les lignes au bout de 75 caractères, on doit ouvrir Nano en tapant :

chaland ~ nano -r 75 hermocrate.txt

Options à nom long

Traditionnellement, et pour des raisons de concision, la majorité des options, sous Unix, est composée d'un tiret et d'une seule lettre. Toutefois, pour des raisons pratiques, il peut également exister des noms d'option longs ; ils sont en général précédés, non d'un, mais de deux tirets. Exemple :

chaland ~ nano --fill=75 hermocrate.txt

Dans cet exemple, il se trouve que l'option à nom long --fill= équivaut à l'option à nom court -r. Mais toutes les commandes courtes n'ont pas systématiquement leur équivalent à nom long, et vice versa.

Jokers et citations

Raccourcis pour les noms de fichiers : les jokers

Il est souvent ennuyeux d'avoir à taper une longue liste de fichiers pour les donner en arguments à une commande, comme :

cc -o foo bar.c gee.c buz.c gog.c

(pour les curieux, sachez que cette commande sert à compiler un programme écrit en langage C, c'est-à-dire à le traduire en langage machine. Mais avant de vous mettre au langage C, attendez de bien connaître le shell, il vous apprendra plus facilement et plus agréablement les bases de la programmation...)

Pour éviter les problèmes liés à la frappe de longues lignes de commandes, on peut utiliser des jokers (wildcards en anglais). Pourquoi ce nom ? Eh bien, dans certains jeux de cartes, le joker permet de remplacer n'importe quelle carte ; dans le shell, les jokers permettent de remplacer n'importe quel caractère ou n'importe quelle séquence de caractères.

Il existe principalement trois types de jokers :

L'étoile : *

Une étoile * dans un nom de fichier est interprétée par le shell comme « n'importe quelle séquence de caractères » (mais ça ignore les fichiers dont le nom commence par un point). Exemple :

cc -o foo *.c

Pour interpréter l'étoile, le shell va faire la liste de tous les noms de fichiers du répertoire courant qui ne commencent pas par . et qui finissent par .c. Ensuite, il remplace *.c par cette liste (triée par ordre alphabétique) dans la ligne de commande, et exécute le résultat, c'est-à-dire par exemple :

cc -o foo bar.c buz.c foo.c gee.c gog.c

Le point d'interrogation : ?

On a aussi le point d'interrogation ?, qui remplace un (et exactement un) caractère quelconque (sauf un point en début de nom). Par exemple, ls *.? liste tous les dont l'extension ne comporte qu'un caractère (.c, .h...).

Les crochets : []

La forme [abcd] remplace un caractère quelconque parmi a, b, c, d. Enfin, [^abcd] remplace un caractère quelconque qui ne se trouve pas parmi a, b, c, d.

Exemple

echo /users/*

affiche à peu près la même chose que

ls /users

(La commande echo se contente d'afficher ses arguments.)

&icone.attention; Attention :

Interlude : comment effacer un fichier nommé ?* ? On ne peut pas taper rm ?* car le shell remplace ?* par la liste de tous les fichiers du répertoire courant. On peut taper rm -i * qui supprime tous les fichiers, mais en demandant confirmation à chaque fichier. On répond n à toutes les questions sauf rm: remove  ?*. Autre méthode: utiliser les mécanismes de citation.

Contourner les jokers : la citation (quotation)

Avec tous ces caractères spéciaux, comment faire pour passer des arguments bizarres à une commande ? Par exemple, comment faire afficher un point d'interrogation suivi d'une étoile et d'un dollar par echo ?

On ne peut pas les taper directement dans la ligne de commande (ici, avec le shell zsh), car le shell essaye de les interpréter comme des jokers :

chaland ~ echo ?*$
zsh: no matches found: ?*$

Il faut donc indiquer au shell de ne pas interpréter ces caractères comme des jokers. Pour cela, nous allons utiliser des mécanismes fournis par le shell : les quotations (mot anglais signifiant citation).

Les mécanismes de citation sont les suivants :

Le backslash (\)

Il suffit de précéder un caractère spécial d'un backslash, et le shell remplace ces deux caractères par le caractère spécial seul. Évidemment, le backslash est lui-même un caractère spécial.

Exemples :

chaland ~ $ echo \?\*\$
?*$
chaland ~ $ echo \\\?\\\*\\\$
\?\*\$

Les apostrophes ou simples quotes (')

Un autre moyen est d'inclure une chaîne de caractères entre apostrophes (simples quotes) '. Tout ce qui se trouve entre deux apostrophes sera passé tel quel par le shell à la commande. Exemple :

chaland ~ $ echo '$?*'
$?*

Les guillemets doubles ou doubles quotes (")

Les guillemets se comportent comme les apostrophes, à une exception près: les dollars et les backslashes sont interprétés entre les guillemets. Exemple :

chaland ~ $ echo "$HOME/*"
/users/87/maths/doligez/*

Une technique utile: Quand on juxtapose deux chaînes de caractères quotées, le shell les concatène, et elles ne forment qu'un argument. Exemple :

chaland ~ $ echo "'"'"'
'"

Quant aux interactions plus compliquées (backslashes à l'intérieur des guillemets, guillemets à l'intérieur des apostrophes, etc.), le meilleur moyen de savoir si ça donne bien le résultat attendu est d'essayer. La commande echo est bien utile dans ce cas.

Les backquotes (`)

Dernière forme de citation : `commande`. Le shell exécute la commande indiquée entre backquotes, lit la sortie de la commande mot par mot, et remplace ` commande ` par la liste de ces mots. Exemple :

chaland ~ $ echo `ls`
Mail News bin foo lib misc mur notes.aux notes.dvi notes.log
notes.tex planar text
chaland ~ $ ls -l `which emacs`
-rwxr-xr-t  1 root   taff   978944 Jul 16  1991 /usr/local/bin/emacs

La commande which cmd employée ci-dessus affiche sur sa sortie le nom absolu du fichier exécuté par le shell quand on lance la commande it cmd :

chaland ~ $ which emacs
/usr/local/bin/emacs

Vous êtes maintenant en mesure de faire ces exercices pour vous entraîner. Ou bien vous pouvez revenir à la page centrale sur le shell, d'où vous pourrez vous orienter vers d'autres parties du cours.

Basé sur un polycopié de Roberto Di Cosmo, Xavier Leroy et Damien Doligez. Modifications : Nicolas George, Baptiste Mélès.