Bestimmt der Shebang die Shell, in der das Skript ausgeführt wird?

84

Das mag eine dumme Frage sein, aber ich frage es immer noch. Wenn ich einen Shebang deklariert habe

#!/bin/bash 

am anfang my_shell_script.shmuss ich dieses script immer mit bash aufrufen

[my@comp]$bash my_shell_script.sh

oder kann ich zB benutzen

[my@comp]$sh my_shell_script.sh

und mein script ermittelt die laufende shell mit dem shebang? Ist es dasselbe, was mit kshShell passiert ? Ich benutze AIX.

jrara
quelle
6
Ihrerseits herrscht ein wenig Verwirrung: Wenn Sie "_some_shell some_script" ausführen, startet _some_shell und fordert Sie auf, some_script zu interpretieren. Also nein, wenn Sie "sh my_shell_script.sh" ausführen, wird der Schebang nicht interpretiert, sondern das Skript wird stattdessen in sh interpretiert. So verwenden Sie den Shebang: chmod +x my_shell_script.sh ; /path/to/my_shell_script.sh # or ./my_shell_script.sh if you happen to be in its directory
Olivier Dulac

Antworten:

117

Der Shebang #! ist eine für den Menschen lesbare Instanz einer magischen Zahl, die aus der Byte-Zeichenfolge 0x23 0x21besteht und von der exec()Funktionsfamilie verwendet wird, um zu bestimmen, ob es sich bei der auszuführenden Datei um ein Skript oder eine Binärdatei handelt. Wenn der Shebang vorhanden ist, exec()wird stattdessen die nach dem Shebang angegebene ausführbare Datei ausgeführt.

Beachten Sie, dass dies bedeutet, dass beim Aufrufen eines Skripts durch Angabe des Interpreters in der Befehlszeile, wie in beiden in der Frage angegebenen Fällen, der in exec()der Befehlszeile angegebene Interpreter ausgeführt wird, das Skript nicht einmal angezeigt wird.

Wenn Sie also, wie andere angemerkt haben, exec()den in der shebang-Zeile angegebenen Interpreter aufrufen möchten , muss für das Skript das ausführbare Bit gesetzt und aufgerufen werden als ./my_shell_script.sh.

Das Verhalten lässt sich mit dem folgenden Skript leicht demonstrieren:

#!/bin/ksh
readlink /proc/$$/exe

Erläuterung:

  • #!/bin/kshlegt fest ksh, der Dolmetscher zu sein.

  • $$ Enthält die PID des aktuellen Prozesses.

  • /proc/pid/exe ist ein Symlink zur ausführbaren Datei des Prozesses (zumindest unter Linux; unter AIX ist /proc/$$/object/a.out ein Link zur ausführbaren Datei).

  • readlink gibt den Wert des symbolischen Links aus.

Beispiel:

Hinweis : Ich bin demonstriert dies auf Ubuntu, wo die Standard - Shell /bin/shein symbolischer Link zu schleudern also /bin/dashund /bin/kshist ein symbolischer Link auf /etc/alternatives/ksh, was wiederum ein symbolischer Link zu /bin/pdksh.

$ chmod +x getshell.sh
$ ./getshell.sh 
/bin/pdksh
$ bash getshell.sh 
/bin/bash
$ sh getshell.sh 
/bin/dash
Thomas Nyman
quelle
Danke Thomas für diese Antwort. Stellen Sie sich vor, wir starten das Skript als untergeordneten Prozess von Node.js oder Java oder was auch immer. Können wir einen "exec" -Prozess starten und exec wird dann das Shell-Skript ausführen? Ich frage, weil ich nach Antworten auf diese Frage suche
Alexander Mills
1
@AlexanderMills In exec()dieser Antwort wird auf einen Systemaufruf verwiesen. Der Befehl execist eine eingebaute Shell. Aus diesem Grund können Sie ein exec Programm nicht über Node.js oder Java aufrufen . Jeder Shell-Befehl, der beispielsweise Runtime.exec()in Java aufgerufen wird, wird schließlich vom exec()Systemaufruf verarbeitet .
Thomas Nyman
Huh, ja, ich bin mit der Java-API, die Sie gerade erwähnt haben, nicht vertraut. Ich frage mich, ob es eine Möglichkeit gibt, den Aufruf von exec () von Node.js irgendwie aufzurufen
Alexander Mills,
@AlexanderMills Ich würde mir vorstellen, child_process.{exec(),execFile(),spawn()} alles würde mit dem C exec()(durch process) implementiert werden .
Thomas Nyman
10

