Zu viele Shebang-Zeilen (Skriptdeklaration) - wie kann man die Anzahl reduzieren?

12

Ich habe ein Projekt bestehend aus ca. 20 kleinen .shDateien. Ich nenne diese "klein", weil im Allgemeinen keine Datei mehr als 20 Codezeilen enthält. Ich habe einen modularen Ansatz gewählt, da ich der Unix-Philosophie treu bin und es für mich einfacher ist, das Projekt zu pflegen.

Am Anfang jeder .shDatei stelle ich #!/bin/bash.

Einfach ausgedrückt, ich verstehe, dass Skriptdeklarationen zwei Zwecke haben:

  1. Sie helfen dem Benutzer beim Abrufen, welche Shell zum Ausführen der Datei erforderlich ist (beispielsweise nach einigen Jahren ohne Verwendung der Datei).
  2. Sie stellen sicher, dass das Skript nur mit einer bestimmten Shell ausgeführt wird (in diesem Fall Bash), um ein unerwartetes Verhalten bei Verwendung einer anderen Shell zu verhindern.

Wenn ein Projekt von beispielsweise 5 Dateien auf 20 Dateien oder von 20 Dateien auf 50 Dateien wächst (nicht in diesem Fall, sondern nur zur Veranschaulichung), stehen 20 Zeilen oder 50 Zeilen Skriptdeklarationen zur Verfügung. Ich gebe zu, obwohl es für manche witzig sein mag, fühlt es sich für mich ein bisschen überflüssig an, 20 oder 50 zu verwenden und stattdessen nur 1 pro Projekt zu sagen (möglicherweise in der Hauptdatei des Projekts).

Gibt es eine Möglichkeit, diese angebliche Redundanz von 20 oder 50 oder einer viel größeren Anzahl von Zeilen von Skriptdeklarationen zu vermeiden, indem eine "globale" Skriptdeklaration in einer Hauptdatei verwendet wird?

user9303970
quelle
2
Sie sollten nicht aber konnten; entferne es und /bin/bash -x "$script"
führe
... Normalerweise ist es zu dem Zeitpunkt, an dem etwas an diesem Punkt angekommen ist, bereits an der Zeit, die Verwendung von Shell-Skripten einzustellen und eine tatsächliche Skriptsprache für die Arbeit zu erhalten.
Shadur

Antworten:

19

Obwohl Ihr Projekt jetzt möglicherweise nur aus 50 Bash-Skripten besteht, sammelt es früher oder später Skripten, die in anderen Sprachen wie Perl oder Python geschrieben wurden (für die Vorteile, die diese Skriptsprachen gegenüber Bash haben).

Ohne eine richtige #!Zeile in jedem Skript wäre es äußerst schwierig , die verschiedenen Skripte zu verwenden, ohne zu wissen, welcher Interpreter zu verwenden ist. Es spielt keine Rolle, ob jedes einzelne Skript von anderen Skripten ausgeführt wird , dies verschiebt nur die Schwierigkeit von den Endbenutzern zu den Entwicklern. Keine dieser beiden Personengruppen sollte wissen müssen, in welcher Sprache ein Skript geschrieben wurde, um es verwenden zu können.

Shell-Skripte, die ohne #!-line und ohne expliziten Interpreter ausgeführt werden, werden auf unterschiedliche Weise ausgeführt, je nachdem, welche Shell sie aufruft (siehe z. B. die Frage, welcher Shell-Interpreter ein Skript ohne Shebang ausführt? Und insbesondere die Antwort von Stéphane ) in einer Produktionsumgebung (Sie möchten konsistentes Verhalten und möglicherweise sogar Portabilität).

Skripte, die mit einem expliziten Interpreter ausgeführt werden, werden von diesem Interpreter ausgeführt, unabhängig davon, was in der #!-Zeile steht. Dies kann später zu Problemen führen, wenn Sie beispielsweise ein Bash-Skript in Python oder einer anderen Sprache erneut implementieren.

Sie sollten diese zusätzlichen Tastenanschläge verwenden #!und jedem Skript eine -Zeile hinzufügen .


In einigen Umgebungen gibt es in jedem Skript in jedem Projekt mehrteilige Gesetzestexte. Seien Sie sehr froh, dass es nur eine #!Zeile ist, die sich in Ihrem Projekt "überflüssig" anfühlt.

Kusalananda
quelle
Die Antwort sollte folgendermaßen erweitert werden: Die Shell bestimmt den Interpreter über die #!Zeile, sobald die Datei über eine ausführbare Berechtigung verfügt und nicht von einem Interpreter, sondern von der Shell geladen wird. Das heißt, es wird /path/to/myprog.plein Perl-Programm ausgeführt, wenn die #!Zeile vorhanden ist (auf den Perl-Interpreter zeigt) und die Datei über die Ausführungsberechtigung verfügt.
Rexkogitans
11

