Comment fonctionne ce shebang qui commence par un double trait d'union (-)?
14
J'ai trouvé le type de shebang suivant sur la page RosettaCode:
--(){:;}; exec db2 -txf "$0"
Cela fonctionne pour Db2, et une chose similaire pour Postgres. Cependant, je ne comprends pas toute la ligne.
Je sais que le double tiret est un commentaire en SQL, et après cela, il appelle l'exécutable Db2 avec certains paramètres en passant le fichier lui-même comme fichier. Mais qu'en est-il des parenthèses, des nacelles frisées, du côlon et du point-virgule, et comment remplacer un vrai shebang #! ?
Le script n'a pas de ligne shebang / hashbang / #!, simplement parce qu'un double tiret ne l'est pas #!.
Cependant, le script sera exécuté par un shell (voir les questions et réponses liées ci-dessus), et dans ce shell, si -est un caractère valide dans un nom de fonction, la ligne déclare une fonction shell appelée --qui ne fait rien (enfin, elle s'exécute :, qui ne fait rien ) et qui n'est jamais appelé.
La fonction, dans la notation multi-lignes la plus courante (juste pour rendre plus évidente à quoi elle ressemble, car son nom étrange masque un peu le fait qu'il s'agit en fait d'une fonction):
--(){:}
Le seul but de la définition de fonction est d'avoir une ligne valide dans un script shell et en même temps une commande SQL valide (un commentaire). Ce type de code est appelé polyglotte .
Après avoir déclaré la fausse fonction de shell, le script, lorsqu'il est exécuté par un interpréteur de script de shell, utilise execpour remplacer le shell actuel par le processus résultant de l'exécution db2 -txf "$0", ce qui reviendrait à utiliser db2 -txfle nom de chemin du script à partir de la ligne de commande.
Cette astuce ne fonctionnerait probablement pas de manière fiable sur des systèmes où dashou d'autres ashshell basés sur yash, le shell Bourne, ksh88ou ksh93est utilisé comme /bin/sh, car ces shell n'acceptent pas les fonctions dont le nom contient des tirets.
Oh oui! Et merci de me rappeler comment on appelle ce genre de chose. :)
ilkkachu
6
Comme @Kusalananda l'a déjà dit, cette astuce est cassée et ne fonctionnera pas dans tous les shells.
Voici ma façon de le faire de manière portable:
--/..2>/dev/null; exec db2 -txf "$0"
La première commande devrait échouer même si un fichier / répertoire nommé --existe dans le répertoire courant et toutes les erreurs seront fermées par le 2>/dev/null; le shell va ensuite passer à la deuxième commande, le exec.
Ce n'est toujours pas vraiment portable. Ce n'est pas un script valide et vous comptez toujours sur le shell appelant pour contourner le fait que le noyau refusera d'exécuter le script et retournera ENOEXECsi vous essayez. Essayez d'exécuter le script sous stracepour voir ce que je veux dire.
kasperd
@kasperd, il devrait toujours être portable, le shell est censé exécuter le script en tant que script shell s'il exec()ne fonctionne pas dessus. "Si la fonction execl () échoue en raison d'une erreur équivalente à l'erreur [ENOEXEC], le shell doit exécuter une commande équivalente à avoir un shell appelé avec le nom de la commande comme premier opérande, ..." (voir pubs.opengroup .org / onlinepubs / 9699919799.2018edition / utilities /… )
ilkkachu
@ilkkachu Mais les scripts ne sont pas toujours exécutés à partir d'un shell. Si vous essayez d'utiliser le script dans un autre contexte où un exécutable fonctionnerait, il échouera. De plus, les shells ne s'entendent pas sur l'interprète à utiliser. Votre script se comportera donc différemment ou échouera complètement selon le contexte à partir duquel il est appelé.
kasperd
@kasperd, eh bien, bien sûr, cela ne fonctionnera pas si vous exec()le faites directement à partir d'autre chose qu'un shell. Mais quel serait ce cas? Vous voudrez peut-être exécuter le script à partir de cronou tel, mais je pense qu'il exécute tout à travers un shell de toute façon, et même si ce n'est pas le cas, il est facile de le préciser db2 -txf /path/to/scriptdans ce cas, car vous ne devez le faire qu'une seule fois. Le raccourci est surtout utile sur un shell interactif. Mais bien sûr, un script wrapper séparé pourrait être plus robuste.
ilkkachu
1
@kasperd Je ne vous ennuierai pas avec les documents et les normes; essayez-le! echo 'int main(int c,char**a){execvp(a[1],a+1);}' | cc -include unistd.h -xc -; echo echo yeah > a.sh; chmod 755 a.sh; ./a.out ./a.sh; PATH=`pwd` ./a.out a.sh
Comme @Kusalananda l'a déjà dit, cette astuce est cassée et ne fonctionnera pas dans tous les shells.
Voici ma façon de le faire de manière portable:
La première commande devrait échouer même si un fichier / répertoire nommé
--
existe dans le répertoire courant et toutes les erreurs seront fermées par le2>/dev/null
; le shell va ensuite passer à la deuxième commande, leexec
.la source
ENOEXEC
si vous essayez. Essayez d'exécuter le script sousstrace
pour voir ce que je veux dire.exec()
ne fonctionne pas dessus. "Si la fonction execl () échoue en raison d'une erreur équivalente à l'erreur [ENOEXEC], le shell doit exécuter une commande équivalente à avoir un shell appelé avec le nom de la commande comme premier opérande, ..." (voir pubs.opengroup .org / onlinepubs / 9699919799.2018edition / utilities /… )exec()
le faites directement à partir d'autre chose qu'un shell. Mais quel serait ce cas? Vous voudrez peut-être exécuter le script à partir decron
ou tel, mais je pense qu'il exécute tout à travers un shell de toute façon, et même si ce n'est pas le cas, il est facile de le préciserdb2 -txf /path/to/script
dans ce cas, car vous ne devez le faire qu'une seule fois. Le raccourci est surtout utile sur un shell interactif. Mais bien sûr, un script wrapper séparé pourrait être plus robuste.echo 'int main(int c,char**a){execvp(a[1],a+1);}' | cc -include unistd.h -xc -; echo echo yeah > a.sh; chmod 755 a.sh; ./a.out ./a.sh; PATH=`pwd` ./a.out a.sh