Perché è meglio usare "#! / Usr / bin / env NAME" invece di "#! / Path / to / NAME" come shebang?


459

Ho notato che alcuni script che ho acquisito da altri hanno lo shebang #!/path/to/NAMEmentre altri (usando lo stesso strumento, NAME) hanno lo shebang #!/usr/bin/env NAME.

Entrambi sembrano funzionare correttamente. Nei tutorial (su Python, per esempio), sembra esserci un suggerimento che quest'ultimo shebang sia migliore. Ma non capisco bene perché sia ​​così.

Mi rendo conto che, per usare quest'ultimo shebang, NAME deve trovarsi nel PERCORSO, mentre il primo shebang non ha questa limitazione.

Inoltre, sembra (per me) che il primo sarebbe il migliore shebang, poiché specifica esattamente dove si trova NAME. Quindi, in questo caso, se esistono più versioni di NAME (ad es. / Usr / bin / NAME, / usr / local / bin / NAME), il primo caso specifica quale utilizzare.

La mia domanda è: perché il primo shebang è preferito al secondo?



@ TheGeeko61: Nel mio caso avevo qualcosa di rotto e alcune variabili non erano in ambiente. Quindi suggerisco di usare questo shebang per verificare se env è caricato correttamente.
Gigamegs

Risposte:


462

Non è necessariamente migliore.

Il vantaggio #!/usr/bin/env pythonè che utilizzerà qualunque pythoneseguibile appare per primo nell'utente $PATH.

Lo svantaggio di #!/usr/bin/env pythonè che utilizzerà qualunque pythoneseguibile appare per primo nell'utente $PATH.

Ciò significa che lo script potrebbe comportarsi diversamente a seconda di chi lo esegue. Per un utente, potrebbe utilizzare /usr/bin/pythonquello installato con il sistema operativo. Per un altro, potrebbe usare un esperimento /home/phred/bin/pythonche non funziona correttamente.

E se pythonè installato solo in /usr/local/bin, un utente che non ha /usr/local/binin $PATHnon sarà nemmeno in grado di eseguire lo script. (Probabilmente non è troppo probabile sui sistemi moderni, ma potrebbe facilmente accadere per un interprete più oscuro.)

Specificando #!/usr/bin/pythonsi specifica esattamente quale interprete verrà utilizzato per eseguire lo script su un determinato sistema .

Un altro potenziale problema è che il #!/usr/bin/envtrucco non consente di passare argomenti all'intreprete (diverso dal nome dello script, che viene passato implicitamente). Questo di solito non è un problema, ma può esserlo. Molti script Perl sono scritti con #!/usr/bin/perl -w, ma use warnings;è la sostituzione consigliata in questi giorni. Gli script Csh dovrebbero usare #!/bin/csh -f- ma gli script csh non sono consigliati in primo luogo. Ma potrebbero esserci altri esempi.

Ho un numero di script Perl in un sistema di controllo del codice sorgente personale che installo quando installo un account su un nuovo sistema. Uso uno script di installazione che modifica la #!riga di ogni script man mano che lo installa nel mio $HOME/bin. (Non ho dovuto usare altro che negli #!/usr/bin/perlultimi tempi; risale ai tempi in cui Perl spesso non era installato di default.)

Un punto secondario: il #!/usr/bin/envtrucco è probabilmente un abuso del envcomando, che originariamente era inteso (come suggerisce il nome) per invocare un comando con un ambiente alterato. Inoltre, alcuni sistemi meno recenti (incluso SunOS 4, se ricordo bene) non avevano il envcomando /usr/bin. Nessuno di questi è probabilmente una preoccupazione significativa. envfunziona in questo modo, molti script usano il #!/usr/bin/envtrucco e è probabile che i provider di sistemi operativi non facciano nulla per romperlo. Si potrebbe essere un problema se si desidera che lo script per l'esecuzione su un sistema molto vecchio, ma allora è molto probabile che la necessità di modificare lo stesso.

