Exécution de script Bash avec et sans shebang sous Linux et BSD

Comment et qui détermine ce qui s’exécute quand un script de type Bash est exécuté en binary sans shebang?

Je suppose que l’exécution d’un script normal avec shebang est gérée avec le module Linux binfmt_script , qui vérifie un shebang, parsing la ligne de commande et exécute un interpréteur de script désigné.

Mais que se passe-t-il quand quelqu’un exécute un script sans shebang? J’ai testé l’approche directe execv et découvert qu’il n’y avait pas de magie du kernel – c’est-à-dire un fichier comme celui-ci:

 $ cat target-script echo Hello echo "bash: $BASH_VERSION" echo "zsh: $ZSH_VERSION" 

En cours d’exécution du programme C compilé qui ne fait qu’un appel execv obtient:

 $ cat test-runner.c
 annulez main () {
         if (execv ("./ target-script", 0) == -1)
                 perror ();
 }
 $ ./test-runner
 ./target-script: erreur de format Exec

Cependant, si je fais la même chose avec un autre script shell, il exécute le script cible en utilisant le même interpréteur de shell que celui d’origine:

 $ cat test-runner.bash
 #! / bin / bash
 ./target-script

 $ ./test-runner.bash 
 Bonjour
 bash: 4.1.0 (1) -release
 zsh: 

Si je fais le même tour avec d’autres shells (par exemple, sh/bin/dash ) de Debian, cela fonctionne aussi:

 $ cat test-runner.dash   
 #! / bin / dash
 ./target-script

 $ ./test-runner.dash 
 Bonjour
 bash: 
 zsh: 

Mystérieusement, il ne fonctionne pas comme prévu avec zsh et ne suit pas le schéma général. On dirait que zsh a exécuté /bin/sh sur de tels fichiers après tout:

 greycat @ burrow-debian ~ / z / test-runner $ cat test-runner.zsh 
 #! / bin / zsh
 echo ZSH_VERSION = $ ZSH_VERSION
 ./target-script

 greycat @ burrow-debian ~ / z / test-runner $ ./test-runner.zsh 
 ZSH_VERSION = 4.3.10
 Bonjour
 bash: 
 zsh: 

Notez que ZSH_VERSION dans le script parent a fonctionné, alors que ZSH_VERSION dans child n’a pas fonctionné!

Comment un shell (Bash, Dash) détermine-t-il ce qui est exécuté quand il n’y a pas de shebang? J’ai essayé de trouver cet endroit dans les sources de Bash / Dash, mais, hélas, on dirait que je suis un peu perdu. Quelqu’un peut-il éclairer la magie qui détermine si le fichier cible sans shebang doit être exécuté en tant que script ou en tant que binary dans Bash / Dash? Ou peut-être existe t il une sorte d’interaction avec le kernel / libc et je serais ravi des explications sur son fonctionnement dans les kernelx / libcs ​​Linux et FreeBSD?

Étant donné que cela se produit dans le tableau de bord et que Dash est plus simple, j’ai regardé en premier.

Il semble que exec.c est l’endroit à rechercher, et les fonctions appropriées sont tryexec , qui est appelée depuis shellexec qui est appelée chaque fois que le shell nécessite l’exécution d’une commande. Et (une version simplifiée de) la fonction tryexec est la suivante:

 STATIC void tryexec(char *cmd, char **argv, char **envp) { char *const path_bshell = _PATH_BSHELL; repeat: execve(cmd, argv, envp); if (cmd != path_bshell && errno == ENOEXEC) { *argv-- = cmd; *argv = cmd = path_bshell; goto repeat; } } 

Donc, il remplace toujours simplement la commande à exécuter par le chemin lui-même ( _PATH_BSHELL défaut à "/bin/sh" ) si ENOEXEC se produit. Il n’y a vraiment pas de magie ici.

Je trouve que FreeBSD présente un comportement identique en bash et dans son propre sh .

La façon dont bash traite cela est similaire mais beaucoup plus compliquée. Si vous voulez aller plus loin, je vous recommande de lire la commande execute_command.c de bash et de regarder en particulier execute_shell_script , puis shell_execve . Les commentaires sont assez descriptifs.

(On dirait que Sorpigal l’a couvert mais je l’ai déjà tapé et ça peut être intéressant.)

Selon la section 3.16 de la FAQ Unix , le shell examine d’abord le nombre magique (deux premiers octets du fichier). Certains nombres indiquent un exécutable binary; #! indique que le rest de la ligne doit être interprété comme un shebang. Sinon, le shell tente de l’exécuter en tant que script shell.

De plus, il semble que csh regarde le premier octet, et s’il est # , il essaiera de l’exécuter en tant que script csh .