Warum ist es besser, "#! / Usr / bin / env NAME" anstelle von "#! / Path / to / NAME" als mein "shebang" zu verwenden?

458

Ich stelle fest, dass einige Skripte, die ich von anderen erworben habe, den Shebang haben, #!/path/to/NAMEwährend andere (mit demselben Tool, NAME) den Shebang haben #!/usr/bin/env NAME.

Beide scheinen richtig zu funktionieren. In Tutorials (zum Beispiel auf Python) scheint es einen Vorschlag zu geben, dass letzterer besser ist. Aber ich verstehe nicht ganz, warum das so ist.

Mir ist klar, dass NAME im PATH enthalten sein muss, um den letztgenannten Shebang zu verwenden, wohingegen der erste Shebang diese Einschränkung nicht hat.

Es scheint mir auch, dass das Erste das bessere ist, da es genau angibt, wo sich NAME befindet. Wenn es in diesem Fall also mehrere Versionen von NAME gibt (z. B. / usr / bin / NAME, / usr / local / bin / NAME), gibt der erste Fall an, welche zu verwenden sind.

Meine Frage ist, warum der erste Schebang dem zweiten vorgezogen wird.

TheGeeko61
quelle
12
Siehe diese Antwort ...
Jasonwryan
@ TheGeeko61: In meinem Fall war etwas kaputt und einige Variablen waren nicht in env. Ich empfehle daher, diesen Shebang zu verwenden, um zu überprüfen, ob env korrekt geladen ist.
Gigamegs

Antworten:

462

Es ist nicht unbedingt besser.

Der Vorteil #!/usr/bin/env pythonist, dass es die pythonausführbare Datei verwendet, die zuerst im Benutzer angezeigt wird $PATH.

Der Nachteil der #!/usr/bin/env pythonist , dass es , was auch immer verwendet pythonausführbar erscheint zuerst in dem Benutzer $PATH.

Das bedeutet, dass sich das Skript je nach Ausführung unterschiedlich verhalten kann. Für einen Benutzer wird möglicherweise das verwendet /usr/bin/python, das mit dem Betriebssystem installiert wurde. Zum anderen könnte ein Experiment verwendet werden /home/phred/bin/python, das nicht richtig funktioniert.

Und wenn pythones nur in installiert ist /usr/local/bin, kann ein Benutzer, der nicht /usr/local/binin installiert ist , $PATHdas Skript nicht einmal ausführen. (Das ist auf modernen Systemen wahrscheinlich nicht allzu wahrscheinlich, könnte aber für einen unklaren Interpreter leicht passieren.)

Indem #!/usr/bin/pythonSie angeben, geben Sie genau an, welcher Interpreter zum Ausführen des Skripts auf einem bestimmten System verwendet wird .

Ein weiteres mögliches Problem ist, dass Sie mit diesem #!/usr/bin/envTrick keine Argumente an den Intrepreter übergeben können (außer dem Namen des Skripts, der implizit übergeben wird). Dies ist normalerweise kein Problem, kann es aber sein. Viele Perl-Skripte werden mit geschrieben #!/usr/bin/perl -w, aber dies use warnings;ist heutzutage der empfohlene Ersatz. CSH-Skripte sollten verwendet werden #!/bin/csh -f- CSH-Skripte werden jedoch nicht empfohlen . Es könnte aber auch andere Beispiele geben.

Ich habe eine Reihe von Perl-Skripten in einem persönlichen Versionsverwaltungssystem, die ich installiere, wenn ich ein Konto auf einem neuen System einrichte. Ich verwende ein Installationsskript, das die #!Zeile jedes Skripts ändert, wenn es in my installiert wird $HOME/bin. (Ich musste nichts anderes als in #!/usr/bin/perlletzter Zeit verwenden. Es geht auf Zeiten zurück, in denen Perl oft nicht standardmäßig installiert war.)

