Comment fonctionne le #! travail de shebang?

Dans un script, vous devez inclure un #! sur la première ligne suivi du chemin d'accès au programme qui exécutera le script (par exemple: sh, perl).

Pour autant que je sache, le caractère # indique le début d'un commentaire et cette ligne est supposée être ignorée par le programme exécutant le script. Il semblerait que cette première ligne à un certain point, lire par quelque chose pour que le script soit exécuté par le programme approprié.

Quelqu'un pourrait-il faire plus de lumière sur le fonctionnement du #!?

Je suis vraiment curieux à ce sujet, donc plus la réponse est approfondie, mieux c'est.

40
demandé sur Jonathan Leffler 2010-06-09 23:27:30

3 réponses

Lecture Recommandée:

Le chargeur de programme du noyau unix est responsable de cela. Lorsque exec() est appelé, il demande au noyau de charger le programme à partir du fichier à son argument. Il vérifiera ensuite les 16 premiers bits du fichier pour voir quoi format de fichier exécutable dont il dispose. S'il trouve que ces bits sont #!, Il utilisera le reste de la première ligne du fichier pour trouver le programme qu'il doit lancer, et il fournit le nom du fichier qu'il essayait de lancer (le script) comme dernier argument du programme interpréteur.

L'interpréteur fonctionne alors comme normal et traite le #! comme une ligne de commentaire.

36
répondu Kevin Panko 2014-07-14 16:02:50

Histoire Courte: Le shebang (#!) ligne est lue par le shell (par ex. sh, bash, etc.) le chargeur de programme du système d'exploitation. Bien qu'il ressemble formellement à un commentaire, le fait que ce soit les deux premiers octets d'un fichier marque le fichier entier comme un fichier texte et comme un script. Le script sera passé à l'exécutable mentionné sur la première ligne après le shebang. Voilà!


Légèrement plus longue histoire: Imaginez que vous avez votre script, foo.sh, avec le bit exécutable (x) défini. Ce fichier contient par exemple les éléments suivants:

#!/bin/sh

# some script commands follow...:
# *snip*

Maintenant, sur votre coquille, vous tapez:

> ./foo.sh

Edit: Veuillez également lire les commentaires ci-dessous après ou avant de lire ce qui suit! Comme il s'avère, j'ai eu tort. Ce n'est apparemment pas le shell qui transmet le script à l'interpréteur cible, mais le système d'exploitation (noyau) lui-même.

Rappelez-vous que vous tapez ceci dans le processus shell (supposons c'est le programme /bin/sh). Par conséquent, cette entrée devra être traitée par ce programme. Il interprète cette ligne comme une commande, car il découvre que la toute première chose entrée sur la ligne est le nom d'un fichier qui existe réellement et qui a le bit exécutable(S) ensemble.

/bin/sh puis commence à lire le contenu du fichier et découvre le shebang (#!) dès le tout début du fichier. Pour le shell, c'est un jeton ("Nombre Magique") par lequel il sait que le fichier contient un script.

Maintenant, comment sait-il quel langage de programmation le script est écrit? Après tout, vous pouvez exécuter des scripts Bash, Perl, Python scripts ... Tout ce que le shell sait jusqu'à présent, c'est qu'il regarde un fichier de script (qui n'est pas un fichier binaire, mais un fichier texte). Ainsi, il lit l'entrée suivante à la première rupture de ligne (ce qui entraînera /bin/sh, comparer avec ce qui précède). C'est l'interpréteur auquel le script sera passé pour l'exécution. (Dans ce dans ce cas, l'interpréteur cible est le shell lui-même, il n'a donc pas besoin d'invoquer un nouveau shell pour le script; il traite simplement le reste du fichier de script lui-même.)

Si le script était destiné par exemple à /bin/perl, Tout ce que L'interpréteur Perl aurait (éventuellement) à faire est de voir si la ligne shebang mentionne vraiment L'interpréteur Perl. Sinon, L'interpréteur Perl saura qu'il ne peut pas exécuter ce script. Si en effet L'interpréteur Perl est mentionné dans la ligne shebang, il lit le reste du fichier de script et l'exécute.

8
répondu stakx 2010-06-09 19:49:10

L'appel système du noyau Linux exec utilise les octets initiaux #! pour identifier le type de fichier

Quand vous faites sur bash:

./something

Sous Linux, cela appelle l'appel système exec avec le chemin ./something.

Cette ligne est appelée dans le noyau sur le fichier transmis à exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

Si ((bprm- > buf[0]!= '#') || (bpgr->buf[1] != '!'))

Il lit le tout premier octets du fichier, et les compare à #!.

Si la comparaison est vraie, alors le reste de la ligne est analysé par le noyau Linux, ce qui fait un autre appel exec avec path /usr/bin/env python et current file comme premier argument:

/usr/bin/env python /path/to/script.py

Et cela fonctionne pour tout langage de script qui utilise # comme caractère de commentaire.

Et oui, vous pouvez faire une boucle infinie avec:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

Bash reconnaît l'erreur:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#! est lisible par l'homme, mais c'est n'est pas nécessaire.

Si le fichier avait démarré avec des octets différents, l'appel système exec utiliserait un gestionnaire différent. L'autre gestionnaire intégré le plus important est pour les fichiers exécutables ELF: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305 qui vérifie les octets 7f 45 4c 46 (qui sont également lisibles par l'homme pour .ELF), qui lit le fichier elf, le met correctement en mémoire et démarre un nouveau processus avec. Voir aussi: Comment le noyau obtient-il un fichier binaire exécutable fonctionnant sous linux?

Enfin, vous pouvez ajouter vos propres gestionnaires shebang avec le mécanisme binfmt_misc. Par exemple, vous pouvez ajouter un gestionnaire personnalisé pour les fichiers .jar . Ce mécanisme prend même en charge les gestionnaires par extension de fichier. Une autre application consiste à exécuter de manière transparente des exécutables d'une architecture différente avec QEMU .

Je ne pense pas que POSIX spécifie des shebangs: https://unix.stackexchange.com/a/346214/32558

4