Warum das "-" in der "#! / bin / sh - "shebang?

27
#! / bin / sh -

Ist (oder war zumindest) oft der empfohlene Knall , um ein Drehbuch interpretieren zu lassen /bin/sh.

Warum nicht einfach #! /bin/shoder #!/bin/sh?

Wofür ist das -?

Stéphane Chazelas
quelle

Antworten:

37

Das hat einen ähnlichen Grund, warum Sie schreiben müssen:

rm -- *.txt

Und nicht

rm *.txt

Es sei denn, Sie können garantieren, dass keine der .txtDateien im aktuellen Verzeichnis einen Namen hat, der mit beginnt -.

Im:

rm <arg>

<arg>wird als Option betrachtet, wenn es mit -oder einer Datei beginnt , die sonst entfernt werden soll. Im

rm - <arg>

argwird immer als zu entfernende Datei betrachtet, unabhängig davon, ob sie mit -oder nicht beginnt .

Das ist das gleiche für sh.

Wenn man ein Skript ausführt, das mit beginnt

#! /bin/sh

Normalerweise mit:

execve("path/to/the-script", ["the-script", "arg"], [environ])

Das System wandelt es um in:

execve("/bin/sh", ["/bin/sh", "path/to/the-script", "arg"], [environ])

Das path/to/the-scriptist normalerweise nicht etwas, das unter der Kontrolle des Drehbuchautors steht. Der Autor kann nicht vorhersagen, wo und unter welchem ​​Namen Kopien des Skripts gespeichert werden. Insbesondere können sie nicht garantieren, dass das, path/to/the-scriptmit dem es aufgerufen wird, nicht mit -(oder +dem auch ein Problem mit sh) beginnt . Deshalb brauchen wir das -Hier, um das Ende der Optionen zu markieren.

Zum Beispiel ist auf meinem System zcat(wie in den meisten anderen Skripten) ein Beispiel für ein Skript, das dieser Empfehlung nicht entsprach:

$ head -n1 /bin/zcat
#!/bin/sh
$ mkdir +
$ ln -s /bin/zcat +/
$ +/zcat
/bin/sh: +/: invalid option
[...]

Jetzt können Sie fragen, warum #! /bin/sh -und nicht #! /bin/sh --?

Während #! /bin/sh --mit POSIX-Shells arbeiten würde, #! /bin/sh -ist das portabler; insbesondere zu alten Versionen von sh. shDie Behandlung -als und das Ende von Optionen als Vorgänger getopt()und die allgemeine Verwendung von --, um das Ende von Optionen für eine lange Zeit zu markieren. So wie die Bourne-Shell (aus den späten 70ern) ihre Argumente analysierte, wurde nur das erste Argument für Optionen berücksichtigt, wenn es mit begann -. Alle nachfolgenden Zeichen -werden als Optionsnamen behandelt. Wenn es nach dem kein Zeichen -gab, gab es keine Optionen. Das steckt und alle späteren Bourne-ähnlichen Muscheln erkennen -als Weg, das Ende von Optionen zu markieren.

In der Bourne-Shell (aber nicht in modernen Bourne-ähnlichen Shells) #! /bin/sh -eufwürde das Problem ebenfalls umgangen, da nur das erste Argument für Optionen berücksichtigt wurde.

Nun könnte man sagen , dass wir hier pedantisch sind zu sein, und es ist auch , warum ich das schrieb Notwendigkeit kursiv oben:

  1. niemand in ihrem rechten Verstand würde ein Skript mit etwas beginnen rufen -oder +oder mit in einem Verzeichnis , dessen Namen beginnt setzen -oder +.
  2. Selbst wenn dies der Fall wäre, würde man zunächst argumentieren, dass sie nur sich selbst die Schuld geben können. Wenn Sie jedoch ein Skript aufrufen, ist dies meistens eine Shell oder eine Funktion vom Typ execvp()/ execlp(). In diesem Fall rufen Sie sie im Allgemeinen entweder the-scriptauf, um nachzuschlagen. $PATHIn diesem Fall execve()beginnt das Pfadargument für den Systemaufruf normalerweise mit /(weder -noch +), oder ./the-scriptSie möchten, dass the-scriptdas aktuelle Verzeichnis ausgeführt wird (und) dann beginnt der Weg mit ./, weder -noch +).

