#! / bin / sh vs #! / bin / bash für maximale Portabilität

cherouvim 07/30/2017. 3 answers, 2.886 views
linux bash shell sh

Ich arbeite normalerweise mit Ubuntu LTS-Servern, was ich symlink /bin/sh zu /bin/dash verstehe. Eine Menge anderer Distributionen, obwohl symlink /bin/sh zu /bin/bash .

Daran erkenne ich, dass wenn ein Skript #!/bin/sh es nicht auf allen Servern gleich laufen kann?

Gibt es eine empfohlene Vorgehensweise für die Shell, die für Skripts verwendet werden soll, wenn eine maximale Portabilität dieser Skripts zwischen Servern gewünscht wird?

1 Comments
Thorbjørn Ravn Andersen 08/01/2017
Es gibt kleine Unterschiede zwischen den verschiedenen Schalen. Wenn Portabilität für Sie am wichtigsten ist, verwenden Sie #!/bin/sh und verwenden Sie nichts anderes als die ursprüngliche Shell.

3 Answers


Gordon Davisson 08/01/2017.

Es gibt ungefähr vier Portabilitätsebenen für Shell-Skripte (soweit es die Shebang-Zeile betrifft):

  1. Most portable: Verwenden Sie ein #!/bin/sh shebang und verwenden Sie only die im POSIX-Standard spezifizierte grundlegende Shell-Syntax. Dies sollte auf jedem POSIX / Unix / Linux-System funktionieren. (Nun, außer Solaris 10 und früher, die die echte Bourne-Vorgänger-Shell hatte, die vor POSIX nicht konform war, als /bin/sh .)

  2. Zweiter Portabel: Verwenden Sie eine #!/bin/bash (oder #!/usr/bin/env bash ) Shebang-Zeile und bleiben Sie bei den v3-Funktionen. Dies funktioniert auf jedem System mit bash (am erwarteten Ort).

  3. Drittes Portabel: Verwenden Sie eine #!/bin/bash (oder #!/usr/bin/env bash ) Shebang-Zeile und verwenden Sie bash v4 features. Dies schlägt auf jedem System mit bash v3 fehl (zB macOS, das es aus Lizenzgründen verwenden muss).

  4. Least portable: Verwenden Sie einen #!/bin/sh shebang und verwenden Sie bash-Erweiterungen zur POSIX-Shell-Syntax. Dies wird auf jedem System fehlschlagen, das etwas anderes als bash für / bin / sh hat (wie die letzten Ubuntu-Versionen). Tue 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-Features, die Sie für das Skript benötigen, zur Verfügung stellt. Für maximale Portabilität, benutze Option # 1, aber meiner Erfahrung nach sind einige Bash-Funktionen (wie Arrays) hilfreich genug, um mit # 2 zu gehen.

Das Schlimmste, was Sie tun können, ist # 4, mit dem falschen Shebang. Wenn Sie nicht sicher sind, welche Funktionen basic POSIX und welche Bash-Erweiterungen sind, bleiben Sie entweder bei einem Bash Shebang (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 .

Es gibt einige sehr gute Informationen über die Geschichte und die Unterschiede zwischen Shells in der Unix & Linux Frage "Was bedeutet es sh kompatibel zu sein?" und die Stackoverflow-Frage "Unterschied zwischen sh und bash" .

Beachten Sie auch, dass die Shell nicht die einzige Sache ist, die zwischen verschiedenen Systemen unterscheidet; Wenn Sie an Linux gewöhnt sind, sind Sie an die GNU-Befehle gewöhnt, die viele nicht standardisierte Erweiterungen haben, die Sie auf anderen Unix-Systemen (zB bsd, macOS) nicht finden können. Leider gibt es hier keine einfache Regel, Sie müssen nur die Variationsbreite der von Ihnen verwendeten Befehle kennen.

Einer der gemeinsten Befehle in Bezug auf Portabilität ist einer der grundlegendsten: echo . Jedes Mal, wenn Sie es mit Optionen (z. B. echo -n oder echo -e ) oder mit beliebigen Escapes (Backslashes) in der zu druckenden Zeichenfolge verwenden, werden verschiedene Versionen unterschiedliche Dinge tun. Immer wenn Sie einen String ohne Zeilenvorschub oder mit Escapes im String drucken möchten, verwenden Sie stattdessen printf (und erfahren Sie, wie es funktioniert - es ist komplizierter als das echo ). Der Befehl ps ist auch ein Durcheinander .

