Gibt es eine Möglichkeit, den Interpreter, der ein Skript ausführt, dynamisch auszuwählen? Ich habe ein Skript, das auf zwei verschiedenen Systemen ausgeführt wird, und der Interpreter, den ich verwenden möchte, befindet sich an verschiedenen Orten auf den beiden Systemen. Am Ende muss ich die Hashbang-Linie jedes Mal ändern, wenn ich umschalte. Ich möchte etwas tun, das dem logisch entspricht (mir ist klar, dass dieses genaue Konstrukt unmöglich ist):
if running on system A:
#!/path/to/python/on/systemA
elif running on system B:
#!/path/on/systemB
#Rest of script goes here
Oder noch besser wäre dies, damit es versucht, den ersten Interpreter zu verwenden, und wenn es den zweiten nicht findet:
try:
#!/path/to/python/on/systemA
except:
#!path/on/systemB
#Rest of script goes here
Offensichtlich kann ich stattdessen führen Sie es als
/path/to/python/on/systemA myscript.py
oder
/path/on/systemB myscript.py
je nachdem , wo ich bin, aber ich habe tatsächlich einen Wrapper - Skript , das gestartet wird myscript.py
, so würde Ich mag den Pfad zum Python - Interpreter spezifizieren programmatisch eher als von Hand.
if
Bedingung zu verwenden, ist für Sie keine Option? like,if something; then /bin/sh restofscript.sh elif...
Antworten:
Nein, das geht nicht. Die beiden Zeichen müssen
#!
unbedingt die ersten beiden Zeichen in der Datei sein (wie würden Sie angeben, was die if-Anweisung überhaupt interpretiert hat?). Dies ist die "magische Zahl", die dieexec()
Funktionsfamilie erkennt, wenn sie feststellt, ob es sich bei einer auszuführenden Datei um ein Skript (das einen Interpreter benötigt) oder eine Binärdatei handelt (was nicht der Fall ist).Das Format der Shebang-Linie ist ziemlich streng. Es muss einen absoluten Pfad zu einem Interpreten und höchstens ein Argument dafür haben.
Was Sie tun können , ist zu verwenden
env
:Nun ist der Weg
env
ist in der Regel/usr/bin/env
, aber technisch , dass keine Garantie ist.Dadurch können Sie das anpassen
PATH
Umgebungsvariable auf jedem System , so dassinterpreter
(es seinbash
,python
oderperl
oder was auch immer Sie haben) gefunden wird.Ein Nachteil dieses Ansatzes ist, dass es unmöglich ist, ein Argument portabel an den Interpreter weiterzuleiten.
Das bedeutet, dass
und
ist unwahrscheinlich, auf einigen Systemen zu arbeiten.
Ein anderer offensichtlicher Ansatz ist die Verwendung von GNU-Autotools (oder einem einfacheren Template-System), um den Interpreter zu finden und den korrekten Pfad in die Datei in einem
./configure
Schritt einzufügen , der bei der Installation des Skripts auf jedem System ausgeführt wird.Man könnte auch das Skript mit einem expliziten Interpreter ausführen, aber das ist offensichtlich das, was Sie vermeiden wollen:
quelle
#!
das am Anfang stehen muss, da es nicht die Shell ist, die diese Zeile verarbeitet. Ich habe mich gefragt, ob es eine Möglichkeit gibt, Logik in die Hashbang-Zeile einzufügen, die der Option if / else entspricht. Ich hatte auch gehofft, dassPATH
ich nicht mit meinem herumalbern sollte, aber ich denke, das sind meine einzigen Möglichkeiten.#!/usr/bin/awk
as verwenden , können Sie genau ein Argument angeben#!/usr/bin/awk -f
. Wenn die Binärdatei, auf die Sie verweisenenv
, das Argument ist, nach der Sieenv
suchen möchten, wie in#!/usr/bin/env awk
./usr/bin/env
mit dem einzigen Argumentawk -f
.foo.awk
mit der Hashbang-Zeile aufgerufen wurde,#!/usr/bin/env awk -f
und es./foo.awk
dann unter Linux aufrufen , werdenenv
die beiden Parameterawk -f
und angezeigt./foo.awk
. Es geht eigentlich darum,/usr/bin/awk -f
mit einem Leerzeichen (etc.) zu suchen .Sie können jederzeit ein Wrapper-Skript erstellen, um den richtigen Interpreter für das aktuelle Programm zu finden:
Speichern Sie den Wrapper
PATH
unterprogram
und legen Sie das eigentliche Programm beiseite oder tragen Sie einen anderen Namen ein.Ich habe
#!/bin/bash
im Hashbang wegen desflags
Arrays verwendet. Wenn Sie keine variable Anzahl von Flags oder dergleichen speichern müssen und darauf verzichten können, sollte das Skript portabel funktionieren#!/bin/sh
.quelle
exec "$interpreter" "${flags[@]}" "$script" "$@"
, wie man den Prozessbaum sauberer hält. Es gibt auch den Exit-Code weiter.exec
.#!/bin/sh
besser als#!/bin/bash
? Selbst wenn/bin/sh
es sich um einen Symlink zu einer anderen Shell handelt, sollte er auf den meisten (wenn nicht allen) * nix-Systemen vorhanden sein. Außerdem würde er den Skriptautor dazu zwingen, ein portierbares Skript zu erstellen, anstatt in Bashismen zu verfallen.flags
ist keine Standardfunktion, aber es ist nützlich genug, um eine variable Anzahl von Flags zu speichern, sodass ich mich entschlossen habe, es beizubehalten.script=/what/ever; something && exec this "$script" "$@"; exec that "$script" -x -y "$@"
. Sie können auch eine Fehlerprüfung für Ausführungsfehler hinzufügen.Sie können auch einen Polyglot schreiben (zwei Sprachen kombinieren). / bin / sh ist garantiert vorhanden.
Dies hat den Nachteil von hässlichem Code und möglicherweise können einige
/bin/sh
s verwirrt werden. Sie kann jedoch verwendet werden, wennenv
sie nicht existiert oder woanders als / usr / bin / env existiert. Es kann auch verwendet werden, wenn Sie eine ziemlich ausgefallene Auswahl treffen möchten.Der erste Teil des Skripts bestimmt, welcher Interpreter verwendet werden soll, wenn / bin / sh als Interpreter ausgeführt wird. Er wird jedoch ignoriert, wenn er vom richtigen Interpreter ausgeführt wird. Verwenden Sie
exec
diese Option , um zu verhindern, dass die Shell mehr als den ersten Teil ausführt.Python-Beispiel:
quelle
exec "$interpreter" "$0" "$@"
den Namen des Skripts selbst auch dem eigentlichen Interpreter mitteilen. (Und dann hoffe, niemand hat beim Aufstellen gelogen$0
.)#!
, ignoriert Scala alles bis auf eine Übereinstimmung!#
. Auf diese Weise können Sie beliebig komplexen Skriptcode in einer beliebigen Sprache und anschließendexec
die Scala-Ausführungsengine mit dem Skript einfügen .Ich bevorzuge die Antworten von Kusalananda und Ilkkachu, aber hier ist eine alternative Antwort, die direkter das tut, wonach die Frage gestellt wurde, einfach weil sie gestellt wurde.
Beachten Sie, dass Sie dies nur tun können, wenn der Interpreter das Schreiben von Code im ersten Argument zulässt. Hier
-e
und alles danach wird wörtlich als Argument für Ruby genommen. Soweit ich das beurteilen kann, können Sie Bash nicht für den Shebang-Code verwenden, dabash -c
der Code in einem separaten Argument stehen muss.Ich habe versucht, dasselbe mit Python für Shebang-Code zu tun:
aber es stellt sich als zu lang heraus und Linux (zumindest auf meinem Computer) schneidet den Shebang auf 127 Zeichen ab. Bitte entschuldigen Sie die Verwendung von
exec
, um Zeilenumbrüche einzufügen, da Python keine Try-Excepts oderimport
s ohne Zeilenumbrüche zulässt.Ich bin mir nicht sicher, wie portabel das ist, und ich würde es nicht mit Code machen, der für die Verteilung gedacht ist. Trotzdem ist es machbar. Vielleicht findet es jemand nützlich für schnelles und schmutziges Debuggen oder so.
quelle
Dadurch wird zwar der Interpreter im Shell-Skript nicht ausgewählt (es wird pro Computer ausgewählt), es ist jedoch eine einfachere Alternative, wenn Sie Administratorzugriff auf alle Computer haben, auf denen Sie das Skript ausführen möchten.
Erstellen Sie einen Symlink (oder einen Hardlink, falls gewünscht), um auf den gewünschten Interpreterpfad zu verweisen. Auf meinem System befinden sich Perl und Python beispielsweise in / usr / bin:
würde einen Symlink erstellen, der es dem Hashbang ermöglicht, nach / bin / perl usw. aufzulösen. Dadurch bleibt die Möglichkeit erhalten, Parameter auch an die Skripte zu übergeben.
quelle
Ich war heute mit einem ähnlichen Problem konfrontiert (
python3
das auf eine auf einem System zu alte Version von Python hinwies) und hatte einen anderen Ansatz als den hier beschriebenen: Verwenden Sie die "falsche" Version von Python zu booten in die "richtige". Die Einschränkung besteht darin, dass einige Python-Versionen zuverlässig erreichbar sein müssen, dies kann jedoch normalerweise erreicht werden, indem z#!/usr/bin/env python3
.Also starte ich mein Skript mit:
Was dies bewirkt ist:
quelle