Neben der theoretischen Richtigkeit gibt es noch einen weiteren Grund, warum diese #! /bin/sh -Methode als bewährte Methode empfohlen wurde . Und das geht auf eine Zeit zurück, in der auf mehreren Systemen noch setuid-Skripte unterstützt wurden.

Wenn Sie ein Skript haben, das Folgendes enthält:

#! /bin/sh
/bin/echo "I'm running as root"

Und dieses Skript war (wie bei den -r-sr-xr-x root binBerechtigungen) setuid root auf diesen Systemen, wenn es von einem normalen Benutzer ausgeführt wurde, dem

execve("/bin/sh", ["/bin/sh", "path/to/the-script"], [environ])

würde so gemacht werden root!

Wenn der Benutzer einen Symlink erstellt /tmp/-i -> path/to/the-scriptund ausgeführt hat -i, startet er eine interaktive Shell ( /bin/sh -i) als root.

The -würde das umgehen (es würde nicht das Race-Condition-Problem umgehen , oder die Tatsache, dass einige shImplementierungen wie einige ksh88-basierte Skriptargumente ohne /in $PATHnachschlagen würden).

Heutzutage unterstützt kaum ein System mehr setuid-Skripte, und einige derjenigen, die dies noch tun (normalerweise nicht standardmäßig), führen am Ende einen execve("/bin/sh", ["/bin/sh", "/dev/fd/<n>", arg])(wobei <n>ein Dateideskriptor zum Lesen des Skripts geöffnet ist) aus, der sowohl dieses als auch das Problem umgeht Rennbedingung.

Beachten Sie, dass bei den meisten Interpreten ähnliche Probleme auftreten, nicht nur bei Bourne-ähnlichen Shells. Nicht-Bourne-ähnliche Shells unterstützen im Allgemeinen nicht -als End-of-Option-Marker, sondern im Allgemeinen --(zumindest für moderne Versionen).

Ebenfalls

#! /usr/bin/awk -f
#! /usr/bin/sed -f

Haben das Problem nicht als das nächste Argument als Argument für die gilt -fOption auf jeden Fall, aber es funktioniert immer noch nicht , wenn path/to/scriptist -(in diesem Fall mit den meisten sed/ awkImplementierungen sed/ awknicht lesen den Code aus dem -Datei, aber stattdessen von stdin).

Beachten Sie auch, dass auf den meisten Systemen Folgendes nicht verwendet werden kann:

#! /usr/bin/env sh -
#! /usr/bin/perl -w --

Wie bei den meisten Systemen lässt der shebang-Mechanismus nur ein Argument nach dem Interpreterpfad zu.

Ob man #! /bin/sh -vs benutzt #!/bin/sh -, ist nur Geschmackssache. Ich bevorzuge das erstere, da es den Interpreterpfad sichtbarer macht und die Mausauswahl erleichtert. Es gibt eine Legende, die besagt, dass der Platz in einigen alten Unix-Versionen benötigt wurde, aber AFAIK, das wurde nie verifiziert.

Eine sehr gute Referenz zum Unix-Shebang finden Sie unter https://www.in-ulm.de/~mascheck/various/shebang

Stéphane Chazelas
quelle
1
Hat dies irgendeine Bedeutung für #! / Bin / bash?
Joe
1
@ Joe bashakzeptiert natürlich Optionen wie jede andere Shell. Akzeptiert als Bourne-ähnliche und POSIX-Shell bashsowohl #! /bin/bash -als auch #! /bin/bash --.
Stéphane Chazelas