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


Ich arbeite normalerweise mit Ubuntu-LTS-Servern, was ich symlink verstehe /bin/sh zu /bin/dash. Viele andere Distributionen obwohl Symlink /bin/sh zu /bin/bash.

Davon verstehe ich, dass wenn ein Skript verwendet #!/bin/sh Obendrein funktioniert es möglicherweise nicht auf allen Servern gleich?

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?


35
2017-07-30 05:08


Ursprung


Es gibt kleine Unterschiede zwischen den verschiedenen Schalen. Wenn Portabilität für Sie am wichtigsten ist, dann verwenden Sie #!/bin/sh und verwende nichts anderes als das, was die ursprüngliche Hülle zur Verfügung gestellt hat. - Thorbjørn Ravn Andersen


Antworten:


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

  1. Am tragbarsten: Verwenden Sie a #!/bin/sh Shebang und Gebrauch nur die grundlegende Shell-Syntax, die in der POSIX-Standard. 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. Zweithäufigster Portable: Verwenden Sie a #!/bin/bash (oder #!/usr/bin/env bash) Shebang-Linie, und halten Sie sich an Bash v3-Funktionen. Dies funktioniert auf jedem System mit bash (am erwarteten Ort).

  3. Drittens am tragbarsten: Verwenden Sie a #!/bin/bash (oder #!/usr/bin/env bash) Shebang-Linie, und verwenden Sie Bash V4-Funktionen. Dies wird auf jedem System mit bash v3 fehlschlagen (z. B. macOS, das es aus Lizenzgründen verwenden muss).

  4. Mindestens tragbar: Verwenden Sie a #!/bin/sh Shebang und verwenden Sie Bash-Erweiterungen für die 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 (d. H. 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 Unterschiede zwischen Shells in der Unix & Linux Frage "Was bedeutet es, 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 enthalten, die Sie auf anderen Unix-Systemen (z. B. 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 verwenden (z. echo -noder echo -e), oder mit irgendwelchen Escapes (Backslashes) in der zu druckenden Zeichenfolge, verschiedene Versionen werden verschiedene Dinge tun. Immer wenn Sie eine Zeichenfolge ohne Zeilenvorschub oder mit Escapes in der Zeichenfolge drucken möchten, verwenden Sie printf stattdessen (und lernen, wie es funktioniert - es ist komplizierter als echo ist). Das ps Befehl 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. Neuere (und oft nicht portable) Varianten umfassen lange Optionen (normalerweise eingeführt mit --), so dass Optionen nach Argumenten kommen und verwendet werden können -- Optionen von Argumenten trennen.


63
2017-07-30 06:37



Die vierte Option ist einfach eine falsche Idee. Bitte benutze es nicht. - pabouk
@pabouk Ich stimme völlig zu, also habe ich meine Antwort bearbeitet, um das klarer zu machen. - Gordon Davisson
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 ihren Namen (sh), so /bin/sh ist nicht garantiert der richtige Weg zu sein. Am portabelsten ist es dann überhaupt keinen Shebang anzugeben oder den Shebang an das verwendete Betriebssystem anzupassen. - jlliagre
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 mich geändert habe #!/bin/sh zu #!/bin/bash Allerdings funktionierte das Skript perfekt. (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.) - α CVn
@ 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 erstellt ksh Verhalten, nicht Bourne. Was die meisten Linux-Distributionen, die dem FHS folgen, haben /bin/sh Als symbolische Verknüpfung zu der Shell, die sie auswählen, wird normalerweise POSIX-Kompatibilität bereitgestellt bashoder dash. - jlliagre


In dem ./configure Skript, das die TXR-Sprache zum Bauen vorbereitet, schrieb ich den folgenden Prolog für bessere Portabilität. Das Skript bootet selbst dann selbst, wenn #!/bin/sh ist eine nicht POSIX-konforme alte Bourne Shell. (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. Das txr_shell Umgebungsvariable wird festgelegt, sodass das erneut ausgeführte Skript weiß, dass es sich um die erneut ausgeführte rekursive Instanz handelt.

(In meinem Drehbuch, der txr_shell Variable wird auch nachfolgend für genau zwei Zwecke verwendet: Erstens wird sie als Teil einer informativen Nachricht in der Ausgabe des Skripts gedruckt. Zweitens ist es als das installiert SHELL Variable in der Makefile, damit make wird diese Shell auch für die Ausführung von Rezepten verwenden.)

Auf einem System, wo /bin/sh Ist Strich, können Sie sehen, dass die obige Logik finden wird /bin/bash und führen Sie das Skript mit diesem Skript erneut aus.

Auf einer Solaris 10-Box ist die /usr/xpg4/bin/sh tritt ein, wenn keine Bash gefunden wird.

Der Prolog ist in einem konservativen Muscheldialekt geschrieben, mit test für Datei Existenz Tests und die ${@+"$@"} Trick, um Argumente zu erweitern, die auf einige zerbrochene alte Schalen (die einfach sein würden "$@" wenn wir in einer POSIX-konformen Shell wären).


5
2017-07-31 04:47



Man würde das nicht brauchen x Hackery, wenn das richtige Quoting verwendet wurde, da die Situationen, die dieses Idiom umgaben, nun die Testaufrufe mit verwarfen -aoder -oKombinieren mehrerer Tests. - Charles Duffy
@CharlesDuffy Tatsächlich; das test x$whatever das, was ich dort verübte, sieht wie eine Zwiebel im Lack aus. Wenn wir der zerbrochenen alten Muschel nicht trauen können, zu zitieren, dann ist das endgültig ${@+"$@"} Versuch ist sinnlos. - Kaz


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 ist der etwas sich selbst nennen /bin/sh ist garantiert bei allem vorhanden, was angeblich Unix sein soll. Dies ist jedoch möglicherweise nicht einmal POSIX-konform. Viele der älteren proprietären Unixes froren die von /bin/sh und die Dienstprogramme im Standardpfad vorher zu den von Unix95 geforderten Änderungen (ja, Unix95, vor zwanzig Jahren und zählend). Es könnte eine Reihe von Unix95 oder sogar POSIX.1-2001 geben, wenn Sie Glück haben, Werkzeuge in einem Verzeichnis nicht auf dem Standardpfad (z.B. /usr/xpg4/bin) aber sie sind nicht garantiert zu existieren.

Die Grundlagen von Perl sind jedoch wahrscheinlicher auf einer beliebig gewählten Unix-Installation vorhanden zu sein als Bash. (Durch "die Grundlagen von Perl" meine ich /usr/bin/perl existiert und ist etwas, möglicherweise 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 funktionieren muss überall die angeblich Unix sein soll (zB ein "configure" -Skript), müssen Sie verwenden #! /bin/shund 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 es bist nicht Wenn man etwas schreibt, das überall funktionieren muss, dann sollte man in dem Moment, in dem man versucht ist, irgendeinen Bashismus zu benutzen, aufhören und das Ganze in einer besseren Skriptsprache umschreiben. Dein zukünftiges Selbst wird dir danken.

(Also wann ist es angemessen, Bash-Erweiterungen zu verwenden? Zur ersten Bestellung: nie. Zu zweiter Ordnung: nur um die Bash interaktive Umgebung zu erweitern - z.B. um intelligente Tab-Vervollständigung und spezielle Eingabeaufforderungen bereitzustellen.)


2
2017-07-31 15:58