Ein kleiner Punkt: Der #!/usr/bin/envTrick ist wohl ein Missbrauch des envBefehls, der ursprünglich (wie der Name schon sagt) dazu gedacht war, einen Befehl in einer veränderten Umgebung aufzurufen. Außerdem hatten einige ältere Systeme (einschließlich SunOS 4, wenn ich mich richtig erinnere) den envBefehl nicht in /usr/bin. Beides dürfte kein wesentliches Problem darstellen. envfunktioniert auf diese Weise, viele Skripte verwenden diesen #!/usr/bin/envTrick, und Betriebssystemanbieter werden wahrscheinlich nichts tun, um ihn zu beschädigen. Es könnte ein Problem sein , wenn Sie ein Skript nur auf einem wirklich altes System zu laufen, aber dann sind Sie wahrscheinlich , es müssen sowieso ändern.

Ein weiteres mögliches Problem (dank Sopalajo de Arrierez für den Hinweis in den Kommentaren) ist, dass Cron-Jobs in einer eingeschränkten Umgebung ausgeführt werden. Insbesondere $PATHist in der Regel so etwas /usr/bin:/bin. Wenn sich das Verzeichnis mit dem Interpreter also nicht in einem dieser Verzeichnisse befindet, auch wenn es $PATHin einer Benutzer-Shell standardmäßig vorhanden /usr/bin/envist, funktioniert der Trick nicht. Sie können den genauen Pfad angeben oder Ihrer Crontab eine Linie hinzufügen, um sie festzulegen $PATH( man 5 crontabfür Details).

Keith Thompson
quelle
4
Wenn / usr / bin / perl Perl 5.8 ist, $ HOME / bin / perl 5.12 ist und ein Skript 5.12-Hardcodes / usr / bin / perl in den Shebangs erfordert, kann die Ausführung des Skripts zu einem großen Problem werden. Ich habe selten gesehen, dass / usr / bin / env perl Grab Perl aus dem Pfad ein Problem ist, aber es ist oft sehr hilfreich. Und es ist viel hübscher als der Exec-Hack!
William Pursell
8
Es ist erwähnenswert, dass / usr / bin / env noch besser ist, wenn Sie eine bestimmte Interpreter-Version verwenden möchten. einfach , weil es in der Regel sind mehrere Interpreter - Versionen auf Ihrem Rechner installiert namens perl5, perl5.12, perl5.10, python3.3, python3.32 usw. und wenn Ihre Anwendung nur auf diese spezifische Version getestet wurde, können Sie angeben , #! / usr / bin / env perl5.12 und alles in Ordnung, auch wenn der Benutzer es an einem ungewöhnlichen Ort installiert hat. Nach meiner Erfahrung ist 'Python' normalerweise nur ein Symlink zur Systemstandardversion (nicht unbedingt die aktuellste Version auf dem System).
root
6
@root: Für Perl, use v5.12;dient einige von diesen Zweck. Und #!/usr/bin/env perl5.12wird fehlschlagen, wenn das System Perl 5.14 aber nicht 5.12 hat. Für Python 2 vs. 3 #!/usr/bin/python2und #!/usr/bin/python3dürfte funktionieren.
Keith Thompson
3
@GoodPerson: Angenommen, ich schreibe ein Perl-Skript, in das installiert werden soll /usr/local/bin. Ich weiß zufällig, dass es richtig funktioniert /usr/bin/perl. Ich habe keine Ahnung, ob es mit jeder perlausführbaren Datei funktioniert, die zufällige Benutzer in sich haben $PATH. Vielleicht experimentiert jemand mit einer alten Version von Perl. Da ich angegeben habe #!/usr/bin/perl, funktioniert mein Skript (das der Benutzer nicht unbedingt kennt oder kümmert, ist es ein Perl-Skript) nicht mehr.
Keith Thompson
5
Wenn es nicht funktioniert /usr/bin/perl, werde ich es sehr schnell herausfinden und es liegt in der Verantwortung des Systembesitzers / Administrators, es auf dem neuesten Stand zu halten. Wenn Sie mein Skript mit Ihrem eigenen Perl ausführen möchten, können Sie eine Kopie abrufen und ändern oder sie über aufrufen perl foo. (Und Sie könnten die Möglichkeit in Betracht ziehen, dass die 55 Personen, die diese Antwort positiv bewertet haben, auch ein oder zwei Dinge wissen. Es ist sicher möglich, dass Sie Recht haben und dass sie alle Unrecht haben, aber ich wette nicht so.)
Keith Thompson
101