Ja tut es. Übrigens ist es keine dumme Frage. Eine Referenz für meine Antwort ist hier . Starten eines Skripts mit #!

  • Es wird ein Shebang oder eine "Bang" -Linie genannt.

  • Es ist nichts anderes als der absolute Weg zum Bash-Interpreter.

  • Es besteht aus einem Nummernzeichen und einem Ausrufezeichen (#!), Gefolgt vom vollständigen Pfad zum Interpreter wie / bin / bash.

    Alle Skripte unter Linux werden mit dem in der ersten Zeile angegebenen Interpreter ausgeführt. Fast alle Bash-Skripte beginnen häufig mit #! / Bin / bash (vorausgesetzt, Bash wurde in / bin installiert). Dadurch wird sichergestellt, dass Bash auch zur Interpretation des Skripts verwendet wird wenn es unter einer anderen Shell ausgeführt wird. Der Shebang wurde von Dennis Ritchie zwischen Version 7 Unix und 8 bei Bell Laboratories eingeführt. Es wurde dann auch der BSD-Linie in Berkeley hinzugefügt.

Ignorieren einer Dolmetscherzeile (shebang)

Wenn Sie keine Interpreterzeile angeben, ist die Standardeinstellung normalerweise / bin / sh. Es wird jedoch empfohlen, die Zeile #! / Bin / bash festzulegen.

vfbsilva
quelle
3
Der Kernel weiß nur, wie man statisch verknüpfte Binärdateien ausführt und wo man Interpreterinformationen für andere findet (ein spezielles Feld in der Binär- oder der Shebang-Zeile). Normalerweise bedeutet das Ausführen eines Shell-Skripts, dass Sie der Shebang-Linie bis zur Shell und dann dem DT_INTERP-Feld in der Shell-Binärdatei bis zum dynamischen Linker folgen.
Simon Richter
5
Beachten Sie auch, dass dies nicht auf Shell-Skripte beschränkt ist. Alle textbasierten Skriptdateien verwenden dies. Beispiel: #!/usr/bin/perl #!/usr/local/bin/python #!/usr/local/bin/rubyEin anderer allgemeiner shebang-Eintrag, der zur Unterstützung mehrerer Systeme verwendet wird, ist die Verwendung von env, um den Interpreter zu finden, den Sie verwenden möchten, beispielsweise#!/usr/bin/env perl #!/usr/bin/env python
sambler
Apropos @sambler env, was sollte eigentlich vorgezogen werden? Bei Python und Perl envwird dies häufig weggelassen, während bei Shellskripten shebang auf die betreffende Shell verweist.
Polemon
1
@polemon weniger davon wird bevorzugt und mehr auf welchen Pfaden variieren. Die Basis-Shells befinden sich auf allen Systemen auf demselben Pfad. Aktuelle Versionen von Perl und Python können an verschiedenen Orten auf verschiedenen Systemen installiert werden, so dass mit env immer derselbe Shebang funktioniert. Deshalb wird env eher mit Perl- und Python-Skripten als mit Shell-Skripten verwendet.
Sambler
envEin Programm in $ PATH zu finden ist ein bisschen hacken. Es werden keine Umgebungsvariablen gesetzt, wie der Name impliziert. $ PATH kann für verschiedene Benutzer ein unterschiedliches Ergebnis sein. Aber es hilft Skripten, ohne Modifikation auf Systemen zu laufen, die einen vernünftigen Perl-Interpreter an einer merkwürdigen Stelle platzieren.
John Mahowald
4

Der execSystemaufruf des Linux-Kernels versteht shebangs ( #!) nativ

Wenn Sie auf Bash machen:

./something

Unter Linux ruft dies den execSystemaufruf mit dem Pfad auf ./something.

Diese Zeile des Kernels wird in der Datei aufgerufen, die an folgende Adresse weitergeleitet wird exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

Es liest die allerersten Bytes der Datei und vergleicht sie mit #!.

Wenn der Vergleich wahr ist, wird der Rest der Zeile vom Linux-Kernel analysiert, der einen weiteren execAufruf mit Pfad /usr/bin/env pythonund aktueller Datei als erstes Argument ausführt:

/usr/bin/env python /path/to/script.py

Dies funktioniert für alle Skriptsprachen, die #als Kommentarzeichen verwendet werden.

Und ja, Sie können eine Endlosschleife machen mit:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

Bash erkennt den Fehler:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#! Zufällig ist es für Menschen lesbar, aber das ist nicht erforderlich.

Wenn die Datei mit unterschiedlichen Bytes gestartet execwürde, würde der Systemaufruf einen anderen Handler verwenden. Der andere wichtigste integrierte Handler ist für ausführbare ELF-Dateien: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305, der nach Bytes sucht 7f 45 4c 46(was zufällig auch menschlich ist) lesbar für .ELF). Lassen Sie uns das bestätigen, indem wir die 4 ersten Bytes von lesen /bin/ls, bei denen es sich um eine ELF-ausführbare Datei handelt:

head -c 4 "$(which ls)" | hd 

Ausgabe:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

Wenn der Kernel diese Bytes sieht, nimmt er die ELF-Datei, speichert sie korrekt und startet damit einen neuen Prozess. Siehe auch: https://stackoverflow.com/questions/8352535/how-does-kernel-get-an-executable-binary-file-running-under-linux/31394861#31394861

Schließlich können Sie mit dem binfmt_miscMechanismus Ihre eigenen Shebang-Handler hinzufügen . Beispielsweise können Sie einen benutzerdefinierten Handler für .jarDateien hinzufügen . Dieser Mechanismus unterstützt sogar Handler nach Dateierweiterung. Eine andere Anwendung besteht darin , ausführbare Dateien einer anderen Architektur transparent mit QEMU auszuführen .

Ich glaube jedoch nicht, dass POSIX Shebangs spezifiziert: https://unix.stackexchange.com/a/346214/32558 , obwohl es dies in Begründungsabschnitten und in der Form "Wenn ausführbare Skripte vom System unterstützt werden" erwähnt könnte passieren".

Ciro Santilli ist ein Schauspieler
quelle
1
Beim Ausführen ./somethingvon einer Shell wird nicht der vollständige Pfad an übergeben exec, sondern genau der eingegebene Pfad. Können Sie dies in Ihrer Antwort korrigieren? Machen Sie echo "$0"in Ihrem Skript und Sie werden sehen, dass dies der Fall ist.
AndiDog
2

Wenn Sie es konsequent nehmen, ist die ausführbare Datei, die in der shebang-Zeile angegeben ist, nur eine ausführbare Datei. Es ist sinnvoll, einen Textinterpreter als ausführbare Datei zu verwenden, dies ist jedoch nicht erforderlich. Nur zur Verdeutlichung und Demonstration habe ich einen ziemlich nutzlosen Test gemacht:

#!/bin/cat
useless text
more useless text
still more useless text

Die Datei test.txt genannt und stellten Sie das ausführbare Bit chmod u+x test.txt„genannt , “ es dann: ./test.txt. Wie erwartet wird der Inhalt der Datei ausgegeben. In diesem Fall ignoriert cat die Shebang-Linie nicht. Es werden einfach alle Zeilen ausgegeben. Jeder nützliche Interpreter sollte daher in der Lage sein, diese Shebang-Linie zu ignorieren. Für Bash, Perl und PHP ist es einfach eine Kommentarzeile. Also ja, diese ignorieren die Shebang-Linie.

Siegfried
quelle
-1

Wenn für eine Datei ein ausführbares Bit gesetzt ist und aufgerufen wird, analysiert der Kernel den Dateiheader, um festzustellen, wie vorzugehen ist (meines Wissens können Sie benutzerdefinierte Handler für benutzerdefinierte Dateiformate über LKMs hinzufügen). Wenn die Datei eine Textdatei mit einem # zu sein scheint! Kombination zu Beginn wird seine Ausführung an eine andere ausführbare Datei (normalerweise eine Art Shell) gesendet, deren Pfad direkt nach dem besagten Shebang in derselben Zeile angegeben werden soll. Der Kernel führt dann die Shell aus und übergibt die Datei zur Bearbeitung.

Kurz gesagt, es spielt keine Rolle, mit welcher Shell Sie das Skript aufrufen - der Kernel leitet die Ausführung auf die entsprechende Weise weiter.

Vladimir Parka
quelle
4
Es gibt einen deutlichen Unterschied zwischen bash ./myscript.shund ./myscript.sh.
ein Lebenslauf vom
Was meinst du mit diesem "deutlichen Unterschied"?
Jrara
3
@jrara Siehe meine Antwort, die Aussage, dass "es egal ist, mit welcher Shell Sie das Skript aufrufen", ist einfach nicht wahr.
Thomas Nyman