Un altro possibile problema (grazie a Sopalajo de Arrierez per averlo sottolineato nei commenti) è che i lavori cron vengono eseguiti con un ambiente limitato. In particolare, $PATHè in genere qualcosa di simile /usr/bin:/bin. Quindi se la directory che contiene l'interprete non si trova in una di quelle directory, anche se è nella tua impostazione predefinita $PATHin una shell utente, il /usr/bin/envtrucco non funzionerà. Puoi specificare il percorso esatto oppure puoi aggiungere una linea al tuo crontab da impostare $PATH( man 5 crontabper i dettagli).


4
Se / usr / bin / perl è perl 5.8, $ HOME / bin / perl è 5.12 e uno script che richiede 5.12 hardcodes / usr / bin / perl negli shebang, può essere un grosso problema eseguire lo script. Raramente ho visto avere / usr / bin / env perl afferrare perl dal PERCORSO è un problema, ma spesso è molto utile. Ed è molto più bello dell'hack dell'execut!
William Pursell,

8
Vale la pena notare che, se si desidera utilizzare una versione specifica dell'interprete, / usr / bin / env è ancora meglio. semplicemente perché di solito ci sono più versioni dell'interprete installati sul computer di nome perl5, perl5.12, perl5.10, python3.3, python3.32, ecc e se la vostra applicazione è stato testato solo su tale versione specifica, è ancora possibile specificare #! / usr / bin / env perl5.12 e va bene anche se l'utente lo ha installato in un posto insolito. Nella mia esperienza, "python" di solito è solo un collegamento simbolico alla versione standard del sistema (non necessariamente la versione più recente del sistema).
radice

6
@root: per Perl, use v5.12;serve un po ' di tale scopo. E #!/usr/bin/env perl5.12fallirà se il sistema ha Perl 5.14 ma non 5.12. Per Python 2 vs. 3, #!/usr/bin/python2e #!/usr/bin/python3probabilmente funzioneranno.
Keith Thompson,

3
@GoodPerson: Supponiamo che io scriva uno script Perl da installare /usr/local/bin. Mi capita di sapere che funziona correttamente con /usr/bin/perl. Non ho idea se funziona con qualsiasi perleseguibile che un utente casuale capita di avere nel suo $PATH. Forse qualcuno sta sperimentando una versione antica di Perl; perché ho specificato #!/usr/bin/perl, il mio script (che l'utente non conosce o cura necessariamente è uno script Perl) non smetterà di funzionare.
Keith Thompson,

5
Se non funziona /usr/bin/perl, lo scoprirò molto rapidamente ed è responsabilità del proprietario / amministratore del sistema mantenerlo aggiornato. Se vuoi eseguire il mio script con il tuo perl, sentiti libero di afferrare e modificare una copia o invocarlo tramite perl foo. (E potresti considerare la possibilità che anche le 55 persone che hanno votato a favore di questa risposta conoscano una cosa o due. È certamente possibile che tu abbia ragione e abbiano tutti torto, ma non è così che scommetterei.)
Keith Thompson,

101

Perché / usr / bin / env può interpretare il tuo $PATH, il che rende gli script più portabili.

#!/usr/local/bin/python

Eseguirà il tuo script solo se python è installato in / usr / local / bin.

#!/usr/bin/env python

Interpreterà il tuo $PATHe troverà Python in qualsiasi directory nel tuo $PATH.

Quindi il tuo script è più portatile e funzionerà senza modifiche sui sistemi in cui è installato python come /usr/bin/python, o /usr/local/bin/python, o anche su directory personalizzate (che sono state aggiunte $PATH), come /opt/local/bin/python.

La portabilità è l'unica ragione per cui envsi preferisce l' utilizzo di percorsi con codifica rigida.


19
Le directory personalizzate per gli pythoneseguibili sono particolarmente comuni virtualenvall'aumentare dell'utilizzo.
Xiong Chiamiov

1
Che dire #! python, perché non viene utilizzato?
kristianp,

6
#! pythonnon viene utilizzato perché dovresti trovarti nella stessa directory del binario python, poiché il bareword pythonviene interpretato come il percorso completo del file. Se non hai un binario python nella directory corrente, otterrai un errore simile bash: ./script.py: python: bad interpreter: No such file or directory. È lo stesso che se avessi usato#! /not/a/real/path/python
Tim Kennedy

47