Denn / usr / bin / env kann Ihre interpretieren $PATH, wodurch Skripte portabler werden.

#!/usr/local/bin/python

Führt Ihr Skript nur aus, wenn Python in / usr / local / bin installiert ist.

#!/usr/bin/env python

Wird Ihre interpretieren $PATHund finden Sie Python in einem beliebigen Verzeichnis in Ihrem $PATH.

So ist Ihr Skript portabler und funktioniert ohne Änderungen auf Systemen, auf denen Python als /usr/bin/pythonoder /usr/local/bin/pythonoder sogar benutzerdefinierte Verzeichnisse (die hinzugefügt wurden $PATH) wie installiert sind /opt/local/bin/python.

Portabilität ist der einzige Grund, weshalb die Verwendung von envPfaden gegenüber fest codierten Pfaden bevorzugt wird.

Tim Kennedy
quelle
19
Benutzerdefinierte Verzeichnisse für pythonausführbare Dateien sind besonders häufig, wenn die virtualenvVerwendung zunimmt.
Xiong Chiamiov
1
Was ist #! python, warum wird das nicht verwendet?
Kristianp
6
#! pythonwird nicht verwendet, da Sie sich im selben Verzeichnis wie die Python-Binärdatei befinden müssten, da das Bareword pythonals vollständiger Pfad zur Datei interpretiert wird. Wenn Sie keine Python-Binärdatei im aktuellen Verzeichnis haben, wird eine Fehlermeldung wie angezeigt bash: ./script.py: python: bad interpreter: No such file or directory. Es ist das gleiche, als ob Sie verwendet#! /not/a/real/path/python
Tim Kennedy
47

Die Angabe des absoluten Pfades ist auf einem bestimmten System genauer. Der Nachteil ist, dass es zu präzise ist. Nehmen wir an , dass das System die Installation von Perl zu alt ist für Ihre Skripte und Sie möchten Ihre eigene stattdessen verwenden: dann müssen Sie die Skripte bearbeiten und ändern #!/usr/bin/perlzu #!/home/myname/bin/perl. Schlimmer noch, wenn Sie Perl /usr/binauf einigen Rechnern, /usr/local/binauf anderen und /home/myname/bin/perlnoch auf anderen Rechnern installiert haben, müssen Sie drei separate Kopien der Skripte verwalten und auf jedem Rechner die entsprechende ausführen.

#!/usr/bin/envbricht, wenn PATHschlecht ist, tut aber so ziemlich alles. Der Versuch, mit einem schlechten zu arbeiten, PATHist sehr selten sinnvoll und zeigt an, dass Sie nur sehr wenig über das System wissen, auf dem das Skript ausgeführt wird, sodass Sie sich sowieso nicht auf einen absoluten Pfad verlassen können.

Es gibt zwei Programme, auf deren Speicherort Sie sich bei fast jeder Unix-Variante verlassen können: /bin/shund /usr/bin/env. Einige obskure und meist pensionierte Unix-Varianten hatten /bin/envkeine /usr/bin/env, aber es ist unwahrscheinlich, dass Sie ihnen begegnen. Moderne Systeme haben /usr/bin/envgerade wegen ihrer weit verbreiteten Verwendung in Pony. /usr/bin/envDarauf können Sie zählen.

Abgesehen davon /bin/shsollten Sie in einem Shebang nur dann einen absoluten Pfad verwenden, wenn Ihr Skript nicht als portabel gedacht ist, sodass Sie sich auf einen bekannten Speicherort für den Interpreter verlassen können. Beispielsweise kann ein Bash-Skript, das nur unter Linux funktioniert, problemlos verwendet werden #!/bin/bash. Ein Skript, das nur für die interne Verwendung vorgesehen ist, kann sich auf Standortkonventionen für Hausinterpreter stützen.