Sie verstehen den Punkt falsch #!. Es ist eigentlich eine Anweisung an das Betriebssystem, dieses Programm unter dem definierten Interpreter auszuführen.

Es könnte also ein Skript sein #!/usr/bin/perlund das resultierende Programm kann ausgeführt werden, ./myprogramund der Perl-Interpreter könnte verwendet werden. Ähnlich #!/usr/bin/pythonwürde ein Programm unter Python laufen.

Die Zeile #!/bin/bashweist das Betriebssystem an, dieses Programm unter bash auszuführen.

Hier ist ein Beispiel:

$ echo $0
/bin/ksh

$ cat x
#!/bin/bash

ps -aux | grep $$

$ ./x
sweh      2148  0.0  0.0   9516  1112 pts/5    S+   07:58   0:00 /bin/bash ./x
sweh      2150  0.0  0.0   9048   668 pts/5    S+   07:58   0:00 grep 2148

Also, obwohl meine Shell ksh ist, läuft das Programm "x" wegen der #!Zeile unter bash , und das können wir explizit in der Prozessauflistung sehen.

Stephen Harris
quelle
ps -auxkann eine Warnung geben,
Weijun Zhou
@ WeijunZhou Sagen Sie mehr ...
Kusalananda
Eine Version von psgibt die Warnung Warning: bad syntax, perhaps a bogus '-'?.
Weijun Zhou
2
pssuooprts sowohl BSD- als auch UXIX- Argument-Syntax. -auxist falsch UNIX Stephen bedeutet wahrscheinlich, auxdie richtige BSD ist
Jasen
1
Leute, 2 Dinge; (1) Konzentrieren Sie sich auf das Beispielkonzept (das psden Aufruf anzeigt bash) und nicht auf die explizite Syntax. (2) ps -auxfunktioniert einwandfrei unter CentOS 7 und Debian 9 ohne Warnungen; Diese Cut'n'Paste war genau die Ausgabe von meiner Maschine. Die ganze Diskussion, ob das -benötigt wird oder nicht, gilt für ältere Versionen von Linux Procps, nicht für aktuelle Versionen.
Stephen Harris
9

Auf .sh

Was schlecht ist, ist das .sham Ende des Dateinamens.

Stellen Sie sich vor, Sie schreiben eines der Skripte in Python neu.

Nun muss man die erste Zeile ändern, das #!/usr/bin/python3ist nicht so schlimm, da man auch jede andere Codezeile in der Datei ändern musste. Sie müssen jedoch auch den Dateinamen von prog.shin ändern prog.py. Und dann müssen Sie überall in den anderen Skripten und all Ihren Benutzerskripten (die, von denen Sie nicht einmal wussten, dass sie existieren) suchen und sie ändern, um das neue Skript zu verwenden. sed -e 's/.sh/.py/g'kann für diejenigen, die Sie kennen, helfen. Aber jetzt zeigt Ihr Revision Control Tool eine Änderung in vielen nicht verwandten Dateien.

Alternativ machen Sie es auf Unix-Art und benennen das Programm prognicht prog.sh.

Auf #!

Wenn Sie die richtige haben #!, und legen Sie die Berechtigung / den Modus fest, um die Ausführung einzuschließen. Dann brauchen Sie den Dolmetscher nicht zu kennen. Der Computer erledigt das für Sie.

chmod +x prog #on gnu adds execute permission to prog.
./prog #runs the program.

Für Nicht-Gnu-Systeme lesen Sie das Handbuch für chmod(und lernen Sie Oktal), vielleicht lesen Sie es trotzdem.

Strg-Alt-Delor
quelle
1
Hmmm, Erweiterungen sind nicht unbedingt schlecht, zumindest helfen sie dabei, mit anderen Benutzern über den Dateityp zu kommunizieren.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy Aber Sie müssen die Sprache nicht kennen, Sie müssen sie nur ausführen. Sogar Microsoft versucht, sich von Erweiterungen zu entfernen (leider tun sie dies, indem ich sie verstecke). Ich stimme jedoch zu, dass sie eine gute Idee sein könnten: .imagefür Bilder. .audiofür Ton usw. Damit sagst du, worum es geht, aber nicht um die Implementierung / Codierung.
Strg-Alt-Delor
1
@ ctrl-alt-delor Wenn die Datei aufgerufen wird launch-missilesoder delete-project-and-make-manager-pissedich nicht "nur ausführen" möchte, wenn ich nur neugierig auf die Sprache war :)
Sergiy Kolodyazhnyy
2
Wenn Sie neugierig auf die Sprache sind, verwenden Sie den fileBefehl. Es erkennt die meisten Dateitypen.
Jasen,
2
Ich bin damit einverstanden, dass Erweiterungen für ausführbare Skripte aus genau den angegebenen Gründen schlecht sind. Ich verwende eine .sh-Erweiterung für ein Skript, das von einem anderen Shell-Skript (dh einem nicht ausführbaren Skript) bezogen werden muss. In diesem Szenario lautet das Suffix wichtig / gültig, weil es uns sagt, wie wir die Datei verwenden können.
Laurence Renshaw
8

