Mehrere Argumente in Shebang

32

Ich frage mich, ob es eine allgemeine Möglichkeit gibt, mehrere Optionen über die shebang-Zeile ( #!) an eine ausführbare Datei zu übergeben .

Ich benutze NixOS und der erste Teil des Shebangs in jedem Skript, das ich schreibe, ist normalerweise /usr/bin/env. Das Problem, auf das ich stoße, ist, dass alles, was danach kommt, vom System als einzelne Datei oder ein einzelnes Verzeichnis interpretiert wird.

Angenommen, ich möchte ein Skript schreiben, das bashim posix-Modus ausgeführt wird . Die naive Art, den Shebang zu schreiben, wäre:

#!/usr/bin/env bash --posix

Der Versuch, das resultierende Skript auszuführen, führt jedoch zu folgendem Fehler:

/usr/bin/env: ‘bash --posix’: No such file or directory

Mir ist dieser Beitrag bekannt , aber ich habe mich gefragt, ob es eine allgemeinere und sauberere Lösung gibt.


EDIT : Ich weiß, dass es für Guile- Skripte einen Weg gibt, um das zu erreichen, was ich will, dokumentiert in Abschnitt 4.3.4 des Handbuchs:

 #!/usr/bin/env sh
 exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
 !#

Der Trick dabei ist, dass die zweite Zeile (beginnend mit exec) als Code interpretiert wird, shaber im Block #!... !#als Kommentar enthalten ist und daher vom Guile-Interpreter ignoriert wird.

Wäre es nicht möglich, diese Methode auf einen Interpreter zu verallgemeinern?


Second EDIT : Nach einigem Herumspielen scheint für Interpreten, die ihre Eingaben lesen können stdin, die folgende Methode zu funktionieren:

#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;

Es ist jedoch wahrscheinlich nicht optimal, da der shProzess so lange dauert, bis der Dolmetscher seine Arbeit beendet hat. Jede Rückmeldung oder Anregung wäre willkommen.

Rastapopoulos
quelle

Antworten:

27

Es gibt keine allgemeine Lösung, zumindest nicht, wenn Sie Linux unterstützen müssen, da der Linux-Kernel alles, was auf das erste „Wort“ in der shebang-Zeile folgt, als ein einziges Argument behandelt .

Ich bin mir nicht sicher, welche Einschränkungen NixOS hat, aber normalerweise schreibe ich einfach Ihren shebang als

#!/bin/bash --posix

oder, wo möglich, Optionen im Skript festlegen :

set -o posix

Alternativ können Sie das Skript mit dem entsprechenden Shell-Aufruf neu starten lassen:

#!/bin/sh -

if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi

shift

# Processing continues

Dieser Ansatz kann auf andere Sprachen verallgemeinert werden, sofern Sie einen Weg finden, die ersten Zeilen (die von der Shell interpretiert werden) von der Zielsprache zu ignorieren.

GNU coreutils' envbietet eine Abhilfe seit Version 8.30 finden Sie unode ‚s Antwort für Details. (Dies ist in Debian 10 und höher, RHEL 8 und höher, Ubuntu 19.04 und höher usw. verfügbar.)

Stephen Kitt
quelle
18

Obwohl nicht gerade portabel, ab Coreutils 8.30 und entsprechend der Dokumentation verwenden:

#!/usr/bin/env -S command arg1 arg2 ...

So gegeben:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

Sie erhalten:

% ./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'

und falls Sie neugierig sind, showargsist:

#!/usr/bin/env sh
echo "\$0 is '$0'"

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done
unode
quelle
Dies ist sehr gut zu wissen, um später darauf zurückgreifen zu können.
John McGehee
Diese Option wurde von FreeBSD übernommen, envwo sie -S2005 hinzugefügt wurde. Siehe lists.gnu.org/r/coreutils/2018-04/msg00011.html
Stéphane Chazelas
Funktioniert eine Belohnung auf Fedora 29
Eric
@unode einige Verbesserungen von showargs: pastebin.com/q9m6xr8H und pastebin.com/gS8AQ5WA ( Einzeiler )
Eric
Zu Ihrer Information: Ab Coreutils 8.31 gibt es envein eigenes showargs: die Option -v, z. B.#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy
9

Der POSIX-Standard beschreibt sehr knapp #!:

Aus dem Abschnitt Begründung der Dokumentation der exec()Familie der Systemschnittstellen :

Eine andere Möglichkeit, wie einige historische Implementierungen mit Shell-Skripten umgehen, besteht darin, die ersten beiden Bytes der Datei als Zeichenfolge zu erkennen #!und den Rest der ersten Zeile der Datei als Namen des auszuführenden Befehlsinterpreters zu verwenden.

Aus dem Shell-Einführungsabschnitt :

Die Shell liest ihre Eingabe aus einer Datei (siehe sh), aus der -cOption oder aus den system()und popen()Funktionen, die im Volume System Interfaces von POSIX.1-2008 definiert sind. Wenn die erste Zeile einer Datei mit Shell-Befehlen mit den Zeichen beginnt #!, werden die Ergebnisse nicht angegeben .

Dies bedeutet im Grunde, dass jede Implementierung (das von Ihnen verwendete Unix) die Spezifika des Parsens der Shebang-Linie nach Belieben ausführen kann.

Einige Unices wie macOS (können ATM nicht testen) teilen die Argumente, die dem Interpreter in der Shebang-Zeile übergeben werden, in separate Argumente auf, während Linux und die meisten anderen Unices dem Interpreter die Argumente als einzelne Option geben.

Es ist daher unklug, sich darauf zu verlassen, dass die Shebang-Linie mehr als ein einziges Argument vertritt.

Siehe auch den Abschnitt Portabilität des Shebang-Artikels auf Wikipedia .


Eine einfache Lösung, die für jedes Dienstprogramm oder jede Sprache verallgemeinerbar ist, besteht darin, ein Wrapper-Skript zu erstellen, das das eigentliche Skript mit den entsprechenden Befehlszeilenargumenten ausführt:

#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"

Ich glaube nicht, dass ich persönlich versuchen würde, es selbst wieder ausführen zu lassen , da sich das etwas fragil anfühlt.

Kusalananda
quelle
7

Der Shebang ist in execve(2) Manpage wie folgt beschrieben:

#! interpreter [optional-arg]

In dieser Syntax werden zwei Leerzeichen akzeptiert:

  1. Ein Leerzeichen vor dem Interpreterpfad , dieses Leerzeichen ist jedoch optional.
  2. Ein Leerzeichen zwischen dem Interpreterpfad und dem optionalen Argument.

Beachten Sie, dass ich bei der Angabe eines optionalen Arguments weder den Plural noch die oben angegebene Syntax verwendet habe [optional-arg ...], da Sie höchstens ein einziges Argument angeben können .

Für das Shell-Scripting können Sie das verwenden set integrierten Befehl am Anfang Ihres Skripts verwenden, mit dem Sie Interpreter-Parameter festlegen können. Dies führt zu demselben Ergebnis wie bei Verwendung von Befehlszeilenargumenten.

In Ihrem Fall:

set -o posix

Überprüfen Sie an einer Bash-Eingabeaufforderung die Ausgabe von help set, um alle verfügbaren Optionen abzurufen.

WhiteWinterWolf
quelle
1
Sie dürfen mehr als zwei Leerzeichen verwenden. Sie werden lediglich als Teil des optionalen Arguments betrachtet.
Stephen Kitt
@StephenKitt: In der Tat ist der Leerraum hier mehr als eine Kategorie zu verstehen als das eigentliche Leerzeichen. Ich nehme an, dass auch andere Leerzeichen wie Tabulatoren allgemein akzeptiert werden sollten.
WhiteWinterWolf
3

Unter Linux ist der Shebang nicht sehr flexibel. Laut mehreren Antworten ( Stephen Kitts Antwort und Jörg W. Mittag ) gibt es keine Möglichkeit, mehrere Argumente in einer Shebang-Zeile zu übergeben.

Ich bin nicht sicher, ob es für irgendjemanden von Nutzen sein wird, aber ich habe ein kurzes Skript geschrieben, um das fehlende Feature zu implementieren. Siehe https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa .

Es ist auch möglich, eingebettete Problemumgehungen zu schreiben. Im Folgenden stelle ich vier sprachunabhängige Problemumgehungen vor, die auf dasselbe Testskript angewendet wurden, und das Ergebnis wird jeweils gedruckt. Ich nehme an, dass das Skript ausführbar ist und sich in befindet /tmp/shebang.


Umhüllen Ihres Skripts mit einem Bash-Heredoc innerhalb der Prozessersetzung

Soweit ich weiß, ist dies der zuverlässigste sprachunabhängige Weg, dies zu tun. Es erlaubt das Übergeben von Argumenten und erhält die Standardeinstellung. Der Nachteil ist, dass der Interpreter den (realen) Speicherort der gelesenen Datei nicht kennt.

#!/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
) "$@"

