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 tout 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 par 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 suis au courant de ce post , mais je me demandais s'il y avait une solution plus générale et plus propre.
[~ # ~] modifier [~ # ~] : Je sais que pour les scripts Guile , il existe un moyen d'atteindre ce que je veux, documenté dans 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 #!
... !#
bloquer, en tant que commentaire, et donc ignoré, par l'interpréteur Guile.
Ne serait-il pas possible de généraliser cette méthode à n'importe quel interprète?
Deuxième EDIT : Après avoir joué un peu, il semble que, pour les interprètes qui peuvent lire leur entrée depuis stdin
, la méthode suivante travaillerait:
#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;
Ce n'est probablement pas optimal, cependant, car le processus sh
dure jusqu'à ce que l'interprète ait terminé son travail. Toute rétroaction ou suggestion serait appréciée.
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 "Word" dans la ligne Shebang comme un seul argument .
Je ne sais pas quelles sont les contraintes de NixOS, mais généralement j'écrirais simplement votre Shebang comme
#!/bin/bash --posix
ou, si possible, définir les options dans le script :
set -o posix
Alternativement, vous pouvez faire redémarrer le script lui-même avec l'invocation Shell appropriée:
#!/bin/sh -
if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi
shift
# Processing continues
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 node ’ s answer 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.)
Bien qu'il ne soit pas exactement portable, à partir de coreutils 8.30 et selon sa documentation vous pourrez utiliser:
#!/usr/bin/env -S command arg1 arg2 ...
Donc donné:
$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too
tu auras:
% ./test.sh
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'
et au cas où vous seriez curieux showargs
c'est:
#!/usr/bin/env sh
echo "\$0 is '$0'"
i=1
for arg in "$@"; do
echo "\$$i is '$arg'"
i=$((i+1))
done
La norme POSIX est très concise sur la description de #!
:
De la section justification de la documentation de la famille d'interfaces système exec()
:
Certaines implémentations historiques gèrent les scripts Shell en reconnaissant les deux premiers octets du fichier comme la chaîne de caractères
#!
Et en utilisant le reste de la première ligne du fichier comme nom de l'interpréteur de commandes à exécuter.
De la section Introduction au shell :
Le Shell lit son entrée dans un fichier (voir
sh
), dans l'option-c
Ou dans les fonctionssystem()
etpopen()
définies dans le système Volume des interfaces de POSIX.1-2008. Si la première ligne d'un fichier de commandes Shell commence par les caractères#!
, Les résultats ne sont pas spécifiés .
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 Shebang pour pouvoir prendre plus d'un seul argument.
Voir aussi section Portabilité de l'article de Shebang sur Wikipedia .
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:
#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"
Je ne pense pas que j'essaierais personnellement de le faire ré-exécuter lui-même car cela semble un peu fragile.
Le Shebang est décrit dans la page man execve
(2) comme suit:
#! interpreter [optional-arg]
Deux espaces sont acceptés dans cette syntaxe:
Notez que je n'ai pas utilisé le pluriel en parlant d'un argument optionnel, la syntaxe ci-dessus n'utilise pas non plus [optional-arg ...]
, car vous pouvez fournir au plus un seul argument .
En ce qui concerne les scripts Shell, vous pouvez utiliser la commande intégrée set
vers le 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:
set -o posix
À partir d'une invite Bash, vérifiez la sortie de help set
pour obtenir toutes les options disponibles.
Sous Linux, le Shebang n'est pas très flexible; selon plusieurs réponses ( réponse de Stephen Kitt et réponse de Jörg W Mittag ), il n'y a pas de moyen désigné pour passer plusieurs arguments dans une ligne 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 du langage appliquées au même script de test et le résultat que chacun imprime. Je suppose que le script est exécutable et se trouve dans /tmp/Shebang
.
Pour autant que je sache, c'est la manière 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.
#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")
from sys import argv
try:
print("input() 0 ::", input())
print("input() 1 ::", input())
except EOFError:
print("input() caused EOFError")
print("argv[0] ::", argv[0])
print("argv[1:] ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False
print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"
L'appel de echo -e 'aa\nbb' | /tmp/Shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
Imprime:
PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0] :: /dev/fd/62
argv[1:] :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: False
PYTHON_SCRIPT_END
Notez que la substitution de processus produit un fichier spécial. Cela peut ne pas convenir à tous les exécutables. Par exemple, #!/usr/bin/less
Se 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.
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 de stdin
.
#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")
from sys import argv
try:
print("input() 0 ::", input())
print("input() 1 ::", input())
except EOFError:
print("input() caused EOFError")
print("argv[0] ::", argv[0])
print("argv[1:] ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False
print("PYTHON_SCRIPT_END")
EOWRAPPER
L'appel de echo -e 'aa\nbb' | /tmp/Shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
Imprime:
PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0] :: -
argv[1:] :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END
system()
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.
#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")
from sys import argv
print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0] ::", argv[0])
print("argv[1:] ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False
print("PYTHON_SCRIPT_END")
L'appel de echo -e 'aa\nbb' | /tmp/Shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
Imprime:
PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0] :: /tmp/Shebang
argv[1:] :: []
__debug__ :: False
PYTHON_SCRIPT_END
system()
, à 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.
#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")
from sys import argv
print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0] ::", argv[0])
print("argv[1:] ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False
print("PYTHON_SCRIPT_END")
L'appel de echo -e 'aa\nbb' | /tmp/Shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
Imprime:
PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0] :: /tmp/Shebang
argv[1:] :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END
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 =.
J'ai trouvé une solution plutôt stupide en cherchant un exécutable qui exclut un script comme argument unique:
#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
Une astuce pour utiliser LD_LIBRARY_PATH
Avec python sur la ligne #!
(Shebang) qui ne dépend pas d'autre chose que du Shell et fonctionne comme un régal:
#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''
__doc__ = 'A great module docstring'
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) par sh
et bien sûr il ne s'exécute pas comme il n'y a pas de commande ''
, donc il renvoie normalement line 2: command not found
sur le descripteur d'erreur standard mais nous redirigeons ce message en utilisant 2>/dev/null
vers le trou noir le plus proche car il serait désordonné et déroutant pour l'utilisateur de laisser sh
l'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 laisser python savoir quel script il doit ouvrir et interpréter, et également définir sys.argv[0]
"$@"
Pour définir le sys.argv[1:]
De python sur les arguments passés sur la ligne de commande du script.Et nous demandons également à env
de définir la variable d'environnement LD_LIBRARY_PATH
, Qui est le seul point du hack.
La commande Shell se termine au commentaire commençant par #
Afin que le shell ignore les triples guillemets de fin '''
.
sh
est alors 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'argument -x
. 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 du fichier de module actuel, donc si vous avez besoin d'une docstring de module valide, définissez simplement __doc__
En premier dans votre programme python comme dans le exemple ci-dessus.