#!/usr/bin/envhat Nachteile. Es ist flexibler als die Angabe eines absoluten Pfades, erfordert jedoch die Kenntnis des Interpreten-Namens. Gelegentlich möchten Sie möglicherweise einen Interpreter ausführen, der sich nicht an der entsprechenden Stelle befindet $PATH, z. B. relativ zum Skript. In solchen Fällen können Sie häufig ein mehrsprachiges Skript erstellen, das sowohl von der Standard-Shell als auch von Ihrem gewünschten Interpreter interpretiert werden kann. So können Sie beispielsweise ein Python 2-Skript sowohl auf Systeme portieren, auf denen pythonPython 3 und python2Python 2 vorhanden sind, als auch auf Systeme, auf denen pythonPython 2 vorhanden ist und python2die nicht vorhanden sind:

#!/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 …
Gilles
quelle
22

Insbesondere für Perl ist die Verwendung #!/usr/bin/envaus zwei Gründen eine schlechte Idee.

Erstens ist es nicht portabel. Auf einigen unbekannten Plattformen befindet sich env nicht in / usr / bin. Zweitens, wie Keith Thompson bemerkt hat, kann es Probleme bereiten, Argumente auf der Shebang-Linie weiterzuleiten. Die maximal tragbare Lösung lautet:

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

Ausführliche Informationen zur Funktionsweise finden Sie unter "perldoc perlrun" und den darin enthaltenen Informationen zum Argument -x.

DrHyde
quelle
Genau die Antwort, nach der ich gesucht habe: Wie schreibe ich porable "shebang" für Perl?
AmokHuginnsson
15

Der Grund, warum es einen Unterschied zwischen den beiden gibt, liegt darin, wie Skripte ausgeführt werden.

Die Verwendung von /usr/bin/env(die, wie in anderen Antworten erwähnt, in /usr/bineinigen Betriebssystemen nicht enthalten ist) ist erforderlich, da Sie nicht einfach einen ausführbaren Namen nach dem setzen können #!- es muss sich um einen absoluten Pfad handeln. Dies liegt daran, dass der #!Mechanismus auf einer niedrigeren Ebene als die Shell arbeitet. Es ist Teil des Binärladers des Kernels. Dies kann getestet werden. Fügen Sie dies in eine Datei ein und markieren Sie sie als ausführbar:

#!bash

echo 'foo'

Sie werden feststellen, dass der folgende Fehler ausgegeben wird, wenn Sie versuchen, ihn auszuführen:

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

Wenn eine Datei als ausführbar markiert ist und mit einem beginnt #!, sucht der Kernel (der nichts über $PATHdas aktuelle Verzeichnis weiß : dies sind User-Land-Konzepte) nach einer Datei unter Verwendung eines absoluten Pfads. Da die Verwendung eines absoluten Pfads problematisch ist (wie in anderen Antworten erwähnt), hat sich jemand einen Trick ausgedacht: Sie können /usr/bin/env(fast immer an diesem Ort) ausführen, um etwas mit auszuführen $PATH.

Tiffany Bennett
quelle
11

Es gibt zwei weitere Probleme bei der Verwendung #!/usr/bin/env

  1. Es löst nicht das Problem, den vollständigen Pfad zum Interpreter anzugeben, sondern verschiebt ihn lediglich nach env.

    envist nicht mehr garantiert, /usr/bin/envals bashgarantiert ist, /bin/bashoder Python in /usr/bin/python.

  2. env Überschreibt ARGV [0] mit dem Namen des Interpreters (z. B. Bash oder Python).

    Dies verhindert, dass der Name Ihres Skripts in z. B. der psAusgabe angezeigt wird (oder ändert, wie / wo er angezeigt wird), und macht es unmöglich, ihn zu finden, z. B. mitps -C scriptname.sh

[Update 04.06.2016]

