Ich habe ein Skript, das auf zwei Computern ausgeführt werden soll. Diese beiden Computer erhalten Kopien des Skripts aus demselben Git-Repository. Das Skript muss mit dem richtigen Interpreter (zB zsh
) ausgeführt werden.
Leider leben beide env
und zsh
an unterschiedlichen Orten auf den lokalen und entfernten Rechnern:
Remote-Maschine
$ which env
/bin/env
$ which zsh
/some/long/path/to/the/right/zsh
Lokale Maschine
$ which env
/usr/bin/env
$which zsh
/usr/local/bin/zsh
Wie kann ich den shebang so einstellen, dass beim Ausführen des Skripts wie /path/to/script.sh
immer das Zsh
verfügbare in verwendet wird PATH
?
env
nicht sowohl in / bin als auch in / usr / bin sind? Versuchen Sie eswhich -a env
zu bestätigen.Antworten:
Sie können dies nicht direkt durch shebang lösen, da shebang rein statisch ist. Was Sie tun können, ist, einen »am wenigsten verbreiteten Multiplikator« (aus Sicht der Shell) im shebang zu haben und Ihr Skript mit der richtigen Shell erneut auszuführen, wenn diese LCM nicht zsh ist. Mit anderen Worten:
zsh
Lassen Sie Ihr Skript von einer Shell ausführen, die auf allen Systemen vorhanden ist. Prüfen Sie, ob eine einzige Funktion vorhanden ist. Wenn der Test falsch ist, führen Sie das Skriptexec
mit auszsh
, wo der Test erfolgreich ist, und fahren Sie einfach fort.Ein einzigartiges Merkmal
zsh
ist beispielsweise das Vorhandensein der$ZSH_VERSION
Variablen:In diesem einfachen Fall wird das Skript zuerst von ausgeführt
/bin/sh
(alle Unix-ähnlichen Systeme nach den 80er Jahren verstehen#!
und haben/bin/sh
entweder Bourne oder POSIX, aber unsere Syntax ist mit beiden kompatibel). Wenn$ZSH_VERSION
ist nicht gesetzt, wird das Skriptexec
s‘selbst durchzsh
. Wenn$ZSH_VERSION
gesetzt ist (bzw. das Skript bereits durchlaufen istzsh
), wird der Test einfach übersprungen. Voilà.Dies scheitert nur, wenn überhaupt
zsh
nichts drin ist$PATH
.Edit: Um sicherzustellen , dass Sie nur
exec
einzsh
an den üblichen Stellen, könnten Sie so etwas wie verwendenDies könnte Sie davor bewahren, versehentlich
exec
etwas in Ihr zu stecken,$PATH
was nicht das ist, waszsh
Sie erwarten.quelle
zsh
in dem$PATH
nicht das ist, was Sie erwarten.zsh
Binärdatei an den Standardstandorten wirklich eine istzsh
.zsh
, wo es mit istzsh -c 'whence zsh'
. Einfacher kann man ebencommand -v zsh
. Sehen Sie meine Antwort für, wie man dynamisch den Pfad#!bang
.zsh
Binärdatei von$PATH
, um den Pfad derzsh
Binärdatei zu ermitteln, würde das Problem nicht ganz lösen. @RyanReich hat darauf hingewiesen, oder? :)zsh
selbst exekutieren , nein, ich denke nicht. Aber wenn Sie die resultierende Zeichenfolge in Ihren Hash-Knall einbetten und dann Ihr eigenes Skript ausführen, wissen Sie zumindest, was Sie erhalten. Trotzdem wäre es für einen einfacheren Test als für das Schleifen.Ich habe jahrelang etwas Ähnliches verwendet, um mit den verschiedenen Speicherorten von Bash auf Systemen umzugehen, für deren Ausführung ich meine Skripte benötigte.
Bash / Zsh / etc.
Das Obige kann leicht für eine Vielzahl von Dolmetschern angepasst werden. Das wichtigste ist, dass dieses Skript zunächst als Bourne-Shell ausgeführt wird. Es ruft sich dann ein zweites Mal rekursiv auf, parst jedoch alles über dem Kommentar
### BEGIN
mitsed
.Perl
Hier ist ein ähnlicher Trick für Perl:
Diese Methode nutzt Perls Fähigkeit, wenn eine Datei ausgeführt wird, und analysiert diese Datei, wobei alle Zeilen vor der Zeile übersprungen werden
#! perl
.quelle
$*
anstelle von"$@"
, unbrauchbare Verwendung von eval, nicht gemeldeter Exit-Status (Sie habenexec
den ersten nicht verwendet ), fehlende-
/--
, Fehlermeldungen nicht auf stderr, 0 Exit-Status für Fehlerzustände Verwenden Sie / bin / sh für LIN_BASH, ein nutzloses Semikolon (kosmetisch), und verwenden Sie Großbuchstaben für Nicht-Umgebungsvariablen.uname -s
ist wieuname
(Uname ist für Unix Name). Sie haben vergessen zu erwähnen, dass das Überspringen durch die-x
Option zu ausgelöst wirdperl
.HINWEIS: @ jw013 erhebt in den folgenden Kommentaren den folgenden nicht unterstützten Einwand:
Ich antwortete auf seine Sicherheit Einwände mit dem Hinweis darauf , dass keine speziellen Berechtigungen werden nur einmal benötigt pro Installation / Update Aktion , um zu installieren / aktualisieren die selbst installierende Skript - was ich persönlich würde ziemlich sicher nennen. Ich wies ihn auch auf einen
man sh
Hinweis darauf hin, ähnliche Ziele mit ähnlichen Mitteln zu erreichen. Ich habe mich zu diesem Zeitpunkt nicht darum gekümmert , darauf hinzuweisen, dass Sicherheitslücken oder sonstige allgemein nicht empfohlene Praktiken , die in meiner Antwort vertreten sein könnten oder nicht, eher in der Frage selbst als in meiner Antwort darauf begründet waren:Nicht zufrieden, fuhr @ jw013 fort, Einwände zu erheben, indem er sein noch nicht unterstütztes Argument mit mindestens ein paar falschen Aussagen vorbrachte :
An erster Stelle:
DER EINZIGE AUSFÜHRBARE CODE IN JEDEM AUSFÜHRBAREN SHELL-SCRIPT IST
#!
DAS SELBST(obwohl selbst
#!
ist offiziell nicht spezifiziert )Ein Shellskript ist nur eine Textdatei. Damit es überhaupt eine Wirkung hat, muss es von einer anderen ausführbaren Datei gelesen und die Anweisungen von dieser anderen ausführbaren Datei interpretiert werden, bevor die andere ausführbare Datei schließlich die Interpretation des Befehls ausführt Shell-Skript. Es ist nicht möglich, dass die Ausführung einer Shell-Skriptdatei weniger als zwei Dateien umfasst. Es gibt eine mögliche Ausnahme im
zsh
eigenen Compiler, aber damit habe ich wenig Erfahrung und es wird hier in keiner Weise dargestellt.Der Hashbang eines Shell-Skripts muss auf den vorgesehenen Interpreter verweisen oder als irrelevant verworfen werden.
DAS ANERKENNUNGS- / AUSFÜHRUNGSVERHALTEN DER MUSCHEL IST STANDARDS-DEFINIERT
Die Shell verfügt über zwei grundlegende Modi zum Analysieren und Interpretieren ihrer Eingabe: Entweder definiert ihre aktuelle Eingabe ein
<<here_document
oder sie definiert ein{ ( command |&&|| list ) ; } &
- mit anderen Worten, die Shell interpretiert ein Token entweder als Begrenzer für einen Befehl, den sie ausführen soll, sobald sie ihn gelesen hat in oder als Anweisungen zum Erstellen einer Datei und Zuordnen zu einem Dateideskriptor für einen anderen Befehl. Das ist es.Bei der Interpretation von Befehlen zur Ausführung der Shell werden Token für eine Reihe reservierter Wörter begrenzt. Wenn die Schale eine Öffnung stößt Token es muss weiterhin in einer Befehlsliste lesen , bis entweder die Liste durch eine Schließ Token wie eine neue Zeile begrenzt wird - falls anwendbar - oder das Schließen wie Token
})
für die({
vor der Ausführung.Die Shell unterscheidet zwischen einem einfachen Befehl und einem zusammengesetzten Befehl. Der zusammengesetzte Befehl ist der Befehlssatz, der vor der Ausführung eingelesen werden muss, aber die Shell führt
$expansion
keinen ihrer einfachen Befehle aus, bis sie jeden einzeln ausführt.Im folgenden Beispiel begrenzen die
;semicolon
reservierten Wörter einzelne einfache Befehle, wohingegen das nicht maskierte\newline
Zeichen zwischen den beiden zusammengesetzten Befehlen liegt:Das ist eine Vereinfachung der Richtlinien. Es wird viel komplizierter, wenn man Shell-Builtins, Subshells, die aktuelle Umgebung usw. betrachtet, aber für meine Zwecke hier ist es genug.
Und sprechen Einbauten und Befehlslisten, eine
function() { declaration ; }
ist lediglich ein Mittel , um eine Zuordnung Verbindung Befehl zu einem einfachen Befehl. Die Shell darf$expansions
die Deklarationsanweisung selbst nicht ausführen,<<redirections>
sondern muss stattdessen die Definition als einzelne, wörtliche Zeichenfolge speichern und als spezielle Shell ausführen, die beim Aufruf integriert ist.Daher wird eine in einem ausführbaren Shell-Skript deklarierte Shell-Funktion im Speicher der interpretierenden Shell in ihrer Literal-String-Form gespeichert - nicht erweitert, um hier angehängte Dokumente als Eingabe einzuschließen - und jedes Mal, wenn sie als Shell-Builded aufgerufen wird, unabhängig von ihrer Quelldatei ausgeführt. in so lange, wie die aktuelle Umgebung der Shell dauert.
A
<<HERE-DOCUMENT
IST EINE INLINE-DATEISiehst du? Für jede Shell über der Shell wird eine Datei erstellt und einem Dateideskriptor zugeordnet. In
zsh, (ba)sh
der Shell wird eine reguläre Datei erstellt/tmp
, ausgegeben, einem Deskriptor zugeordnet und dann die/tmp
Datei gelöscht , sodass nur die Kopie des Deskriptors des Kernels übrig bleibt.dash
Vermeidet all diesen Unsinn und legt die Ausgabeverarbeitung einfach in einer anonymen|pipe
Datei ab, die auf das Umleitungsziel abzielt<<
.Das macht
dash
's:funktionell äquivalent zu
bash
's:dash
Die Implementierung von while ist zumindest POSIX-fähig.Das macht MEHRERE FILES
Also in der Antwort unten, wenn ich es tue:
Folgendes passiert:
Ich zuerst
cat
der Inhalt , was auch immer Datei die Shell erstelltFILE
in./file
, macht es ausführbar, dann ausführen.Der Kernel interpretiert die
#!
und Anrufe/usr/bin/sh
mit einem<read
Dateideskriptor zugewiesen./file
.sh
Ordnet eine Zeichenfolge in den Speicher zu, die aus dem zusammengesetzten Befehl besteht, der um beginnt_fn()
und um endetSCRIPT
.Wenn
_fn
aufgerufen wird,sh
muss zunächst dann auf einen Deskriptor in die Datei map interpret definiert<<SCRIPT...SCRIPT
vor Aufruf_fn
als spezielle integrierte Dienstprogramm , daSCRIPT
ist_fn
‚s<input.
Die von
printf
und ausgegebenen Zeichenfolgencommand
werden an_fn
die Standardausgabe>&1
(die an die aktuelle Shell umgeleitet wird)ARGV0
oder ausgegeben$0
.cat
verkettet seinen<&0
Standardeingabedateideskriptor -SCRIPT
- über das>
abgeschnitteneARGV0
Argument der aktuellen Shell , oder$0
.Vervollständigen Sie den bereits eingelesenen aktuellen zusammengesetzten Befehl mit
sh exec
dem ausführbaren und neu geschriebenen$0
Argument.Von der Zeit ,
./file
bis die darin enthaltenen Anweisungen aufgerufen wird , festzulegen , dass es sollteexec
wieder d,sh
liest er in einer einzigen Verbindung Befehl zu einem Zeitpunkt , als es sie ausführt, während./file
sich gar nichts tut , außer glücklich seine neue Inhalte. Die Dateien, die tatsächlich in Arbeit sind, sind/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
DANKE NACH ALLEN
Wenn @ jw013 Folgendes angibt:
... inmitten seiner fehlerhaften Kritik an dieser Antwort billigt er tatsächlich unabsichtlich die einzige Methode, die hier angewendet wird, was im Grunde genommen nur zu Folgendem führt:
ANTWORTEN
Alle Antworten hier sind gut, aber keine von ihnen ist völlig richtig. Jeder scheint zu behaupten, dass Sie nicht dynamisch und dauerhaft auf Ihrem Weg sein können
#!bang
. Hier ist eine Demonstration der Einrichtung eines wegunabhängigen Schebangs:DEMO
AUSGABE
Siehst du? Wir lassen das Skript einfach selbst überschreiben. Und es passiert immer nur einmal nach einer
git
Synchronisierung. Von diesem Punkt an hat es den richtigen Pfad in der #! Knalllinie.Jetzt ist fast alles da oben nur noch Flusen. Um dies sicher zu machen, benötigen Sie:
Eine oben definierte und unten aufgerufene Funktion, die das Schreiben übernimmt. Auf diese Weise speichern wir alles, was wir brauchen, im Speicher und stellen sicher, dass die gesamte Datei eingelesen wird, bevor wir anfangen, darüber zu schreiben.
Eine Möglichkeit, den Pfad zu bestimmen.
command -v
ist ziemlich gut dafür.Heredocs helfen wirklich, weil es sich um aktuelle Dateien handelt. Sie werden Ihr Skript in der Zwischenzeit speichern. Sie können auch Zeichenfolgen verwenden, aber ...
Sie müssen sicherstellen, dass die Shell den Befehl einliest, der Ihr Skript in derselben Befehlsliste überschreibt wie den Befehl, der es ausführt.
Aussehen:
Beachten Sie, dass ich den
exec
Befehl nur eine Zeile nach unten verschoben habe . Jetzt:Ich bekomme die zweite Hälfte der Ausgabe nicht, weil das Skript den nächsten Befehl nicht einlesen kann. Trotzdem, weil der einzige fehlende Befehl der letzte war:
Das Drehbuch ist so durchgekommen, wie es hätte sein sollen - hauptsächlich, weil alles im Heredoc enthalten war -, aber wenn Sie es nicht richtig planen, können Sie Ihren Dateistream abschneiden.
quelle
man command
für eine widersprüchliche Meinung.man command
für eine widersprüchliche Meinung - nicht finden. Können Sie mich zu dem Abschnitt / Absatz weiterleiten, über den Sie gesprochen haben?man sh
- Suche nach 'Befehl -v'. Ich wusste, dass es auf einer derman
Seiten war, die ich mir neulich angesehen habe .command -v
Beispiel, von dem Sie gesprochen habenman sh
. Das ist ein normal aussehendes Installationsskript und kein selbstmodifizierendes . Selbst unabhängige Installer enthalten nur Eingaben vor der Änderung und geben ihre Änderungen an anderer Stelle aus. Sie schreiben sich nicht so um, wie Sie es empfehlen.Hier ist eine Möglichkeit, ein selbstmodifizierendes Skript zu erstellen, mit dem das Problem behoben werden kann. Dieser Code sollte Ihrem eigentlichen Skript vorangestellt werden.
Einige Kommentare:
Es sollte einmal von jemandem ausgeführt werden, der die Berechtigung hat, Dateien in dem Verzeichnis zu erstellen und zu löschen, in dem sich das Skript befindet.
Es wird nur die alte Bourne-Shell-Syntax verwendet, da trotz der weit verbreiteten Meinung
/bin/sh
nicht garantiert wird, dass es sich um eine POSIX-Shell handelt, auch nicht bei POSIX-kompatiblen Betriebssystemen.Der Pfad wurde auf POSIX-konform gesetzt, gefolgt von einer Liste möglicher zsh-Speicherorte, um zu vermeiden, dass ein "falscher" zsh ausgewählt wird.
Wenn aus irgendeinem Grund ein selbstmodifizierendes Skript nicht erwünscht ist, wäre es trivial, zwei Skripte anstelle eines zu verteilen, wobei das erste dasjenige ist, das Sie patchen möchten, und das zweite, das ich für die Verarbeitung des ersteren leicht modifiziert habe.
quelle
/bin/sh
Punkt ist gut - aber brauchen Sie in diesem Fall überhaupt einen vormodifizierten#!
? Und ist es nichtawk
genauso wahrscheinlich, dass es falsch ist, wie eszsh
ist?sed -i
sowieso tut. Ich persönlich denke, das$PATH
Problem, das in den Kommentaren zu einer anderen Antwort erwähnt wurde und das Sie so sicher wie möglich ansprechen, lässt sich besser lösen, indem Sie einfach und explizit Abhängigkeiten definieren und / oder strenge und explizite Tests durchführen -getconf
vielleicht jetzt gefälscht, aber die Chancen stehen nahe null, genau wie fürzsh
undawk.
$(getconf PATH)
ist nicht Bourne.cp $0 $0.old
ist zsh-Syntax. Das Bourne Äquivalent wärecp "$0" "$0.old"
wenn man sich wünschencp -- "$0" "$0.old"