[Die Hashbang-Zeilen] stellen sicher, dass das Skript nur mit einer bestimmten Shell ausgeführt wird (in diesem Fall Bash), um unerwartetes Verhalten zu verhindern, falls eine andere Shell verwendet wurde.

Es ist wahrscheinlich erwähnenswert, dass dies völlig falsch ist. Die Hashbang-Zeile hindert Sie in keiner Weise daran, die Datei einem falschen Shell / Interpreter zuzuführen:

$ cat array.sh
#!/bin/bash
a=(a b c)
echo ${a[1]} $BASH_VERSION
$ dash array.sh
array.sh: 2: array.sh: Syntax error: "(" unexpected

(oder ähnlich mit awk -f array.shetc.)


Ein anderer Ansatz zur Modularität wäre jedoch, Shell-Funktionen in den Dateien zu definieren, eine Funktion pro Datei und dann sourcedie Dateien aus dem Hauptprogramm. Auf diese Weise würden Sie keine Hashbangs benötigen: Sie würden wie alle anderen Kommentare behandelt, und alles würde mit derselben Shell ausgeführt. Der Nachteil wäre, dass Sie sourcedie Dateien mit den Funktionen (z. B. for x in functions/*.src ; do source "$x" ; done) benötigen und sie alle mit derselben Shell ausgeführt werden, sodass Sie eine davon nicht direkt durch eine Perl-Implementierung ersetzen können.

ilkkachu
quelle
+1 zum Beispiel mit Bash-Arrays. Benutzer, die nicht mit mehreren Shells oder mit POSIX-Definitionen vertraut sind, können davon ausgehen, dass einige Funktionen einer Shell in einer anderen vorhanden sind. Auch für Funktionen kann dies relevant sein: unix.stackexchange.com/q/313256/85039
Sergiy Kolodyazhnyy
4

Gibt es eine Möglichkeit, diese angebliche Redundanz von 20 oder 50 oder einer viel größeren Anzahl von Zeilen von Skriptdeklarationen zu vermeiden, indem eine "globale" Skriptdeklaration in einer Hauptdatei verwendet wird?

Es gibt - es heißt Portabilität oder POSIX-Konformität. Bemühen Sie sich, Skripts immer portabel und shellneutral zu schreiben, und verwenden Sie nur Skripts, die #!/bin/shSie verwenden oder wenn Sie sie verwenden/bin/sh interaktiv verwenden (oder zumindest aus einer POSIX-kompatiblen Shell wie z. B. ksh).

Portable Skripte schützen Sie jedoch nicht vor:

  • Merkmale einer der Muscheln verwenden müssen ( ilkkachus Antwort) ist ein Beispiel)
  • Verwenden von Skriptsprachen, die fortgeschrittener sind als Shells (Python und Perl)
  • PEBKAC-Fehler: Ihr Skript fällt in die Hände des Benutzers J.Doe, der Ihr Skript nicht so ausführt, wie Sie es beabsichtigt haben. Beispielsweise führen sie /bin/shSkripts mit aus csh.

Wenn ich meine Meinung äußern darf, ist "Vermeidung von Redundanzen" durch Eliminierung #!ein falsches Ziel. Das Ziel sollte sein , konsistente Leistung und tatsächlich ein funktionierendes Skript , das funktioniert, und hält Ecke Fällen folgt einige allgemeine Regeln , wie nicht-Parsing-ls oder immer-Zitat-Variablen-es sei denn-need-Wort-Splitting .

Sie helfen dem Benutzer beim Abrufen, welche Shell zum Ausführen der Datei erforderlich ist (beispielsweise nach einigen Jahren ohne Verwendung der Datei).

Sie sind wirklich nicht für Benutzer - sie sind für das Betriebssystem, um den richtigen Interpreter auszuführen. "Richtig" bedeutet in diesem Fall, dass das Betriebssystem das findet, was in shebang enthalten ist. Wenn der Code darunter für eine falsche Shell steht - das ist die Schuld des Autors. Was den Benutzerspeicher angeht, glaube ich nicht, dass "Benutzer" von Programmen wie Microsoft Word oder Google Chrome jemals wissen müssen, in welcher Sprache Programme geschrieben sind. Autoren benötigen möglicherweise.

Andererseits müssen Sie sich bei portabel geschriebenen Skripten möglicherweise nicht mehr merken, welche Shell Sie ursprünglich verwendet haben, da portable Skripte in jeder POSIX-kompatiblen Shell funktionieren sollten.

Sie stellen sicher, dass das Skript nur mit einer bestimmten Shell ausgeführt wird (in diesem Fall Bash), um ein unerwartetes Verhalten bei Verwendung einer anderen Shell zu verhindern.

Sie tun es nicht. Was unerwartetes Verhalten tatsächlich verhindert, ist das Schreiben portabler Skripte.

Sergiy Kolodyazhnyy
quelle
1

Der Grund, warum Sie #!/bin/bashjedes Skript oben einfügen müssen, ist, dass Sie es als separaten Prozess ausführen.

Wenn Sie ein Skript als neuen Prozess ausführen, erkennt das Betriebssystem, dass es sich um eine Textdatei handelt (im Gegensatz zu einer ausführbaren Binärdatei), und es muss bekannt sein, welcher Interpreter ausgeführt werden soll. Wenn Sie es nicht mitteilen, kann das Betriebssystem nur anhand der Standardeinstellung raten (die ein Administrator ändern kann). Die #!Zeile (und natürlich das Hinzufügen von ausführbaren Berechtigungen) verwandelt eine einfache Textdatei in eine ausführbare Datei, die direkt ausgeführt werden kann, mit der Gewissheit, dass das Betriebssystem weiß, wie es damit umzugehen ist.

Wenn Sie die #!Linie entfernen möchten , haben Sie folgende Optionen:

  1. Führen Sie das Skript direkt aus (so wie Sie es jetzt tun) und hoffen Sie, dass der Standardinterpreter mit dem Skript übereinstimmt - Sie sind wahrscheinlich auf den meisten Linux-Systemen sicher, aber nicht auf andere Systeme (z. B. Solaris) übertragbar, und es kann in geändert werden alles (ich könnte es sogar so einstellen, dass es in vimder Datei läuft !).

  2. Führen Sie das Skript mit aus bash myscript.sh. Dies funktioniert gut, aber Sie müssen den Pfad zum Skript angeben, und es ist chaotisch.

  3. Quell das Skript mit . myscript.sh, das das Skript innerhalb des aktuellen Interpreter-Prozesses ausführt (dh nicht als Unterprozess). Dies erfordert jedoch mit ziemlicher Sicherheit Änderungen an Ihren Skripten und macht sie weniger modular / wiederverwendbar.

In den Fällen 2 und 3 benötigt die Datei keine Ausführungsberechtigungen (und sollte diese auch nicht haben), und es ist eine gute Idee , ihr ein .sh(oder .bash) Suffix zu geben.

Aber keine dieser Optionen passt zu Ihrem Projekt, da Sie sagten, Sie möchten Ihre Skripte modular halten (=> unabhängig und in sich geschlossen), also sollten Sie Ihre #!/bin/bashZeile ganz oben in den Skripten belassen.

In Ihrer Frage sagten Sie auch, dass Sie der Unix-Philosophie folgen. Wenn das stimmt, sollten Sie lernen, wofür die #!Zeile wirklich ist, und sie weiterhin in jedem ausführbaren Skript verwenden!

Laurence Renshaw
quelle
Danke für die gute Antwort. Ich liebte alles in der Antwort und dachte an upvoting , bis diese: then you should learn what the #! line is really for, and keep using it in every executable script!. Man kann der U.Philosophie bis zu einem gewissen Punkt des Wissens folgen. Nach all diesen Antworten habe ich verstanden, warum es in diesen Begriff aufgenommen werden kann. Ich schlage vor, die Antwort zu bearbeiten und durch "Sehen Sie den Shebang als internen Teil der Unix-Philosophie, die Sie respektieren und übernehmen" zu ersetzen.
user9303970
1
Tut mir leid, wenn ich direkt war oder wenn dieser Kommentar unhöflich schien. Aus Ihren Kommentaren ging hervor, dass Sie es vorziehen, in einer IDE zu arbeiten - mit einem "Projekt", mit dem Sie globale Regeln für die Skripte in diesem Projekt definieren können. Das ist eine gültige Entwicklungsmethode, passt aber nicht gut dazu, jedes Skript als eigenständiges Programm zu behandeln (die Unix-Philosophie, auf die Sie sich bezogen haben).
Laurence Renshaw