Est-ce que le shebang détermine le shell qui exécute le script?


84

C'est peut-être une question idiote, mais je la pose quand même. Si j'ai déclaré un shebang

#!/bin/bash 

au début de my_shell_script.sh, alors dois-je toujours invoquer ce script en utilisant bash

[my@comp]$bash my_shell_script.sh

ou puis-je utiliser par exemple

[my@comp]$sh my_shell_script.sh

et mon script détermine le shell en cours d'exécution en utilisant le shebang? Est-ce la même chose avec kshshell? J'utilise AIX.


6
il y a un peu de confusion de votre part: quand vous faites "_some_shell some_script", il commence à _some_shell et lui demande d'interpréter some_script. Donc non, si vous faites "sh my_shell_script.sh", il n'interprétera pas le shebang, mais interprétera le script en sh à la place. Pour utiliser le shebang: chmod +x my_shell_script.sh ; /path/to/my_shell_script.sh # or ./my_shell_script.sh if you happen to be in its directory
Olivier Dulac

Réponses:


117

Le shebang #! est une instance lisible par l'homme d'un nombre magique composé de la chaîne d'octets 0x23 0x21, qui est utilisée par la exec()famille de fonctions pour déterminer si le fichier à exécuter est un script ou un binaire. Lorsque le shebang est présent, exec()exécutera l'exécutable spécifié après le shebang.

Notez que cela signifie que si vous appelez un script en spécifiant l'interpréteur sur la ligne de commande, comme dans les deux cas donnés dans la question, exec()il exécutera l'interpréteur spécifié sur la ligne de commande, le script ne sera même pas examiné.

Ainsi, comme d'autres l'ont noté, si vous souhaitez exec()appeler l'interpréteur spécifié sur la ligne shebang, le script doit avoir le bit exécutable défini et appelé en tant que ./my_shell_script.sh.

Le comportement est facile à démontrer avec le script suivant:

#!/bin/ksh
readlink /proc/$$/exe

Explication:

  • #!/bin/kshdéfinit kshpour être l'interprète.

  • $$ contient le PID du processus en cours.

  • /proc/pid/exe est un lien symbolique vers l'exécutable du processus (au moins sous Linux; sous AIX, /proc/$$/object/a.out est un lien vers l'exécutable).

  • readlink affichera la valeur du lien symbolique.

Exemple:

Remarque : je le démontre sur Ubuntu, où le shell par défaut /bin/shest un lien symbolique vers un tiret, c’est /bin/dash-à- /bin/kshdire un lien symbolique vers /etc/alternatives/ksh, qui à son tour est un lien symbolique vers /bin/pdksh.

$ chmod +x getshell.sh
$ ./getshell.sh 
/bin/pdksh
$ bash getshell.sh 
/bin/bash
$ sh getshell.sh 
/bin/dash

merci Thomas pour cette réponse. Imaginons que nous lançons le script en tant que processus enfant à partir de Node.js ou de Java ou autre. Pouvons-nous lancer un processus "exec", puis exec exécutera le script shell? Je demande beause je cherche des réponses à cette question: stackoverflow.com/questions/41067872/…
Alexander Mills

1
@AlexanderMills La exec()réponse à cette question est un appel système, la commande execest un shell intégré. C'est pourquoi vous ne pouvez pas appeler un exec programme à partir de Node.js ou de Java. Cependant, toute commande shell appelée, par exemple Runtime.exec()en Java, est finalement traitée par l' exec()appel système.
Thomas Nyman

Euh, ouais je suis en effet familier avec l'API Java que vous venez de mentionner, je me demande s'il existe un moyen d'invoquer l'appel de niveau inférieur exec () de Node.js d'une manière ou d'une autre
Alexander Mills

@AlexanderMills j'imagine child_process.{exec(),execFile(),spawn()} tout serait mis en œuvre en utilisant le C exec()(à travers process).
Thomas Nyman

10

Oui. En passant, ce n'est pas une question idiote. Une référence pour ma réponse est ici . Commencer un script avec #!

  • C'est ce qu'on appelle une ligne "shebang" ou "bang".

  • Ce n'est rien d'autre que le chemin absolu vers l'interprète Bash.

  • Il se compose d'un signe numérique et d'un caractère de point d'exclamation (#!), Suivi du chemin d'accès complet à l'interpréteur, tel que / bin / bash.

    Tous les scripts sous Linux sont exécutés à l'aide de l'interpréteur spécifié sur une première ligne. Presque tous les scripts bash commencent souvent par #! / Bin / bash (en supposant que Bash a été installé dans / bin). Ainsi, Bash sera utilisé pour interpréter le script, même s'il est exécuté sous un autre shell. Le shebang a été introduit par Dennis Ritchie entre la version 7 Unix et la version 8 des laboratoires Bell. Il a ensuite été ajouté à la ligne BSD à Berkeley.

Ignorer une ligne d'interprétation (shebang)

Si vous ne spécifiez pas de ligne d'interpréteur, la valeur par défaut est généralement / bin / sh. Cependant, il est recommandé de définir la ligne #! / Bin / bash.


3
Pour élaborer, le noyau sait seulement comment exécuter des binaires liés de manière statique et où trouver les informations d'interpréteur pour les autres (un champ spécial dans le binaire, ou la ligne shebang). L'exécution d'un script de shell signifie généralement de suivre la ligne shebang jusqu'au shell, puis de suivre le champ DT_INTERP du fichier binaire du shell vers l'éditeur de liens dynamique.
Simon Richter

