web-dev-qa-db-fra.com

Arguments multiples dans shebang

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.

41
Rastapopoulos

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 coreutilsenv 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.)

29
Stephen Kitt

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
26
unode

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 fonctions system() et popen() 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.

10
Kusalananda

Le Shebang est décrit dans la page man execve (2) comme suit:

#! interpreter [optional-arg]

Deux espaces sont acceptés dans cette syntaxe:

  1. Un espace avant le chemin de l'interpréteur , mais cet espace est facultatif.
  2. Un espace séparant le chemin de l'interpréteur et son argument facultatif.

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.

7
WhiteWinterWolf

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.


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 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.


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 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

Utilisez l'appel awk system() mais sans arguments

Transmet 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

Utilisez l'appel awk 4.1+ system(), à condition que vos arguments ne contiennent pas d'espaces

Bien, 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 =.

3
loxaxs

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])}
2
till

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.

2
Eric