La specifica del percorso assoluto è più precisa su un determinato sistema. Il rovescio della medaglia è che è troppo preciso. Supponete di rendervi conto che l'installazione del sistema di Perl è troppo vecchia per i vostri script e volete invece usare i vostri: allora dovete modificare gli script e passare #!/usr/bin/perla #!/home/myname/bin/perl. Peggio ancora, se hai Perl /usr/binsu alcune macchine, /usr/local/binsu altre e /home/myname/bin/perlancora su altre macchine, allora dovresti conservare tre copie separate degli script ed eseguire quella appropriata su ogni macchina.

#!/usr/bin/envsi rompe se PATHè male, ma così fa quasi tutto. Tentare di operare con un male PATHè molto raramente utile e indica che sai molto poco sul sistema su cui è in esecuzione lo script, quindi non puoi comunque fare affidamento su un percorso assoluto.

Esistono due programmi la cui posizione è possibile fare affidamento su quasi tutte le varianti di unix: /bin/she /usr/bin/env. Alcune varianti Unix oscure e per lo più in pensione avevano /bin/envsenza /usr/bin/env, ma è improbabile che le incontriate. I sistemi moderni hanno /usr/bin/envproprio a causa del suo uso diffuso nelle shebang. /usr/bin/envè qualcosa su cui puoi contare.

A parte /bin/sh, l'unica volta in cui dovresti usare un percorso assoluto in uno shebang è quando il tuo script non è pensato per essere portatile, quindi puoi contare su una posizione nota per l'interprete. Ad esempio, uno script bash che funziona solo su Linux può essere tranquillamente utilizzato #!/bin/bash. Uno script che deve essere utilizzato solo internamente può fare affidamento sulle convenzioni sulla posizione dell'interprete domestico.

#!/usr/bin/envha degli aspetti negativi. È più flessibile che specificare un percorso assoluto ma richiede comunque di conoscere il nome dell'interprete. Occasionalmente potresti voler eseguire un interprete che non si trova $PATH, ad esempio in una posizione relativa allo script. In questi casi, è spesso possibile creare uno script poliglotta che può essere interpretato sia dalla shell standard che dall'interprete desiderato. Ad esempio, per rendere portatile uno script Python 2 sia per i sistemi in cui pythonè Python 3 ed python2è Python 2, sia per i sistemi in cui pythonè Python 2 e python2non esiste:

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0" "$@"
else
  exec python "$0" "$@"