Und ein drittes Problem:

  1. Das Ändern Ihres PFADS ist mehr als nur das Bearbeiten der ersten Zeile eines Skripts, insbesondere wenn das Erstellen von Skripts für solche Änderungen trivial ist. z.B:

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

    Das Anhängen oder Voranhängen eines Verzeichnisses an $ PATH ist ziemlich einfach (obwohl Sie eine Datei noch bearbeiten müssen, um sie dauerhaft zu machen - Ihre ~/.profileoder was auch immer - und es ist alles andere als einfach, DIESES Skript zu erstellen, da der PATH an einer beliebigen Stelle im Skript festgelegt werden könnte nicht in der ersten Zeile).

    Das Ändern der Reihenfolge von PATH-Verzeichnissen ist erheblich schwieriger und weitaus schwieriger als das bloße Bearbeiten der #!Zeile.

    Und Sie haben immer noch alle anderen Probleme, die bei der Verwendung auftreten #!/usr/bin/env.

    @jlliagre schlägt in einem Kommentar vor, der #!/usr/bin/envzum Testen Ihres Skripts mit mehreren Interpreter-Versionen nützlich ist, "indem nur deren PATH / PATH-Reihenfolge geändert wird".

    Wenn Sie das tun müssen, ist es viel einfacher, nur mehrere #!Zeilen am oberen Rand Ihres Skripts zu haben (sie sind nur Kommentare an einer beliebigen Stelle außer der allerersten Zeile) und diejenige auszuschneiden / zu kopieren und einzufügen, die Sie jetzt verwenden möchten die erste Zeile.

    Dies viist so einfach, als würden Sie den Cursor auf die #!gewünschte Zeile bewegen und dann dd1GPoder eingeben Y1GP. Selbst mit einem so einfachen Editor nanowürde es Sekunden dauern, die Maus zum Kopieren und Einfügen zu verwenden.


Insgesamt sind die Vorteile der Verwendung #!/usr/bin/envbestenfalls minimal und überwiegen sicherlich nicht annähernd die Nachteile. Sogar der Vorteil "Bequemlichkeit" ist größtenteils illusorisch.

IMO, es ist eine dumme Idee, die von einem bestimmten Programmierer vertreten wird, der der Meinung ist, dass Betriebssysteme nichts sind, mit dem man arbeiten kann, sondern ein Problem , das man umgehen (oder das man bestenfalls ignoriert).

PS: Hier ist ein einfaches Skript, mit dem Sie den Interpreter mehrerer Dateien gleichzeitig ändern können.

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

Führen Sie es aus wie zB change-shebang.sh python2.7 *.pyoderchange-shebang.sh $HOME/bin/my-experimental-ruby *.rb

cas
quelle
4
Niemand hier hat das ARGV-Problem erwähnt. Und niemand hat das Problem / path / to / env in einer Form erwähnt, die eines der Argumente für die Verwendung direkt anspricht (dh, dass sich Bash oder Perl möglicherweise an einem unerwarteten Ort befinden).
cas
2
Das Problem mit dem Interpreterpfad kann leicht von einem Systemadministrator mit Symlinks oder vom Benutzer, der sein Skript bearbeitet, behoben werden. Es ist sicherlich kein ausreichend großes Problem, um die Leute zu ermutigen, sich mit all den anderen Problemen auseinanderzusetzen, die durch die Verwendung von env in der Shebang-Zeile, die hier und in anderen Fragen erwähnt werden, verursacht werden. Das fördert eine schlechte Lösung auf die gleiche Weise wie das Fördern von cshSkripten eine schlechte Lösung: Es funktioniert, aber es gibt viel bessere Alternativen.
cas
3
Nicht-Systemadministratoren können ihren Systemadministrator bitten, dies zu tun. oder sie können einfach das Skript bearbeiten und die #!Zeile ändern .
cas
2
Genau das hilft env zu vermeiden: Abhängig von einem Sysadmin oder einem Betriebssystem, das spezifische technische Kenntnisse hat, die nichts mit Python zu tun haben, oder was auch immer.
Juli
1
Ich wünschte , ich könnte dies tausendmal upvote, weil es eigentlich eine gute Antwort ist so oder so - wenn jemand verwendet /usr/bin/envund ich brauche lokal davon loszuwerden, oder , wenn sie es nicht benutzen , und ich brauche , um es hinzuzufügen. In beiden Fällen kann es vorkommen, dass die in dieser Antwort bereitgestellten Skripts ein möglicherweise nützliches Werkzeug in der Toolbox darstellen.
Jstine
8

Hier ein weiteres Beispiel hinzufügen:

