#! / bin / sh vs #! / bin / bash per la massima portabilità


36

Di solito lavoro con i server di Ubuntu LTS, che da quanto ho capito link simbolico /bin/sha /bin/dash. Molte altre distro tramite link simbolico /bin/sha /bin/bash.

Da ciò capisco che se uno script utilizza #!/bin/shin cima potrebbe non funzionare allo stesso modo su tutti i server?

Esiste una pratica suggerita su quale shell utilizzare per gli script quando si desidera la massima portabilità di quegli script tra server?


Vi sono lievi differenze tra i vari gusci. Se la portabilità è la più importante per te, usa #!/bin/she non usare nient'altro che quello fornito dalla shell originale.
Thorbjørn Ravn Andersen,

Risposte:


65

Esistono circa quattro livelli di portabilità per gli script di shell (per quanto riguarda la linea shebang):

  1. Più portatile: usa uno #!/bin/shshebang e usa solo la sintassi di shell di base specificata nello standard POSIX . Questo dovrebbe funzionare praticamente su qualsiasi sistema POSIX / unix / linux. (Beh, ad eccezione di Solaris 10 e precedenti che avevano la vera eredità della shell Bourne, precedente a POSIX così non conforme, come /bin/sh.)

  2. Il secondo più portatile: usa una #!/bin/bash(o #!/usr/bin/env bash) linea shebang e segui le funzionalità di v3. Funzionerà su qualsiasi sistema che ha bash (nella posizione prevista).

  3. Terzo più portatile: usa una #!/bin/bash(o #!/usr/bin/env bash) linea shebang e usa le funzionalità di bash v4. Questo fallirà su qualsiasi sistema che ha bash v3 (ad esempio macOS, che deve usarlo per motivi di licenza).

  4. Meno portatile: usa un #!/bin/shshebang e usa le estensioni bash alla sintassi della shell POSIX. Questo fallirà su qualsiasi sistema che ha qualcosa di diverso da bash per / bin / sh (come le recenti versioni di Ubuntu). Non farlo mai; non è solo un problema di compatibilità, è semplicemente sbagliato. Sfortunatamente, è un errore che fanno molte persone.

Il mio consiglio: usa il più conservativo dei primi tre che fornisce tutte le funzionalità della shell di cui hai bisogno per lo script. Per la massima portabilità, utilizzare l'opzione n. 1, ma nella mia esperienza alcune funzioni bash (come gli array) sono abbastanza utili che andrò con il n.

La cosa peggiore che puoi fare è # 4, usando lo shebang sbagliato. Se non sei sicuro di quali sono le funzioni POSIX di base e quali sono le estensioni di bash, segui una bash shebang (cioè l'opzione n. 2) o testa lo script con una shell molto semplice (come un trattino sui tuoi server Ubuntu LTS). La wiki di Ubuntu ha una buona lista di bashismi a cui prestare attenzione .

Ci sono alcune informazioni molto utili sulla storia e le differenze tra le shell nella domanda Unix e Linux "Cosa significa essere sh compatibili?" e la domanda StackOverflow "Differenza tra sh e bash" .

Inoltre, tieni presente che la shell non è l'unica cosa che differisce tra i diversi sistemi; se sei abituato a Linux, sei abituato ai comandi GNU, che hanno molte estensioni non standard che potresti non trovare su altri sistemi unix (es. bsd, macOS). Sfortunatamente, non esiste una semplice regola qui, devi solo conoscere l'intervallo di variazione per i comandi che stai utilizzando.

Uno dei comandi peggiori in termini di portabilità è uno dei più fondamentali: echo. Ogni volta che lo usi con qualsiasi opzione (es. echo -nO echo -e) o con qualsiasi escape (barra rovesciata) nella stringa da stampare, versioni diverse faranno cose diverse. Ogni volta che vuoi stampare una stringa senza un avanzamento di riga dopo di essa, o con escape nella stringa, usa printfinvece (e scopri come funziona - è più complicato di quanto non echosia). Anche il pscomando è un casino .

Un'altra cosa generale da tenere d'occhio sono le estensioni recenti / GNUish alla sintassi delle opzioni di comando: il vecchio formato di comando (standard) è che il comando è seguito da opzioni (con un singolo trattino e ogni opzione è una singola lettera), seguita da argomenti di comando. Le varianti recenti (e spesso non portatili) includono opzioni lunghe (generalmente introdotte con --), che consentono alle opzioni di seguire gli argomenti e --di separare le opzioni dagli argomenti.


