Normalerweise arbeite ich mit Ubuntu LTS-Servern, von denen ich verstehe, dass sie mit Symlink verbunden /bin/sh
sind /bin/dash
. Viele andere Distributionen, obwohl Symlink /bin/sh
zu /bin/bash
.
Daraus ergibt sich, dass ein Skript, das #!/bin/sh
oben verwendet wird, möglicherweise nicht auf allen Servern auf die gleiche Weise ausgeführt wird.
Gibt es eine empfohlene Vorgehensweise für die Shell, die für Skripte verwendet werden soll, wenn eine maximale Portabilität dieser Skripte zwischen Servern gewünscht wird?
#!/bin/sh
nichts anderes als die von der Original-Shell bereitgestellte.Antworten:
Es gibt ungefähr vier Portabilitätsstufen für Shell-Skripte (in Bezug auf die Shebang-Linie):
Am portabelsten: Verwenden Sie einen
#!/bin/sh
Shebang und nur die im POSIX-Standard angegebene grundlegende Shell-Syntax . Dies sollte auf so ziemlich jedem POSIX / Unix / Linux-System funktionieren. (Nun, mit Ausnahme von Solaris 10 und früheren Versionen, die die echte Bourne-Legacy-Shell besaßen, die vor POSIX so nicht kompatibel war, wie/bin/sh
.)Am zweithäufigsten tragbar: Verwenden Sie eine
#!/bin/bash
(oder#!/usr/bin/env bash
) Shebang-Linie und halten Sie sich an die Funktionen von Bash V3. Dies funktioniert auf jedem System, auf dem bash installiert ist (am erwarteten Speicherort).Am dritthäufigsten tragbar: Verwenden Sie eine
#!/bin/bash
(oder#!/usr/bin/env bash
) Shebang-Linie und verwenden Sie bash v4-Funktionen. Dies schlägt auf allen Systemen fehl, auf denen bash v3 installiert ist (z. B. macOS, das es aus Lizenzgründen verwenden muss).Am wenigsten portabel: Verwenden Sie einen
#!/bin/sh
Shebang und Bash-Erweiterungen für die POSIX-Shell-Syntax. Dies schlägt auf jedem System fehl, das etwas anderes als Bash für / bin / sh enthält (z. B. neuere Ubuntu-Versionen). Mach das niemals; es ist nicht nur ein Kompatibilitätsproblem, es ist einfach falsch. Leider ist es ein Fehler, den viele Leute machen.Meine Empfehlung: Verwenden Sie die konservativste der ersten drei, die alle Shell-Funktionen bietet, die Sie für das Skript benötigen. Verwenden Sie für maximale Portabilität Option 1, aber meiner Erfahrung nach sind einige Bash-Funktionen (wie Arrays) hilfreich genug, um mit # 2 fortzufahren.
Das Schlimmste, was Sie tun können, ist # 4, wenn Sie den falschen Shebang verwenden. Wenn Sie sich nicht sicher sind, welche Funktionen POSIX-Basisfunktionen und welche Bash-Erweiterungen sind, halten Sie sich entweder an einen Bash-Schebang (Option 2) oder testen Sie das Skript gründlich mit einer sehr einfachen Shell (wie dash auf Ihren Ubuntu LTS-Servern). Das Ubuntu-Wiki hat eine gute Liste von Bashismen, auf die man achten sollte .
In der Unix- und Linux-Frage "Was bedeutet es, sh-kompatibel zu sein?" Finden Sie einige sehr gute Informationen zur Geschichte und zu den Unterschieden zwischen Shells. und die Stackoverflow-Frage "Unterschied zwischen sh und bash" .
Beachten Sie auch, dass die Shell nicht das einzige ist, das sich zwischen verschiedenen Systemen unterscheidet. Wenn Sie an Linux gewöhnt sind, sind Sie an die GNU-Befehle gewöhnt, die viele nicht standardmäßige Erweiterungen haben, die Sie auf anderen Unix-Systemen (z. B. bsd, macOS) möglicherweise nicht finden. Leider gibt es hier keine einfache Regel. Sie müssen lediglich den Variationsbereich für die von Ihnen verwendeten Befehle kennen.
Einer der übelsten Befehle in Bezug auf die Portabilität ist eine der grundlegendsten:
echo
. Jedes Mal, wenn Sie sie mit Optionen (z. B.echo -n
oderecho -e
) oder mit Escapezeichen (Backslashes) in der zu druckenden Zeichenfolge verwenden, werden verschiedene Versionen unterschiedliche Aktionen ausführen. Verwenden Sieprintf
stattdessen, wenn Sie eine Zeichenfolge ohne Zeilenvorschub oder mit Escapezeichen in der Zeichenfolge drucken möchten (und erfahren Sie, wie dies funktioniert - es ist komplizierter als esecho
ist). Derps
Befehl ist auch ein Durcheinander .Ein weiterer allgemeiner Punkt, auf den Sie achten sollten, ist die jüngste / GNUish-Erweiterung der Befehlsoptionssyntax: Altes (Standard-) Befehlsformat: Auf den Befehl folgen Optionen (mit einem Bindestrich, und jede Option ist ein einzelner Buchstabe), gefolgt von Befehlsargumente. Neuere (und häufig nicht portierbare) Varianten enthalten lange Optionen (normalerweise eingeführt mit
--
), mit denen Optionen nach Argumenten folgen und--
Optionen von Argumenten trennen können.quelle
sh
), sodass/bin/sh
nicht garantiert wird, dass es sich um den richtigen Pfad handelt. Am portabelsten ist es dann, gar keinen shebang anzugeben oder den shebang an das verwendete Betriebssystem anzupassen.#!/bin/sh
um#!/bin/bash
perfekt aber funktionierte das Skript. (Zu meiner Verteidigung hatte sich dieses Drehbuch im Laufe der Zeit von einem, das wirklich nur Sh-Ismen brauchte, zu einem entwickelt, das sich auf bash-artiges Verhalten stützte.)ksh
Verhalten erstellt, nicht aus Bourne. Was die meisten Linux-Distributionen nach dem FHS tun,/bin/sh
ist eine symbolische Verknüpfung mit der Shell, die sie auswählen, um normalerweisebash
oder in der Regel POSIX-Kompatibilität bereitzustellendash
.In dem
./configure
Skript, das die TXR-Sprache für die Erstellung vorbereitet, habe ich den folgenden Prolog geschrieben, um die Portabilität zu verbessern. Das Skript bootet sich selbst dann, wenn#!/bin/sh
es sich um eine nicht POSIX-konforme alte Bourne-Shell handelt. (Ich erstelle jede Version auf einer Solaris 10-VM).Die Idee dabei ist, dass wir eine bessere Shell als die finden, unter der wir laufen, und das Skript mit dieser Shell erneut ausführen. Die
txr_shell
Umgebungsvariable ist so festgelegt, dass das erneut ausgeführte Skript weiß, dass es sich um die erneut ausgeführte rekursive Instanz handelt.(In meinem Skript wird die
txr_shell
Variable auch nachträglich für genau zwei Zwecke verwendet: Erstens wird sie als Teil einer Informationsnachricht in der Ausgabe des Skripts gedruckt. Zweitens wird sie alsSHELL
Variable in der installiertMakefile
, damit diese verwendetmake
wird Shell auch zum Ausführen von Rezepten.)Auf einem System mit
/bin/sh
Bindestrich können Sie sehen, dass die obige Logik/bin/bash
das Skript damit findet und erneut ausführt.Auf einer Solaris 10-Box wird die
/usr/xpg4/bin/sh
aktiviert, wenn kein Bash gefunden wird.Der Prolog ist in einem konservativen Shell-Dialekt geschrieben, der
test
für die Prüfung der Existenz von Dateien verwendet wird, und der${@+"$@"}
Trick, Argumente zu erweitern, die sich auf einige zerbrochene alte Shells beziehen (was einfach der Fall wäre,"$@"
wenn wir uns in einer POSIX-konformen Shell befinden).quelle
x
Hackery nicht brauchen, wenn das richtige Zitieren verwendet würde, da die Situationen, die diese Redewendung erforderten , jetzt veraltete Testaufrufe mit mehreren Tests verbinden-a
oder diese-o
kombinieren.test x$whatever
, was ich dort tue, sieht aus wie eine Zwiebel im Lack. Wenn wir der kaputten alten Shell nicht vertrauen können, ist der letzte${@+"$@"}
Versuch sinnlos.Alle Variationen der Bourne-Shell-Sprache sind im Vergleich zu modernen Skriptsprachen wie Perl, Python, Ruby, node.js und sogar (wohl) Tcl objektiv schrecklich. Wenn Sie etwas kompliziertes tun müssen, werden Sie auf lange Sicht glücklicher sein, wenn Sie eines der oben genannten anstelle eines Shell-Skripts verwenden.
Der einzige Vorteil, den die Shell-Sprache gegenüber diesen neueren Sprachen noch hat, ist, dass etwas , das sich selbst nennt
/bin/sh
, auf allem, das vorgibt, Unix zu sein, garantiert existiert. Dies ist jedoch möglicherweise nicht einmal POSIX-konform. viele der Legacy - proprietäre Unix - Varianten froren die Sprache umgesetzt durch/bin/sh
und die Dienstprogramme in dem Standardpfad vor den Änderungen durch Unix95 gefordert (ja, Unix95, vor zwanzig Jahren und Zählen). Möglicherweise gibt es eine Reihe von Unix95- oder sogar POSIX.1-2001-Tools, wenn Sie Glück haben, die sich in einem Verzeichnis befinden, das sich nicht auf dem Standardpfad befindet (z. B./usr/xpg4/bin
), deren Existenz jedoch nicht garantiert ist.Es ist jedoch wahrscheinlicher , dass die Grundlagen von Perl in einer willkürlich ausgewählten Unix-Installation vorhanden sind, als dies bei Bash der Fall ist. (Mit "den Grundlagen von Perl" meine ich, dass
/usr/bin/perl
es eine , möglicherweise ziemlich alte, Version von Perl 5 gibt, und wenn Sie Glück haben, sind auch die Module verfügbar, die mit dieser Version des Interpreters geliefert wurden.)Deshalb:
Wenn Sie etwas schreiben, das überall dort funktionieren muss, wo es sich um Unix handelt (z. B. ein "configure" -Skript), müssen Sie es verwenden
#! /bin/sh
, und Sie müssen keinerlei Erweiterungen verwenden. Heutzutage würde ich unter diesen Umständen eine POSIX.1-2001-kompatible Shell schreiben, aber ich wäre bereit, POSIXisms auszubessern, wenn jemand um Unterstützung für rostiges Eisen bittet.Wenn Sie jedoch nicht überall etwas schreiben, das funktionieren muss, sollten Sie in dem Moment, in dem Sie versucht sind, Bashismus überhaupt zu verwenden, das Ganze stoppen und stattdessen in einer besseren Skriptsprache umschreiben. Dein zukünftiges Ich wird es dir danken.
(Wann ist es angebracht, Bash-Erweiterungen zu verwenden? Bei der ersten Bestellung: Nie. Bei der zweiten Bestellung: Nur zur Erweiterung der interaktiven Bash-Umgebung - z.
quelle
find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -
. Ohne bashisms (read -d ""
) und GNU-Erweiterungen (find -print0
,tar --null
) würde es nicht richtig funktionieren . Die Neuimplementierung dieser Zeile in Perl wäre viel länger und umständlicher. Dies ist die Art von Situation, in der die Verwendung von Bashismen und anderen Nicht-POSIX-Erweiterungen das Richtige ist.perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmd
ungefähr so aus. Beachten Sie, dass Sie nicht nur keine Bashismen, sondern auch keine GNU-Tar-Erweiterungen benötigen. (Wenn die Befehlszeilenlänge ein Problem ist oder Sie möchten, dass Scan und Teer parallel ausgeführt werden, müssen Sie dies tuntar --null -T
.)find
über 50.000 Dateinamen in meinem Szenario zurückgegeben, so dass Ihr Skript wahrscheinlich das Argument Längenlimit erreicht. Eine korrekte Implementierung, die keine Nicht-POSIX-Erweiterungen verwendet, müsste die tar-Ausgabe inkrementell erstellen oder möglicherweise Archive :: Tar :: Stream im Perl-Code verwenden. Natürlich kann es gemacht werden, aber in diesem Fall ist das Bash-Skript viel schneller zu schreiben und hat weniger Boilerplate und daher weniger Platz für Bugs.find ... -print0 |perl -0ne '... print ...' |tar c --null -T -
. Aber die Fragen sind: 1) Ich benutzefind -print0
undtar --null
trotzdem, was ist der Sinn des Vermeidens des Bashismusread -d ""
, der genau die gleiche Sache ist (eine GNU-Erweiterung für den Umgang mit Null-Separatoren, die im Gegensatz zu Perl eines Tages zu einer POSIX-Sache werden kann )? 2) Ist Perl wirklich weiter verbreitet als Bash? Ich habe kürzlich Docker-Images verwendet, und viele von ihnen haben Bash, aber kein Perl. Es ist wahrscheinlicher, dass dort neue Skripte ausgeführt werden als auf alten Unices.