Shebang beginnend mit `//`?

60

Ich bin verwirrt, wenn ich dem Skript ( hello.go) folge .

//usr/bin/env go run $0 $@ ; exit

package main
import "fmt"
func main() {
    fmt.Printf("hello, world\n")
}

Es kann ausgeführt werden. (unter MacOS X 10.9.5)

$ chmod +x hello.go
$ ./hello.go
hello, world

Ich habe noch nie von Shebang gehört //. Und es funktioniert immer noch, wenn ich oben im Skript eine leere Zeile einfüge. Warum funktioniert dieses Skript?

kawty
quelle
//&>/dev/null;x="${0%.*}";[ ! "$x" -ot "$0" ]||(rm -f "$x";cc -o "$x" "$0")&&exec "$x" "$@" ...
REINSTATE MONICA - Jeremy Banks
2
Nach den Kommentaren von @ g-man und Jörg und der Antwort von gilles ( unix.stackexchange.com/a/1919/27616 ) sollte dieser Trick verwendet werden, ///....anstatt der //...kompatibelste zu sein!
Olivier Dulac
1
Argumente (oder Speicherorte in einem Verzeichnis) mit Leerzeichen ohne weitere Anführungszeichen werden nicht korrekt behandelt:go run "$0" "$@"
Charles Duffy

Antworten:

71

Es ist kein Scheiß, es ist nur ein Skript, das von der Standard-Shell ausgeführt wird. Die Shell führt die erste Zeile aus

//usr/bin/env go run $0 $@ ; exit 

was bewirkt , dass goder Name dieser Datei aufgerufen werden, so das Ergebnis ist , dass diese Datei als ein Go - Skript ausgeführt wird und dann die Shell beendet , ohne den Rest der Datei zu suchen.

Aber warum mit //statt nur /oder einem richtigen Schafskopf anfangen #!?

Dies liegt daran, dass die Datei ein gültiges go-Skript sein muss oder go sich beschwert. In go bezeichnen die Zeichen //einen Kommentar. Go betrachtet die erste Zeile also als Kommentar und versucht nicht, diesen zu interpretieren. Das Zeichen gibt #jedoch keinen Kommentar an, sodass ein normaler Shebang zu einem Fehler führen würde, wenn go die Datei interpretiert.

Dieser Grund für die Syntax besteht lediglich darin, eine Datei zu erstellen, die sowohl ein Shell-Skript als auch ein Go-Skript ist, ohne dass einer auf den anderen tritt.

casey
quelle
10
Es wird vom Kernel gehandhabt, nicht von der Shell. Siehe Gilles 'Antwort auf Wie Linux mit Trennzeichen für mehrere Pfade umgeht (/ home //// Benutzername /// Datei) .
G-Man
3
@HermanTorjussen Feature - Die Synax der Pfade ist ziemlich gut definiert und erlaubt viele nützliche Varianten - und mit der Leistung kommt die Komplexität: /als Pfadsuffix wird definiert als /.; Wenn aes sich nicht um einen Symlink handelt, aist a/dies dasselbe wie bei a/.Thera. In diesen Fällen kann ein Pfad einen zusätzlichen Pfad erhalten, /ohne dass sich die Bedeutung ändert. Wenn ein kanonischer Pfad abgeleitet wird, wird in einem Normalisierungsschritt ein Schrägstrich in Folge zu einem Schrägstrich zusammengezogen. Zugegeben, es ist kein sauberer Teil der formalen Syntax.
Volker Siegel
13
Eigentlich sagt POSIX , dass mehrere Hiebe das gleiche wie ein einfacher Schrägstrich sind außer , wenn es genau zwei Schrägstriche genau gleich am Anfang des Weges. Wie hier. In diesem Fall ist die Interpretation des Pfads von der Implementierung abhängig: "Wenn ein Pfadname mit zwei aufeinanderfolgenden <Schrägstrichen> beginnt, wird die erste Komponente nach den führenden <Schrägstrichen> möglicherweise implementierungsdefiniert interpretiert, obwohl mehr als zwei führende <Schrägstriche> werden als einzelne <Schrägstriche> behandelt. "
Jörg W Mittag
11
Also, um es portabel zu machen, sollte man stattdessen schreiben ///usr/bin/env go run $0 $@ ; exit...
Ruslan
1
@geek Die Shell wird beendet, jedoch nicht vor dem Starten des go-Interpreters. Go druckt Hallo Welt, nicht die Shell.
Casey
8

Es wird ausgeführt, weil als ausführbare Datei standardmäßig / bin / sh-Skript angenommen wird. Dh wenn Sie keine bestimmte Shell angegeben haben - es ist #! / Bin / sh.

Das // wird in Pfaden einfach ignoriert - Sie können es als einzelnes '/' betrachten.

Sie können also davon ausgehen, dass Sie ein Shell-Skript mit der ersten Zeile haben:

/usr/bin/env go run $0 $@ ; exit

Was macht diese Zeile? Es läuft 'env' mit den Parametern 'go run $ 0 $ @'. Es gibt den Befehl 'go' und 'run $ 0 $ @' als Argumente und beendet das Skript anschließend. $ 0 ist dieser Skriptname. $ @ sind ursprüngliche Skriptargumente. Also läuft diese Zeile los, die dieses Skript mit seinen Argumenten ausführt

Wie in Kommentaren erwähnt, gibt es sehr interessante Details, dass zwei Schrägstriche implementierungsdefiniert sind und dieses Skript POSIX-korrekt wird, wenn drei oder mehr Schrägstriche angegeben werden. Weitere Informationen zum Umgang mit Schrägstrichen in Pfaden finden Sie unter http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html .

Beachten Sie auch, dass es einen weiteren Fehler im Skript gibt, nämlich $ @. Es ist richtig, stattdessen "$ @" zu verwenden, da andernfalls, wenn ein Parameter Leerzeichen enthält, dieser in viele Parameter aufgeteilt wird. Sie können beispielsweise keinen Dateinamen mit Leerzeichen übergeben, wenn Sie nicht das "$ @" verwenden.

Dieses spezielle Skript basiert offensichtlich auf der Idee, dass '//' gleich '/' ist.

gena2x
quelle
9
"Das // wird in Pfaden einfach ignoriert" - Das ist nicht garantiert: "Wenn ein Pfadname mit zwei aufeinanderfolgenden <Schrägstrichen> beginnt, kann die erste Komponente nach den führenden <Schrägstrichen> in einer implementierungsdefinierten Weise interpretiert werden" ( pubs .opengroup.org / onlinepubs / 9699919799 / basedefs /… )
Jörg W Mittag
Sehr interessante, aktualisierte Antwort.
Gena2x
1
Insbesondere AFS wurde // anders implementiert, aber es ist nicht mehr üblich.
Charles Duffy
0

Dies funktioniert für C ++ (und C, wenn dieses C // für Kommentare zulässt)

//usr/bin/env sh -c 'p=$(expr '"_$0"' : "_\(.*\)\.[^.]*"); make $p > /dev/null && $p'; exit

Matthew Hannigan
quelle