12
La quarta opzione è semplicemente un'idea sbagliata. Per favore, non usarlo.
pabouk,

3
@pabouk Sono completamente d'accordo, quindi ho modificato la mia risposta per renderlo più chiaro.
Gordon Davisson,

1
La tua prima affermazione è leggermente fuorviante. Lo standard POSIX non specifica nulla sul shebang al di fuori del dire che usarlo porta a comportamenti non specificati. Inoltre, POSIX non specifica dove deve essere posizionata la shell posix, solo il suo nome ( sh), quindi /bin/shnon è garantito che sia il percorso giusto. Il più portatile non è quindi quello di specificare alcun shebang, o di adattare il shebang al sistema operativo utilizzato.
jlliagre,

2
Di recente sono caduto nel # 4 con una mia sceneggiatura e non sono riuscito a capire perché non funzionasse; dopo tutto, gli stessi comandi hanno funzionato in modo solido e hanno fatto esattamente quello che volevo che facessero, quando li ho provati direttamente nella shell. Non appena ho cambiato #!/bin/shper #!/bin/bashse, lo script ha funzionato perfettamente. (A mia difesa, quella sceneggiatura si era evoluta nel tempo da una che aveva davvero solo bisogno di sh-ism, a una che si basava su comportamenti tipo bash.)
un CVn del

2
@ MichaelKjörling La (vera) shell Bourne non è quasi mai in bundle con le distribuzioni Linux e non è comunque conforme a POSIX. La shell standard POSIX è stata creata dal kshcomportamento, non da Bourne. Quello che fanno la maggior parte delle distribuzioni Linux che seguono l'FHS è avere /bin/shun collegamento simbolico alla shell selezionata per fornire la compatibilità POSIX, di solito basho dash.
jlliagre,

5

Nella ./configuresceneggiatura che prepara il linguaggio TXR per la costruzione, ho scritto il seguente prologo per una migliore portabilità. Lo script si avvia automaticamente anche se #!/bin/shè una vecchia Bourne Shell non conforme a POSIX. (Costruisco ogni versione su una VM Solaris 10).

#!/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

L'idea qui è che troviamo una shell migliore di quella su cui stiamo correndo e rieseguiamo lo script usando quella shell. La txr_shellvariabile di ambiente è impostata, in modo che lo script rieseguito sappia che è l'istanza ricorsiva rieseguita.

(Nel mio script, la txr_shellvariabile è anche successivamente utilizzato, esattamente per due scopi: in primo luogo viene stampato come parte di un messaggio informativo nell'output dello script In secondo luogo, viene installato come. SHELLVariabile nel Makefile, in modo che makeuseranno questo shell anche per l'esecuzione di ricette.)

Su un sistema in cui /bin/shè trattino, puoi vedere che la logica sopra troverà /bin/bashe rieseguirà lo script con quello.

Su una scatola di Solaris 10, si /usr/xpg4/bin/shavvierà se non viene trovato alcun Bash.

Il prologo è scritto in un dialetto conservativo della shell, usando testper i test di esistenza dei file, e il ${@+"$@"}trucco per espandere gli argomenti servendosi di alcune vecchie shell rotte (che sarebbe semplicemente "$@"se fossimo in una shell conforme a POSIX).


Uno non avrebbe bisogno xdell'hackery se fossero in uso le citazioni appropriate, poiché le situazioni che obbligavano quell'idioma circondano invocazioni di test ormai deprecate -ao che -ocombinano più test.
Charles Duffy,

@CharlesDuffy Effettivamente; il test x$whateverche sto perpetrando lì sembra una cipolla nella vernice. Se non possiamo fidarci della vecchia shell rotta per fare le quotazioni, il ${@+"$@"}tentativo finale è inutile.
Kaz,

2

Tutte le varianti del linguaggio shell Bourne sono oggettivamente terribili rispetto ai moderni linguaggi di scripting come Perl, Python, Ruby, node.js e persino (probabilmente) Tcl. Se devi fare qualcosa anche un po 'complicato, sarai più felice a lungo termine se usi uno dei precedenti invece di uno script di shell.

