Ich möchte das aktuelle Skript durchsuchen, damit ich Hilfe- und Versionsinformationen aus dem Kommentarbereich oben ausdrucken kann.
Ich dachte an so etwas:
grep '^#h ' -- "$0" | sed -e 's/#h //'
Aber dann habe ich mich gefragt, was passieren würde, wenn sich das Skript in einem Verzeichnis befindet, das sich in PATH befindet und ohne explizite Angabe des Verzeichnisses aufgerufen wird.
Ich suchte nach einer Erklärung der speziellen Variablen und fand die folgenden Beschreibungen von $0
:
Name der aktuellen Shell oder des aktuellen Programms
Dateiname des aktuellen Skripts
Name des Skripts selbst
Befehl, wie es ausgeführt wurde
Keines davon macht deutlich, ob der Wert von $0
das Verzeichnis enthalten würde oder nicht, wenn das Skript ohne es aufgerufen würde. Der letzte impliziert mir tatsächlich, dass dies nicht der Fall ist.
Testen auf meinem System (Bash 4.1)
Ich habe eine ausführbare Datei in / usr / local / bin mit dem Namen scriptname mit einer Zeile erstellt echo $0
und sie von verschiedenen Speicherorten aus aufgerufen.
Das sind meine Ergebnisse:
> cd /usr/local/bin/test
> ../scriptname
../scriptname
> cd /usr/local/bin
> ./scriptname
./scriptname
> cd /usr/local
> bin/scriptname
bin/scriptname
> cd /tmp
> /usr/local/bin/scriptname
/usr/local/bin/scriptname
> scriptname
/usr/local/bin/scriptname
In diesen Tests ist der Wert von $0
immer genau so, wie das Skript aufgerufen wurde, außer wenn es ohne Pfadkomponente aufgerufen wird. In diesem Fall ist der Wert von $0
der absolute Pfad . Es sieht also so aus, als wäre es sicher, an einen anderen Befehl weiterzugeben.
Aber dann stieß ich auf einen Kommentar zu Stack Overflow , der mich verwirrte. In der Antwort wird vorgeschlagen $(dirname $0)
, das Verzeichnis des aktuellen Skripts abzurufen. Der Kommentar (7-mal positiv bewertet) besagt, dass "das nicht funktioniert, wenn sich das Skript in Ihrem Pfad befindet".
Fragen
- Ist dieser Kommentar richtig?
- Ist das Verhalten auf anderen Systemen anders?
- Gibt es Situationen, in denen
$0
das Verzeichnis nicht enthalten wäre?
quelle
$0
etwas anderes als das Skript ist, das den Fragentitel beantwortet. Ich interessiere mich jedoch auch für Situationen, in denen$0
sich das Skript selbst befindet, das Verzeichnis jedoch nicht enthält. Insbesondere versuche ich, den Kommentar zur SO-Antwort zu verstehen.Antworten:
In den meisten Fällen
$0
wird ein Pfad enthalten, absolut oder relativ zum Skript, also(vorausgesetzt, es gibt einen
readlink
Befehl, der unterstützt wird-e
) ist im Allgemeinen ein guter Weg, um den kanonischen absoluten Pfad zum Skript zu erhalten.$0
wird aus dem Argument zugewiesen, das das an den Interpreter übergebene Skript angibt.Zum Beispiel in:
$0
bekommtthe/script
.Wenn Sie laufen:
Ihre Shell wird Folgendes tun:
Wenn das Skript
#! /bin/sh -
zum Beispiel einen Knall enthält , wandelt das System diesen um in:(Wenn es keinen She-Bang enthält oder allgemeiner, wenn das System einen ENOEXEC-Fehler zurückgibt, dann ist es Ihre Shell, die dasselbe tut)
Es gibt eine Ausnahme für setuid / setgid-Skripte auf einigen Systemen, bei denen das System das Skript auf einigen Systemen öffnet
fd
x
und stattdessen ausführt:um Rennbedingungen zu vermeiden (in diesem Fall
$0
wird enthalten/dev/fd/x
).Nun kann man argumentieren , dass
/dev/fd/x
ist ein Pfad zu diesem Skript. Beachten Sie jedoch, dass Sie beim Lesen$0
das Skript unterbrechen, wenn Sie die Eingabe verwenden.Jetzt gibt es einen Unterschied, wenn der aufgerufene Skriptbefehlsname keinen Schrägstrich enthält. Im:
Shell wird aufblicken
the-script
in$PATH
.$PATH
kann absolute oder relative Pfade (einschließlich der leeren Zeichenfolge) zu einigen Verzeichnissen enthalten. Wenn beispielsweise das aktuelle Verzeichnis$PATH
enthält/bin:/usr/bin:
undthe-script
gefunden wird, führt die Shell Folgendes aus:was wird werden:
Oder wenn es gefunden wird in
/usr/bin
:In allen oben genannten Fällen mit Ausnahme des Falles setuid Corner
$0
wird ein Pfad (absolut oder relativ) zum Skript enthalten.Ein Skript kann jetzt auch wie folgt aufgerufen werden:
Wenn
the-script
wie oben keine Schrägstriche enthalten sind, variiert das Verhalten geringfügig von Shell zu Shell.Alte AT & T-
ksh
Implementierungen haben das Skript tatsächlich bedingungslos nachgeschlagen$PATH
(was eigentlich ein Fehler und eine Sicherheitslücke für Setuid-Skripte war) und$0
enthielten daher keinen Pfad zum Skript, es sei denn, die$PATH
Suche wurde tatsächlichthe-script
im aktuellen Verzeichnis gefunden.Neuere AT & T
ksh
würden versuchen,the-script
im aktuellen Verzeichnis zu interpretieren, wenn es lesbar ist. Wenn nicht, würde es nach einer lesbaren und ausführbaren Dateithe-script
in suchen$PATH
.Denn
bash
es prüft, obthe-script
es sich im aktuellen Verzeichnis befindet (und kein defekter Symlink ist), und wenn nicht, sucht es nach einem lesbaren (nicht unbedingt ausführbaren)the-script
in$PATH
.zsh
insh
Emulation würde gerne tun,bash
außer dass, wennthe-script
ein defekter Symlink im aktuellen Verzeichnis ist, es nicht nach einemthe-script
In suchen$PATH
würde und stattdessen einen Fehler melden würde.Alle anderen Bourne-ähnlichen Muscheln sehen nicht
the-script
auf$PATH
.Wenn Sie bei all diesen Shells feststellen, dass
$0
sie kein a enthalten/
und nicht lesbar sind, wurde sie wahrscheinlich nachgeschlagen$PATH
. Da Dateien in$PATH
wahrscheinlich ausführbar sind, ist es wahrscheinlich eine sichere Annäherung,command -v -- "$0"
um den Pfad zu finden (obwohl dies nicht funktionieren würde, wenn$0
zufällig auch der Name einer eingebauten Shell oder eines Schlüsselworts (in den meisten Shells) verwendet wird).Wenn Sie diesen Fall wirklich abdecken möchten, können Sie ihn schreiben:
(Das
""
angehängte$PATH
Element besteht darin, ein nachfolgendes leeres Element mit Schalen$IFS
beizubehalten, die als Trennzeichen anstelle des Trennzeichens fungieren .)Jetzt gibt es esoterischere Möglichkeiten, ein Skript aufzurufen. Man könnte tun:
Oder:
In diesem Fall ist
$0
dies das erste Argument (argv[0]
), das der Interpreter erhalten hat (obenthe-shell
, aber das kann alles sein, obwohl im Allgemeinen entweder der Basisname oder ein Pfad zu diesem Interpreter).Es
$0
ist nicht zuverlässig zu erkennen, dass Sie sich in dieser Situation befinden, basierend auf dem Wert von . Sie können sich die Ausgabe von ansehenps -o args= -p "$$"
, um einen Hinweis zu erhalten. Im Pipe-Fall gibt es keine echte Möglichkeit, zu einem Pfad zum Skript zurückzukehren.Man könnte auch tun:
Dann, es sei denn
zsh
(und einige alte Implementierung der Bourne - Shell),$0
wäreblah
. Auch hier ist es schwierig, in diesen Shells zum Pfad des Skripts zu gelangen.Oder:
etc.
Um sicherzustellen, dass Sie das Recht haben
$progname
, können Sie nach einer bestimmten Zeichenfolge suchen:Aber ich denke auch nicht, dass es die Mühe wert ist.
quelle
"-"
in den obigen Beispielen nicht. Nach meiner Erfahrungexec("the-script", ["the-script", "its", "args"])
wirdexec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"])
natürlich mit der Möglichkeit eine Dolmetscheroption.#! /bin/sh -
ist das Sprichwort "Immer verwenden,cmd -- something
wenn Sie nicht garantieren können, dasssomething
es nicht beginnt-
", das hier angewendet wird/bin/sh
(wobei-
der Marker für das Ende der Option portabler ist als--
), wobeisomething
es der Pfad / Name des ist Skript. Wenn Sie das nicht für setuid Skripte verwenden (auf Systeme , die sie unterstützen , aber nicht mit dem / dev / fd / x - Methode in der Antwort erwähnt), dann kann man ein Root - Shell erhalten , indem Sie einen symbolischen Link zu Ihrem Skript erstellen genannt-i
oder-s
für Beispiel./bin/sh
sollten Sie die eigene Optionsverarbeitung deaktivieren, wenn festgestellt werden kann, dass Setuid ausgeführt wird. Ich sehe nicht, wie die Verwendung von / dev / fd / x allein das behebt. Sie brauchen immer noch den einfachen / doppelten Bindestrich, denke ich./dev/fd/x
beginnt mit/
, nicht-
. Das primäre Ziel ist es jedoch, die Race-Bedingung zwischen den beidenexecve()
s zu entfernen (zwischen dem,execve("the-script")
was die Berechtigungen erhöht, und dem nachfolgenden,execve("interpreter", "thescript")
wointerpreter
das Skript später geöffnet wird (was in der Zwischenzeit möglicherweise durch einen Symlink zu etwas anderem ersetzt wurde). Systeme, die implementiert werden suid-Skripte machenexecve("interpreter", "/dev/fd/n")
stattdessen ein, wo n als Teil des ersten execve () geöffnet wurde.Hier sind zwei Situationen, in denen das Verzeichnis nicht enthalten wäre:
In beiden Fällen müsste das aktuelle Verzeichnis das Verzeichnis sein, in dem sich der Skriptname befindet.
Im ersten Fall könnte der Wert von
$0
weiterhin übergeben werden,grep
da davon ausgegangen wird, dass das Argument FILE relativ zum aktuellen Verzeichnis ist.Im zweiten Fall sollte es kein Problem sein, wenn die Hilfe- und Versionsinformationen nur als Antwort auf eine bestimmte Befehlszeilenoption gedruckt werden. Ich bin mir nicht sicher, warum jemand auf diese Weise ein Skript aufrufen würde, um die Hilfe- oder Versionsinformationen zu drucken.
Vorsichtsmaßnahmen
Wenn das Skript das aktuelle Verzeichnis ändert, möchten Sie keine relativen Pfade verwenden.
Wenn das Skript bezogen wird, ist der Wert von
$0
normalerweise das Aufruferskript und nicht das bezogene Skript.quelle
Ein beliebiges Argument Null kann angegeben werden, wenn die
-c
Option für die meisten (alle?) Shells verwendet wird. Z.B:Von
man bash
(nur gewählt, weil dies eine bessere Beschreibung als meine hatman sh
- die Verwendung ist unabhängig davon gleich):quelle
argv0
das erste Nichtoperanden -Befehlszeilenargument ist.HINWEIS: Andere haben die Mechanik bereits erklärt,
$0
daher überspringe ich das alles.Im Allgemeinen gehe ich dieses ganze Problem zur Seite und benutze einfach den Befehl
readlink -f $0
. Dies gibt Ihnen immer den vollständigen Weg zurück, was Sie ihm als Argument geben.Beispiele
Angenommen, ich bin hier, um zu beginnen mit:
Erstellen Sie ein Verzeichnis + Datei:
Jetzt fang an anzugeben
readlink
:Zusätzlicher Trick
Jetzt, da ein konsistentes Ergebnis zurückgegeben wird, wenn wir
$0
über abfragenreadlink
, können wir einfach verwendendirname $(readlink -f $0)
, um den absoluten Pfad zum Skript -oder-basename $(readlink -f $0)
zu erhalten, um den tatsächlichen Namen des Skripts zu erhalten.quelle
Meine
man
Seite sagt:Es scheint, dass dies auf
argv[0]
die aktuelle Shell übersetzt wird - oder auf das erste Nichtoperanden- Befehlszeilenargument, das der aktuell interpretierenden Shell beim Aufrufen zugeführt wird. Ich habe zuvor angegeben, dasssh ./somescript
der Pfad zu seiner Variablen führen würde , aber dies war falsch, da dies ein neuer Prozess für sich ist und mit einem neuen aufgerufen wird .$0 $ENV
sh
shell
$ENV
Auf diese Weise
sh ./somescript.sh
unterscheidet sich von dem,. ./somescript.sh
was in der aktuellen Umgebung läuft und$0
bereits eingestellt ist.Sie können dies durch einen Vergleich
$0
zu/proc/$$/status
.Danke für die Korrektur, @toxalot. Ich habe etwas gelernt
quelle
. ./myscript.sh
odersource ./myscript.sh
), dann$0
ist es die Shell. Wenn es jedoch als Argument an shell (sh ./myscript.sh
) übergeben wird,$0
ist dies der Pfad zum Skript. Natürlich istsh
auf meinem System Bash. Ich weiß also nicht, ob das einen Unterschied macht oder nicht.exec
tun und stattdessen beschaffen, aber damit sollteexec
er es tun ../somescript
mit der Bangline ausführen#!/bin/sh
, entspricht dies dem Ausführen/bin/sh ./somescript
. Ansonsten machen sie keinen Unterschied für die Shell.Während
$0
des Skriptnamen enthält , kann es einen Präfix Pfad auf dem Weg basierend enthält das Skript aufgerufen wird, habe ich immer verwenden${0##*/}
den Namen des Skripts in der Hilfe - Ausgabe zu drucken , die von jedem führenden Pfad entfernt$0
.Entnommen aus dem Advanced Bash Scripting-Handbuch - Abschnitt 10.2 Parameterersetzung
Der längste Teil
$0
dieser Übereinstimmungen*/
ist also das gesamte Pfadpräfix, wobei nur der Skriptname zurückgegeben wird.quelle
Für etwas ähnliches benutze ich:
rPath
hat immer den gleichen Wert.quelle