Eine weitere allgemeine Sache, auf die man achten sollte, sind kürzliche / GNUish-Erweiterungen der Befehlssyntax: Das alte (Standard) Befehlsformat besteht darin, dass dem Befehl Optionen folgen (mit einem einzigen Bindestrich und jeder Option ein einzelner Buchstabe), gefolgt von Befehlsargumente. Kürzliche (und oft nicht portable) Varianten enthalten lange Optionen (normalerweise mit -- ), die es ermöglichen, dass Optionen nach Argumenten kommen und -- um Optionen von Argumenten zu trennen.

5 comments
12 pabouk 07/30/2017
Die vierte Option ist einfach eine falsche Idee. Bitte benutze es nicht.
3 Gordon Davisson 07/30/2017
@pabouk Ich stimme völlig zu, also habe ich meine Antwort bearbeitet, um das klarer zu machen.
1 jlliagre 07/30/2017
Ihre erste Aussage ist leicht irreführend. Der POSIX-Standard spezifiziert nichts über das Shebang-Outside-Telling, das zu unspezifiziertem Verhalten führt. Außerdem gibt POSIX nicht an, wo sich die Posix-Shell befinden soll, sondern nur den Namen ( sh ), also ist /bin/sh nicht der richtige Pfad. Am portabelsten ist es dann überhaupt keinen Shebang anzugeben oder den Shebang an das verwendete Betriebssystem anzupassen.
2 Michael Kjörling 07/30/2017
Ich bin kürzlich mit einem meiner Skripte in # 4 eingestiegen und konnte einfach nicht herausfinden, warum es nicht funktionierte. schließlich funktionierten die gleichen Befehle zuverlässig und taten genau das, was ich von ihnen wollte, als ich sie direkt in der Shell ausprobierte. Sobald ich #!/bin/sh zu #!/bin/bash änderte, funktionierte das Skript einwandfrei. (Zu meiner Verteidigung hat sich dieses Skript im Laufe der Zeit von einem entwickelt, das wirklich nur Sh-Ismen benötigt, zu einem, das sich auf bash-ähnliches Verhalten stützt.)
2 jlliagre 07/30/2017
@ MichaelKjörling Die (wahre) Bourne-Shell ist fast nie mit Linux-Distributionen gebündelt und ist sowieso nicht POSIX-konform. Die POSIX-Standard-Shell wurde aus dem ksh Verhalten erstellt, nicht aus Bourne. Die meisten Linux-Distributionen, die FHS folgen, verwenden /bin/sh als symbolische Verknüpfung zu der Shell, die sie für die POSIX-Kompatibilität auswählen, normalerweise bash oder dash .

Kaz 07/31/2017.

Im Skript ./configure das die TXR-Sprache für das ./configure vorbereitet, schrieb ich den folgenden Prolog für bessere Portabilität. Das Skript bootet selbst, selbst wenn #!/bin/sh eine nicht POSIX-konforme alte Bourne-Shell ist. (Ich erstelle jede Version auf einer Solaris 10-VM).

#!/bin/sh

# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells

if test x$txr_shell = x ; then
  for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
    if test -x $shell ; then
       txr_shell=$shell
       break
    fi
  done
  if test x$txr_shell = x ; then
    echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
    txr_shell=/bin/sh
  fi
  export txr_shell
  exec $txr_shell $0 ${@+"$@"}
fi

# rest of the script here, executing in upgraded shell 

Die Idee hier ist, dass wir eine bessere Shell finden als die, unter der wir laufen, und das Skript mit dieser Shell erneut ausführen. Die Umgebungsvariable txr_shell wird festgelegt, sodass das erneut ausgeführte Skript weiß, dass es sich um die erneut ausgeführte rekursive Instanz handelt.

(In meinem Skript wird die Variable txr_shell auch anschließend für genau zwei Zwecke verwendet: Erstens wird sie als Teil einer informativen Nachricht in der Ausgabe des Skripts gedruckt. Zweitens wird sie als die SHELL Variable im Makefile installiert, so dass make verwendet diese Shell auch zum Ausführen von Rezepten.)

