Je me demande s'il existe un moyen général de passer plusieurs options à un exécutable via la ligne shebang ( #!
).
J'utilise NixOS, et la première partie du shebang dans n'importe quel script que j'écris est généralement /usr/bin/env
. Le problème que je rencontre alors est que tout ce qui vient après est interprété comme un seul fichier ou répertoire par le système.
Supposons, par exemple, que je veuille écrire un script à exécuter bash
en mode posix. La manière naïve d'écrire le shebang serait:
#!/usr/bin/env bash --posix
mais essayer d'exécuter le script résultant produit l'erreur suivante:
/usr/bin/env: ‘bash --posix’: No such file or directory
Je connais ce post , mais je me demandais s'il y avait une solution plus générale et plus propre.
EDIT : Je sais que pour les scripts Guile , il existe un moyen d'atteindre ce que je veux, documenté dans la section 4.3.4 du manuel:
#!/usr/bin/env sh
exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
!#
L'astuce, ici, est que la deuxième ligne (commençant par exec
) est interprétée comme du code par sh
mais, étant dans le bloc #!
... !#
, comme un commentaire, et donc ignorée, par l'interpréteur Guile.
Ne serait-il pas possible de généraliser cette méthode à n'importe quel interprète?
Second EDIT : Après avoir joué un peu, il semble que, pour les interprètes qui peuvent lire leur entrée stdin
, la méthode suivante fonctionnerait:
#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;
Ce n'est probablement pas optimal, cependant, car le sh
processus se poursuit jusqu'à ce que l'interprète ait terminé son travail. Toute rétroaction ou suggestion serait appréciée.
Réponses:
Il n'y a pas de solution générale, du moins pas si vous devez prendre en charge Linux, car le noyau Linux traite tout ce qui suit le premier «mot» de la ligne shebang comme un seul argument .
Je ne sais pas quelles sont les contraintes de NixOS, mais en général, j'écrirais simplement votre shebang comme
ou, si possible, définissez les options dans le script :
Alternativement, vous pouvez faire redémarrer le script lui-même avec l'invocation de shell appropriée:
Cette approche peut être généralisée à d'autres langues, à condition que vous trouviez un moyen pour les deux premières lignes (qui sont interprétées par le shell) d'être ignorées par la langue cible.
GNU
coreutils
'env
fournit une solution de contournement depuis la version 8.30, voir la réponse de unode pour plus de détails. (Ceci est disponible dans Debian 10 et versions ultérieures, RHEL 8 et versions ultérieures, Ubuntu 19.04 et versions ultérieures, etc.)la source
Bien que pas exactement portable, à partir de coreutils 8.30 et selon sa documentation, vous pourrez utiliser:
Donc donné:
tu auras:
et si vous êtes curieux,
showargs
c'est:la source
env
où a-S
été ajoutée en 2005. Voir lists.gnu.org/r/coreutils/2018-04/msg00011.htmlshowargs
: pastebin.com/q9m6xr8H et pastebin.com/gS8AQ5WA (one-liner)env
inclut la sienneshowargs
: l'option -v par exemple#!/usr/bin/env -vS --option1 --option2 ...
La norme POSIX est très concise pour décrire
#!
:De la section justification de la documentation de la
exec()
famille des interfaces système :Dans la section Introduction au shell :
Cela signifie essentiellement que toute implémentation (l'Unix que vous utilisez) est libre de faire les spécificités de l'analyse de la ligne shebang comme elle le souhaite.
Certains Unices, comme macOS (ne peut pas tester ATM), diviseront les arguments donnés à l'interpréteur sur la ligne shebang en arguments séparés, tandis que Linux et la plupart des autres Unices donneront les arguments comme une seule option à l'interpréteur.
Il n'est donc pas judicieux de compter sur la ligne de shebang pour pouvoir prendre plus d'un seul argument.
Voir également la section Portabilité de l'article de Shebang sur Wikipédia .
Une solution simple, généralisable à tout utilitaire ou langage, consiste à créer un script wrapper qui exécute le script réel avec les arguments de ligne de commande appropriés:
Je ne pense pas que je voudrais essayer personnellement de le faire re-exécuter lui - même comme cela se sent un peu fragile.
la source
Le shebang est décrit dans la page de manuel
execve
(2) comme suit:Deux espaces sont acceptés dans cette syntaxe:
Notez que je n'ai pas utilisé le pluriel en parlant d'un argument facultatif, la syntaxe ci-dessus non plus
[optional-arg ...]
, car vous pouvez fournir au plus un seul argument .En ce qui concerne les scripts shell, vous pouvez utiliser la
set
commande intégrée près du début de votre script qui permettra de définir les paramètres des interprètes, fournissant le même résultat que si vous utilisiez des arguments de ligne de commande.Dans ton cas:
À partir d'une invite Bash, vérifiez la sortie de
help set
pour obtenir toutes les options disponibles.la source
Sous Linux, le shebang n'est pas très flexible; selon plusieurs réponses (réponse de Stephen Kitt et Jörg W Mittag ), il n'y a pas de moyen désigné pour passer plusieurs arguments dans une ligne de shebang.
Je ne sais pas si cela sera utile à personne, mais j'ai écrit un court script pour implémenter la fonctionnalité manquante. Voir https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa .
Il est également possible d'écrire des solutions de contournement intégrées. Ci-dessous, je présente quatre solutions de contournement indépendantes de la langue appliquées au même script de test et le résultat de chaque impression. Je suppose que le script est exécutable et est dedans
/tmp/shebang
.Envelopper votre script dans un hérédoc bash à l'intérieur de la substitution de processus
Pour autant que je sache, c'est la façon la plus fiable de le faire indépendamment du langage. Il permet de passer des arguments et préserve stdin. L'inconvénient est que l'interpréteur ne connaît pas l'emplacement (réel) du fichier qu'il lit.
echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
Impressions d' appel :Notez que la substitution de processus produit un fichier spécial. Cela peut ne pas convenir à tous les exécutables. Par exemple, se
#!/usr/bin/less
plaint:/dev/fd/63 is not a regular file (use -f to see it)
Je ne sais pas s'il est possible d'avoir heredoc inside substitution de processus dans le tiret.
Envelopper votre script dans un hérédoc simple
Plus court et plus simple, mais vous ne pourrez pas accéder à
stdin
partir de votre script et cela nécessite que l'interpréteur puisse lire et exécuter un script à partir destdin
.echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
Impressions d' appel :Utiliser un
system()
appel awk mais sans argumentsTransmet correctement le nom du fichier exécuté, mais votre script ne recevra pas les arguments que vous lui donnez. Notez que awk est la seule langue que je connaisse dont l'interprète est installé sur linux par défaut et lit ses instructions depuis la ligne de commande par défaut.
echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
Impressions d' appel :Utilisez l'
system()
appel awk 4.1+ , à condition que vos arguments ne contiennent pas d'espacesBien, mais seulement si vous êtes sûr que votre script ne sera pas appelé avec des arguments contenant des espaces. Comme vous pouvez le voir, vos arguments contenant des espaces seraient divisés, à moins que les espaces ne soient échappés.
echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
Impressions d' appel :Pour les versions awk inférieures à 4.1, vous devrez utiliser la concaténation de chaînes à l'intérieur d'une boucle for, voir l'exemple de fonction https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .
la source
$variable
ou`command`
remplacer:exec python3 -O <(cat <<'EOWRAPPER'
Une astuce à utiliser
LD_LIBRARY_PATH
avec python sur la ligne#!
(shebang) qui ne dépend pas d'autre chose que du shell et qui fonctionne comme un régal:Comme expliqué ailleurs dans cette page, certains shells comme
sh
peuvent prendre un script sur leur entrée standard.Le script que nous donnons
sh
essaie d'exécuter la commande''''
qui est simplifiée en''
(la chaîne vide) parsh
et bien sûr il ne parvient pas à l'exécuter car il n'y a pas de''
commande, donc il sort normalementline 2: command not found
sur le descripteur d'erreur standard mais nous redirigeons ce message en utilisant2>/dev/null
le trou noir le plus proche car ce serait désordonné et déroutant pour l'utilisateur de le laissersh
afficher.Nous passons ensuite à la commande qui nous intéresse:
exec
qui remplace le processus shell actuel par ce qui suit, dans notre cas:/usr/bin/env python
avec les paramètres adéquats:"$0"
pour faire savoir à python quel script il doit ouvrir et interpréter, et aussi définirsys.argv[0]
"$@"
pour définir pythonsys.argv[1:]
aux arguments passés sur la ligne de commande de script.Et nous demandons également
env
de définir laLD_LIBRARY_PATH
variable d'environnement, qui est le seul point du hack.La commande shell se termine au commentaire commençant par
#
afin que le shell ignore les guillemets de fin'''
.sh
est ensuite remplacé par une nouvelle instance brillante de l'interpréteur python qui ouvre et lit le script source python donné comme premier argument (le"$0"
).Python ouvre le fichier et saute la 1ère ligne de la source grâce à l'
-x
argument. Remarque: cela fonctionne également sans-x
car pour Python un shebang n'est qu'un commentaire .Python interprète ensuite la 2e ligne comme la docstring pour le fichier de module actuel, donc si vous avez besoin d'une docstring de module valide, définissez simplement la
__doc__
première chose dans votre programme python comme dans l'exemple ci-dessus.la source
''''exec ...
devrait faire le travail. Notez qu'il n'y a pas d'espace avant exec ou cela fera chercher la commande vide. Vous voulez épisser le vide sur le premier argument pour qu'il en$0
soit ainsiexec
.J'ai trouvé une solution plutôt stupide en cherchant un exécutable qui exclut un script comme argument unique:
la source