Der Aufruf echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'druckt:

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

Beachten Sie, dass die Prozessersetzung eine spezielle Datei erzeugt. Dies ist möglicherweise nicht für alle ausführbaren Dateien geeignet. Zum Beispiel #!/usr/bin/lessbeschwert sich:/dev/fd/63 is not a regular file (use -f to see it)

Ich weiß nicht, ob es möglich ist, Heredoc innerhalb der Prozessersetzung im Bindestrich zu haben.


Packen Sie Ihr Skript in einen einfachen Heredoc

Kürzer und einfacher, aber Sie können nicht über stdinIhr Skript darauf zugreifen, und der Interpreter muss in der Lage sein, ein Skript zu lesen und auszuführen 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

Der Aufruf echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'druckt:

PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0]   :: -
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END

Benutze awk system()call aber ohne Argumente

Übergibt den Namen der ausgeführten Datei korrekt, aber Ihr Skript empfängt nicht die Argumente, die Sie ihm geben. Beachten Sie, dass awk die einzige mir bekannte Sprache ist, deren Interpreter standardmäßig unter Linux installiert ist und deren Anweisungen standardmäßig über die Befehlszeile gelesen werden.

#!/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")

Der Aufruf echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'druckt:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: []
__debug__ :: False
PYTHON_SCRIPT_END