Auf einem System, in dem /bin/sh ein Bindestrich ist, können Sie sehen, dass die obige Logik /bin/bash und das Skript damit erneut ausführt.

Auf einer Solaris 10-Box wird /usr/xpg4/bin/sh wenn keine Bash gefunden wird.

Der Prolog ist in einem konservativen Shell-Dialekt geschrieben, wobei test für Datei-Existenztests verwendet werden, und der ${@+"$@"} -Trick zum Erweitern von Argumenten, die sich auf einige kaputte alte Shells beziehen (was einfach "$@" wäre) in einer POSIX-konformen Shell).

2 comments
Charles Duffy 07/31/2017
Man würde die x Hackerei nicht benötigen, wenn die richtigen Zitate verwendet würden, da die Situationen, die diese Idiom-Umgebung jetzt umgaben, die Testaufrufe mit -a oder -o die mehrere Tests kombinieren, verwarf.
Kaz 07/31/2017
@CharlesDuffy Tatsächlich; der test x$whatever ich dort mache, sieht wie eine zwiebel im lack aus. Wenn wir der gebrochenen alten Shell nicht trauen können, zu quotieren, dann ist der letzte ${@+"$@"} Versuch sinnlos.

zwol 07/31/2017.

Alle Varianten der Bourne-Shell-Sprache sind im Vergleich zu modernen Skriptsprachen wie Perl, Python, Ruby, node.js und sogar (wohl wohl) Tcl objektiv schrecklich. Wenn Sie etwas auch nur ein bisschen kompliziert machen müssen, werden Sie auf lange Sicht glücklicher sein, wenn Sie eines der oben genannten anstelle eines Shell-Skripts verwenden.

Der einzige Vorteil der Shell-Sprache gegenüber diesen neueren Sprachen besteht darin, dass something , das sich selbst /bin/sh nennt, garantiert bei allem vorhanden ist, was angeblich Unix ist. Dies ist jedoch möglicherweise nicht einmal POSIX-konform. Viele der proprietären Legacy-Unixes froren die Sprache, die von /bin/sh implementiert wurde, und die Dienstprogramme im Standard-PATH prior den von Unix95 geforderten Änderungen ein (ja, Unix95, vor zwanzig Jahren und gezählt). Es könnte eine Reihe von Unix95 oder sogar POSIX.1-2001 geben, wenn Sie Glück haben, Werkzeuge in einem Verzeichnis not auf dem Standardpfad (zB /usr/xpg4/bin ), aber sie sind nicht garantiert zu existieren.

Die Grundlagen von Perl sind more likely bei einer beliebig gewählten Unix-Installation more likely als bei Bash. (Mit "die Grundlagen von Perl", ich meine /usr/bin/perl existiert und ist some , vielleicht ziemlich alte, Version von Perl 5, und wenn Sie Glück haben, sind auch die Module, die mit dieser Version des Interpreters geliefert wurden verfügbar.)

Deshalb:

Wenn du etwas schreibst, das everywhere funktionieren soll und Unix sein soll (wie zum Beispiel ein "configure" -Skript), musst du #! /bin/sh #! /bin/sh , und Sie müssen keinerlei Erweiterungen verwenden. Heutzutage würde ich unter diesen Umständen POSIX.1-2001-konforme Shell schreiben, aber ich wäre bereit, POSIXisms auszufüllen, wenn jemand um Unterstützung für rostiges Eisen bittet.

Aber wenn du not etwas schreibst, das überall funktionieren muss, dann solltest du in dem Moment, in dem du versucht bist, irgendeinen Bashismus zu benutzen, aufhören und das Ganze stattdessen in einer besseren Skriptsprache schreiben. Dein zukünftiges Selbst wird dir danken.

(Also wann is es angebracht, Bash-Erweiterungen zu verwenden? Zur ersten Bestellung: Niemals. Zur zweiten Bestellung: Nur um die interaktive Bash-Umgebung zu erweitern - z. B. um intelligente Tabulator-Vervollständigung und spezielle Eingabeaufforderungen bereitzustellen.)

Related questions

Hot questions

Language

Popular Tags