Das Verwenden von envist auch nützlich, wenn Sie Skripts rvmbeispielsweise für mehrere Umgebungen freigeben möchten .

Wenn Sie dies in der cmd-Zeile ausführen, wird angezeigt, welche Ruby-Version verwendet wird, wenn #!/usr/bin/env rubysie in einem Skript verwendet wird:

env ruby --version

Daher können Sie bei Verwendung envvon rvm verschiedene Ruby-Versionen verwenden, ohne Ihre Skripte zu ändern.

Nicht jetzt
quelle
1
// , Exzellente Idee. Bei mehreren Interpreten geht es NICHT darum, den Code zu knacken oder den Code von diesem bestimmten Interpreter abhängig zu machen .
Nathan Basanese
4

Wenn Sie nur für sich selbst oder für Ihre Arbeit schreiben und der Ort des Dolmetschers, den Sie anrufen, immer am selben Ort ist, verwenden Sie auf jeden Fall den direkten Pfad. In allen anderen Fällen verwenden #!/usr/bin/env.

Hier ist der Grund: In Ihrer Situation befand sich der pythonInterpreter an derselben Stelle, unabhängig von der verwendeten Syntax, aber für viele Benutzer könnte er an einer anderen Stelle installiert sein. Obwohl sich die meisten großen Programmierinterpreter in /usr/bin/einer Vielzahl neuerer Software befinden, wird dies standardmäßig verwendet /usr/local/bin/.

Ich würde sogar behaupten, immer zu verwenden, #!/usr/bin/envdenn wenn Sie mehrere Versionen desselben Interpreters installiert haben und nicht wissen, wie Ihre Shell standardmäßig vorgeht, sollten Sie das wahrscheinlich beheben.

Mauvis Ledford
quelle
5
"In allen anderen Fällen verwenden Sie #! / Usr / bin / env" ist zu stark. // Wie @KeithThompson und andere betonen, bedeutet #! / Usr / bin / env, dass sich das Skript je nachdem, wer / wie ausgeführt wird, unterschiedlich verhalten kann. Manchmal kann dieses unterschiedliche Verhalten eine Sicherheitslücke sein. // Verwenden Sie beispielsweise NIEMALS "#! / Usr / bin / env python" für ein setuid- oder setgid-Skript, da der Benutzer, der das Skript aufruft, Malware in einer Datei namens "python" in PATH ablegen kann. Ob setuid / gid-Skripte jemals eine gute Idee sind, ist eine andere Frage - aber sicherlich sollte keine ausführbare setuid / gid-Datei jemals der vom Benutzer bereitgestellten Umgebung vertrauen.
Krazy Glew
@KrazyGlew, Sie können unter Linux kein Skript festlegen. Du machst es durch eine ausführbare Datei. Und wenn Sie diese ausführbare Datei schreiben, ist es eine gute und weit verbreitete Praxis, die Umgebungsvariablen zu löschen. Einige Umgebungsvariablen werden auch absichtlich ignoriert .
MayeulC
3

Aus Gründen der Portabilität und Kompatibilität ist die Verwendung besser

#!/usr/bin/env bash

Anstatt von

#!/usr/bin/bash

Es gibt mehrere Möglichkeiten, in denen sich eine Binärdatei auf einem Linux / Unix-System befinden kann. Auf der Hilfeseite hier (7) finden Sie eine detaillierte Beschreibung der Dateisystemhierarchie.

FreeBSD installiert beispielsweise alle Programme, die nicht zum Basissystem gehören, in / usr / local / . Da die Bash nicht Teil des Basissystems ist, wird die Bash-Binärdatei unter / usr / local / bin / bash installiert .

Wenn Sie ein portables bash / csh / perl / whatever-Skript benötigen, das unter den meisten Linux-Distributionen und FreeBSD ausgeführt wird, sollten Sie #! / Usr / bin / env verwenden .

Beachten Sie auch, dass die meisten Linux-Installationen die env-Binärdatei auch (fest) mit / bin / env oder softlinked / usr / bin in / bin verknüpft haben, was im shebang nicht verwendet werden sollte . Verwenden Sie also nicht #! / Bin / env .

Mirko Steiner
quelle