Verwenden Sie den system()Aufruf awk 4.1+ , sofern Ihre Argumente keine Leerzeichen enthalten

Schön, aber nur, wenn Sie sicher sind, dass Ihr Skript nicht mit Argumenten aufgerufen wird, die Leerzeichen enthalten. Wie Sie sehen, werden Ihre Argumente, die Leerzeichen enthalten, geteilt, es sei denn, die Leerzeichen werden maskiert.

#!/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")

Der Aufruf echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'druckt:

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

Für awk-Versionen unter 4.1 müssen Sie die Zeichenfolgenverkettung in einer for-Schleife verwenden (siehe Beispielfunktion https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html) .

loxaxs
quelle
1
Zitieren Sie das hier angegebene Dokumentabschlusszeichen, um Folgendes zu verhindern $variableoder zu `command`ersetzen:exec python3 -O <(cat <<'EOWRAPPER'
John McGehee,
2

Ein Trick für LD_LIBRARY_PATHPython in der #!(shebang) -Linie, der von nichts anderem als der Shell abhängt und sich lohnt:

#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''

__doc__ = 'A great module docstring'

Wie an anderer Stelle auf dieser Seite erklärt, mögen einige Muscheln sh können ein Skript für ihre Standardeingabe verwenden.

Das Skript, das wir geben, shversucht, den Befehl auszuführen, der ''''durch ''(die leere Zeichenfolge) vereinfacht wird, shund führt ihn natürlich nicht aus, da es keinen ''Befehl gibt. Daher wird er normalerweise line 2: command not foundauf dem Standard-Fehlerdeskriptor ausgegeben, aber wir leiten diese Nachricht mit 2>/dev/nullan um Schwarzes Loch am nächsten, weil es für den Benutzer unübersichtlich und verwirrend wäre, es sich shanzeigen zu lassen .

Wir gehen dann zu dem für uns interessanten Befehl über exec, der in unserem Fall den aktuellen Shell-Prozess durch Folgendes ersetzt: /usr/bin/env pythonmit den entsprechenden Parametern:

  • "$0" um python wissen zu lassen, welches script es öffnen und interpretieren soll, und um auch zu setzen sys.argv[0]
  • "$@"Pythons sys.argv[1:]auf die in der Skriptbefehlszeile übergebenen Argumente setzen.

Und wir bitten Sie auch env, die LD_LIBRARY_PATHUmgebungsvariable festzulegen, die der einzige Punkt des Hacks ist.

Der Shell-Befehl endet mit dem Kommentar, der mit beginnt, #sodass die Shell die nachstehenden dreifachen Anführungszeichen ignoriert '''.

sh wird dann durch eine shinny neue Instanz des Python - Interpreters ersetzt, die das als erstes Argument angegebene Python - Quellenskript öffnet und liest (das "$0" ) .

Python öffnet die Datei und überspringt dank des -xArguments die erste Zeile der Quelle . Hinweis: Es funktioniert auch ohne, -xda für Python ein Shebang nur ein Kommentar ist .

Python interpretiert dann die 2. Zeile als Docstring für die aktuelle Moduldatei. Wenn Sie also einen gültigen Modul-Docstring benötigen, legen Sie einfach als __doc__Erstes in Ihrem Python-Programm fest, wie im obigen Beispiel.

Eric
quelle
Angesichts der Tatsache, dass eine leere Zeichenfolge ... ähm ... leer ist, sollten Sie in der Lage sein, Ihren Befehl zu ''''exec ...verwerfen. Beachten Sie, dass vor exec kein Leerzeichen steht, da sonst nach dem leeren Befehl gesucht wird. Sie möchten das Leere auf das erste Argument spleißen, damit das so $0ist exec.
Caleb
1

Als ich nach einer ausführbaren Datei suchte, die ein Skript als einziges Argument ausnimmt, fand ich eine ziemlich dumme Problemumgehung:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
bis
quelle