fi
'''
# real Python script starts here
def …

22

In particolare per Perl, l'utilizzo #!/usr/bin/envè una cattiva idea per due motivi.

Innanzitutto, non è portatile. Su alcune piattaforme oscure env non è in / usr / bin. In secondo luogo, come ha notato Keith Thompson , può causare problemi nel passare argomenti sulla linea shebang. La soluzione massima portatile è questa:

#!/bin/sh
exec perl -x "$0" "$@"
#!perl

Per i dettagli su come funziona, vedere 'perldoc perlrun' e cosa dice sull'argomento -x.


Esattamente la risposta che stavo cercando: come scrivere "shebang" parabile per lo script perl che consentirà ulteriori passaggi a perl (l'ultima riga del tuo esempio accetta altri strumenti).
AmokHuginnsson,

15

Il motivo per cui esiste una distinzione tra i due è dovuto al modo in cui vengono eseguiti gli script.

L'uso /usr/bin/env(che, come menzionato in altre risposte, non è presente in /usr/binalcuni sistemi operativi) è necessario perché non è possibile inserire un nome eseguibile dopo il #!- deve essere un percorso assoluto. Questo perché il #!meccanismo funziona a un livello inferiore rispetto alla shell. Fa parte del caricatore binario del kernel. Questo può essere testato. Metti questo in un file e contrassegnalo come eseguibile:

#!bash

echo 'foo'

Troverai che stampa un errore come questo quando tenti di eseguirlo:

Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.

Se un file è contrassegnato come eseguibile e inizia con a #!, il kernel (che non conosce $PATHo la directory corrente: questi sono concetti land-user) cercherà un file usando un percorso assoluto. Poiché l'utilizzo di un percorso assoluto è problematico (come indicato in altre risposte), qualcuno ha escogitato un trucco: è possibile eseguire /usr/bin/env(che è quasi sempre in quella posizione) per eseguire qualcosa utilizzando $PATH.


11

Ci sono altri due problemi con l'utilizzo #!/usr/bin/env

  1. Non risolve il problema di specificare il percorso completo per l'interprete, ma semplicemente lo sposta in env.

    envnon è più garantito essere in di /usr/bin/envquanto bashè garantito essere in /bin/basho python in /usr/bin/python.

  2. env sovrascrive ARGV [0] con il nome dell'interprete (e..g bash o python).

    Questo impedisce la visualizzazione del nome del tuo script, ad esempio psoutput (o modifica il modo / dove appare) e rende impossibile trovarlo con, ad esempio,ps -C scriptname.sh

[aggiornamento 04/06/2016]

E un terzo problema:

  1. Cambiare il tuo PERCORSO è più lavoro che semplicemente modificare la prima riga di uno script, specialmente quando lo scripting di tali modifiche è banale. per esempio:

    printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py

    L'aggiunta o la pre-attesa di una directory a $ PATH è abbastanza semplice (anche se devi ancora modificare un file per renderlo permanente - tuo ~/.profileo quello che vuoi - ed è tutt'altro che facile da scriptare quella modifica perché il PATH potrebbe essere impostato ovunque nello script , non nella prima riga).

    Cambiare l'ordine delle directory PATH è significativamente più difficile .... e molto più difficile della semplice modifica della #!linea.

    E hai ancora tutti gli altri problemi che #!/usr/bin/envti dà l' uso .

    @jlliagre suggerisce in un commento che #!/usr/bin/envè utile per testare il tuo script con più versioni di interprete, "cambiando solo il loro ordine PERCORSO / PERCORSO"

    Se hai bisogno di farlo, è molto più facile avere solo diverse #!righe nella parte superiore dello script (sono solo commenti ovunque tranne la prima riga) e tagliare / copiare e incollare quello che vuoi usare ora per la prima riga.

    In vi, sarebbe semplice come spostare il cursore sulla #!riga desiderata, quindi digitare dd1GPo Y1GP. Anche con un editor tanto banale nano, occorrerebbero pochi secondi per usare il mouse per copiare e incollare.


Nel complesso, i vantaggi dell'utilizzo #!/usr/bin/envsono minimi nel migliore dei casi e certamente non si avvicinano nemmeno a superare gli svantaggi. Anche il vantaggio della "convenienza" è ampiamente illusorio.

IMO, è un'idea sciocca promossa da un particolare tipo di programmatore che pensa che i sistemi operativi non siano qualcosa su cui lavorare, sono un problema su cui aggirare (o ignorare al meglio).

PS: ecco un semplice script per cambiare l'interprete di più file contemporaneamente.

change-shebang.sh:

#!/bin/bash

interpreter="$1"
shift

if [ -z "$(type -P $interpreter)" ] ; then
  echo "Error: '$interpreter' is not executable." >&2
  exit 1
fi

if [ ! -d "$interpreter" ] && [ -x "$interpreter" ] ; then
  shebang='#!'"$(realpath -e $interpreter)" || exit 1
else
  shebang='#!'"$(type -P $interpreter)"
fi

for f in "$@" ; do
  printf "%s\n" 1 i "$shebang" . w | ed "$f"
done

Eseguirlo come, ad esempio, change-shebang.sh python2.7 *.pyochange-shebang.sh $HOME/bin/my-experimental-ruby *.rb


4
Nessun altro qui ha menzionato il problema ARGV [0]. E nessuno ha menzionato il problema / path / to / env in una forma che affronta direttamente uno degli argomenti per usarlo (cioè che bash o perl potrebbero trovarsi in una posizione inaspettata).
Cas

2
Il problema del percorso dell'interprete è facilmente risolto da un amministratore di sistema con collegamenti simbolici o dall'utente che modifica il proprio script. Non è certamente un problema abbastanza significativo per incoraggiare le persone a sopportare tutte le altre questioni causate dall'uso di env sulla linea shebang che sono menzionate qui e su altre domande. Questo sta promuovendo una cattiva soluzione nello stesso modo in cui incoraggiare lo cshscripting sta promuovendo una cattiva soluzione: funziona, ma ci sono alternative molto migliori.
Cas

3
i non amministratori di sistema possono chiedere al proprio amministratore di sistema di farlo. oppure possono semplicemente modificare lo script e cambiare la #!riga.
Cas

2
Questo è esattamente ciò che env sta aiutando ad evitare: dipende da un amministratore di sistema o da conoscenze tecniche specifiche del sistema operativo non correlate a Python o altro.
jlliagre,

1
Vorrei poter votare questo mille volte, perché in realtà è un'ottima risposta in entrambi i casi - se qualcuno lo usa /usr/bin/enve devo liberarmene localmente, o se non lo hanno usato e devo aggiungerlo. Possono verificarsi entrambi i casi, e quindi gli script forniti in questa risposta sono uno strumento potenzialmente utile da avere nella casella degli strumenti.
jstine,

8

Aggiungendo un altro esempio qui:

L'uso envè utile anche quando si desidera condividere script tra più rvmambienti, ad esempio.

Eseguendolo sulla riga cmd, mostra quale versione ruby ​​verrà utilizzata quando #!/usr/bin/env rubyviene utilizzata all'interno di uno script:

env ruby --version

Pertanto, quando si utilizza env, è possibile utilizzare diverse versioni di ruby ​​tramite rvm, senza modificare i propri script.


1
//, Ottima idea. Il punto centrale di più interpreti NON è rompere il codice o far dipendere il codice da quello specifico interprete .
Nathan Basanese,

4

Se stai scrivendo esclusivamente per te stesso o per il tuo lavoro e la posizione dell'interprete che stai chiamando è sempre nello stesso posto, usa sicuramente il percorso diretto. In tutti gli altri casi usare #!/usr/bin/env.

Ecco perché: nella tua situazione, l' pythoninterprete si trovava nello stesso posto indipendentemente dalla sintassi utilizzata, ma per molte persone avrebbe potuto essere installato in un posto diverso. Anche se la maggior parte dei principali interpreti di programmazione si trovano in /usr/bin/molti software più recenti, l'impostazione predefinita è /usr/local/bin/.

Direi anche di usarlo sempre #!/usr/bin/envperché se hai più versioni dello stesso interprete installate e non sai a cosa servirà di default la tua shell, probabilmente dovresti risolverlo.


5
"In tutti gli altri casi usare #! / Usr / bin / env" è troppo forte. // Come sottolineato da @KeithThompson e altri, #! / Usr / bin / env significa che lo script può comportarsi diversamente a seconda di chi / come viene eseguito. A volte questo diverso comportamento può essere un bug di sicurezza. // Ad esempio, MAI utilizzare "#! / Usr / bin / env python" per uno script setuid o setgid, poiché l'utente che invoca lo script può inserire malware in un file chiamato "python" in PATH. Se gli script setuid / gid siano mai una buona idea è un problema diverso, ma certamente nessun eseguibile setuid / gid dovrebbe mai fidarsi dell'ambiente fornito dall'utente.
Krazy Glew,

@KrazyGlew, non puoi impostare uno script su Linux. Lo fai attraverso un eseguibile. E quando si scrive questo eseguibile, è buona pratica, e ampiamente fatto, cancellare le variabili di ambiente. Alcune variabili d'ambiente sono anche volutamente ignorate .
MayeulC

3

Per motivi di portabilità e compatibilità è meglio usarlo

#!/usr/bin/env bash

invece di

#!/usr/bin/bash

Esistono molteplici possibilità, in cui un binario può trovarsi su un sistema Linux / Unix. Controllare la manpage hier (7) per una descrizione dettagliata della gerarchia del file system.

FreeBSD per esempio installa tutto il software, che non fa parte del sistema di base, in / usr / local / . Poiché bash non fa parte del sistema di base, il binario bash è installato in / usr / local / bin / bash .

Quando vuoi uno script bash / csh / perl / qualunque, che funziona con la maggior parte delle distribuzioni Linux e FreeBSD, dovresti usare #! / Usr / bin / env .

Si noti inoltre che la maggior parte delle installazioni Linux ha anche (hard) collegato il binario env a / bin / env o softlinked / usr / bin in / bin che non dovrebbe essere usato nello shebang. Quindi non usare #! / Bin / env .

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.