5
Notez également que cela ne se limite pas aux scripts shell. Tous les fichiers de script basés sur du texte utilisent ceci. Par exemple, une #!/usr/bin/perl #!/usr/local/bin/python #!/usr/local/bin/rubyautre entrée shebang utilisée pour prendre en charge plusieurs systèmes consiste à utiliser env pour localiser l’interpréteur que vous souhaitez utiliser, comme#!/usr/bin/env perl #!/usr/bin/env python
sambler

@sambler en parlant de env, lequel devrait être réellement préféré? Python et Perl utilisent souvent env, alors que sur les scripts de shell, ceci est souvent omis et shebang pointe vers la coque en question.
polemon

1
@Polemon moins de ce qui est préféré et plus sur quels chemins varient. Les shells de base sont sur le même chemin sur tous les systèmes. Les versions les plus récentes de perl et de python peuvent être installées à des emplacements différents sur des systèmes différents. L'utilisation de env permet donc au même shebang de fonctionner en permanence. C'est pourquoi env est davantage utilisé avec les scripts perl et python que les scripts shell.
sambler

envtrouver un programme dans $ PATH est un peu un bidouillage. Il ne définit pas les variables d'environnement comme son nom l'indique. $ PATH peut être un résultat différent pour différents utilisateurs. Mais cela aide les scripts à fonctionner sans modification sur les systèmes qui placent un interpréteur perl raisonnable à un endroit étrange.
John Mahowald

4

L' execappel système du noyau Linux comprend shebangs ( #!) de manière native

Quand vous faites sur bash:

./something

sous Linux, cela appelle l' execappel système avec le chemin ./something.

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

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

Il lit les tout premiers octets du fichier et les compare à #!.

Si la comparaison est vraie, alors le reste de la ligne est analysé par le noyau Linux, qui effectue un autre execappel avec le chemin /usr/bin/env pythonet le fichier en cours comme premier argument:

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

et cela fonctionne pour tout langage de script utilisant #un 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

#! se trouve simplement lisible par l’homme, mais ce n’est pas nécessaire.

Si le fichier a démarré avec différents octets, l' execappel système utilisera un autre gestionnaire. L’autre gestionnaire intégré le plus important concerne les fichiers exécutables ELF: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305, qui vérifie la présence d’octets 7f 45 4c 46(qui se trouve également être humain). lisible pour .ELF). Confirmons cela en lisant les 4 premiers octets de /bin/ls, qui est un exécutable ELF:

head -c 4 "$(which ls)" | hd 

sortie:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

Ainsi, lorsque le noyau voit ces octets, il prend le fichier ELF, le met correctement en mémoire et lance un nouveau processus avec celui-ci. Voir aussi: https://stackoverflow.com/questions/8352535/how-does-kernel-get-an-executable-binary-file-running-under-linux/31394861#31394861

Enfin, vous pouvez ajouter vos propres gestionnaires Shebang avec le binfmt_miscmécanisme. Par exemple, vous pouvez ajouter un gestionnaire personnalisé pour les .jarfichiers . 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 .

Cependant, je ne pense pas que POSIX spécifie des shebangs: https://unix.stackexchange.com/a/346214/32558 , bien qu'il le mentionne dans les sections de justification, et sous la forme "si les scripts exécutables sont pris en charge par le système Pourrait arriver".


1
Exécuter à ./somethingpartir d'un shell ne passera pas par le chemin completexec , mais exactement par le chemin entré. Pouvez-vous corriger cela dans votre réponse? Faites echo "$0"dans votre script et vous verrez que c'est le cas.
AndiDog

2

En fait, si vous le prenez en conséquence, l'exécutable indiqué dans la ligne shebang n'est qu'un exécutable. Il est logique d’utiliser un interpréteur de texte comme exécutable, mais ce n’est pas nécessaire. Juste pour clarification et démonstration, j’ai fait un test plutôt inutile:

#!/bin/cat
useless text
more useless text
still more useless text

Nommé le fichier test.txt et définir le bit exectuable chmod u+x test.txt, puis « appelé » il: ./test.txt. Comme prévu, le contenu du fichier est sorti. Dans ce cas, le chat n'ignore pas la ligne shebang. Il affiche simplement toutes les lignes. Tout interprète utile devrait donc pouvoir ignorer cette ligne shebang. Pour bash, perl et PHP, il s’agit simplement d’une ligne de commentaire. Alors oui, ceux-ci ignorent la ligne shebang.


-1

D'après ce que j'ai pu comprendre, chaque fois qu'un fichier contenant un bit exécutable est appelé et appelé, le noyau analyse l'en-tête du fichier afin de déterminer la marche à suivre (pour autant que je sache, vous pouvez ajouter des gestionnaires personnalisés pour les formats de fichier personnalisés via LKM). Si le fichier semble être un fichier texte avec un #! combinaison au début, son exécution est envoyée à un autre exécutable (généralement un shell), chemin vers lequel doit être spécifié directement après ledit shebang, dans la même ligne. Le noyau exécute ensuite le shell et passe le fichier à gérer.

En bref, peu importe le shell avec lequel vous appelez le script - le noyau distribuera l’exécution de la manière appropriée.


4
Il y a une différence marquée entre bash ./myscript.shet ./myscript.sh.
un CVn

Qu'entendez-vous par cette "différence marquée"?
jrara

3
@jrara Voir ma réponse, l'affirmation selon laquelle "peu importe le shell avec lequel vous appelez le script" n'est tout simplement pas vraie.
Thomas Nyman
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.