L'unico vantaggio che il linguaggio shell ha ancora rispetto a quei linguaggi più recenti è che qualcosa che si chiama /bin/shè garantito su qualsiasi cosa che pretenda di essere Unix. Tuttavia, quel qualcosa potrebbe non essere nemmeno conforme a POSIX; molti degli Unix proprietari legacy hanno bloccato il linguaggio implementato da /bin/she le utility nel PATH predefinito prima delle modifiche richieste da Unix95 (sì, Unix95, venti anni fa e oltre). Potrebbe esserci un set di Unix95, o anche POSIX.1-2001 se sei fortunato, strumenti in una directory che non si trova sul PERCORSO predefinito (es. /usr/xpg4/bin) Ma non è garantito che esistano.

Tuttavia, le basi di Perl hanno più probabilità di essere presenti su un'installazione Unix arbitrariamente selezionata rispetto a Bash. (Con "le basi di Perl" intendo che /usr/bin/perlesiste ed è una versione , forse piuttosto vecchia, di Perl 5, e se sei fortunato sono disponibili anche i moduli forniti con quella versione dell'interprete.)

Perciò:

Se stai scrivendo qualcosa che deve funzionare ovunque che pretende di essere Unix (come uno script "configura"), devi usare #! /bin/she non devi usare alcuna estensione. Oggi scriverei shell conforme a POSIX.1-2001 in questa circostanza, ma sarei pronto a correggere POSIXismi se qualcuno chiedesse supporto per il ferro arrugginito.

Ma se siete non scrivere qualcosa che deve lavorare ovunque, allora il momento si è tentati di utilizzare qualsiasi da bash a tutti, si dovrebbe smettere e riscrivere l'intera cosa in un linguaggio di scripting meglio invece. Il tuo sé futuro ti ringrazierà.

(Quindi, quando è appropriato usare le estensioni di Bash? Al primo ordine: mai. Al secondo ordine: solo per estendere l'ambiente interattivo di Bash - ad esempio per fornire un completamento intelligente delle schede e istruzioni fantasiose.)


L'altro giorno ho dovuto scrivere una sceneggiatura che ha fatto qualcosa di simile find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -. Non sarebbe possibile farlo funzionare correttamente senza bashisms ( read -d "") ed estensioni GNU ( find -print0, tar --null). Reimplementare quella linea in Perl sarebbe molto più lungo e più maldestro. Questo è il tipo di situazione quando si usano bashismi e altre estensioni non POSIX è la cosa giusta da fare.
Michau,

@michau Potrebbe non essere più qualificato come one-liner, a seconda di cosa succede in quelle ellissi, ma penso che non stai prendendo il "riscrivere l' intera cosa in un linguaggio di scripting migliore" letteralmente come volevo dire. La mia strada sembra simile a perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmdNotare che non solo non hai bisogno di alcun bashism, ma non hai bisogno di estensioni di tar GNU. (Se la lunghezza della riga di comando è un problema o vuoi che la scansione e il tar siano eseguiti in parallelo, allora ne hai bisogno tar --null -T.)
zwol

Il fatto è che findnel mio scenario sono stati restituiti oltre 50.000 nomi di file, quindi è probabile che il tuo script raggiunga il limite di lunghezza dell'argomento. Un'implementazione corretta che non utilizza estensioni non POSIX dovrebbe costruire l'output tar in modo incrementale, o forse usare Archive :: Tar :: Stream nel codice Perl. Certo, si può fare, ma in questo caso lo script Bash è molto più veloce da scrivere e ha un numero inferiore di caratteri, quindi meno spazio per i bug.
Michau,

BTW, potrei usare Perl al posto di Bash in modo rapido e conciso: find ... -print0 |perl -0ne '... print ...' |tar c --null -T -. Ma le domande sono: 1) Sto usando find -print0e tar --nullcomunque, quindi qual è il punto di evitare il bashismo read -d "", che è esattamente lo stesso tipo di cosa (un'estensione GNU per gestire il separatore null che, a differenza di Perl, un giorno potrebbe diventare una cosa POSIX )? 2) Il Perl è davvero più diffuso di Bash? Ho usato immagini Docker di recente e molti di loro hanno Bash ma non Perl. È più probabile che vengano eseguiti nuovi script lì, rispetto agli antichi Unices.
Michau,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.