it-swarm-eu.dev

Proč je lepší použít „#! / Usr / bin / env NAME“ místo „#! / Path / to / NAME“ jako můj shebang?

Všiml jsem si, že některé skripty, které jsem získal od jiných, mají Shebang #!/path/to/NAME zatímco ostatní (pomocí stejného nástroje, NAME) mají Shebang #!/usr/bin/env NAME.

Zdá se, že oba fungují správně. V tutoriálech (např. Na Pythonu) se zdá, že je lepší navrhnout Shebang. Ale nechápu, proč tomu tak je.

Uvědomuji si, že pro použití posledního Shebangu musí být NAME v PATH, zatímco první Shebang toto omezení nemá.

Také se mi zdá, že první by byl lepší Shebang, protože přesně určuje, kde se nachází NAME. V tomto případě, pokud existuje více verzí JMÉNA (např./Usr/bin/NAME,/usr/local/bin/NAME), pak první případ specifikuje, které použít.

Moje otázka je, proč je první Shebang preferován před druhým?

497
TheGeeko61

Cílová kritéria/požadavky:

Při určování, zda použít absolutní nebo logické ) (_/usr/bin/env_) cesta k tlumočníkovi v třesku, existují (2) klíčové úvahy:

a) interpret lze nalézt v cílovém systému

b) Správná verze tlumočníka lze nalézt v cílovém systému

Pokud se [~ # ~] shodneme [~ # ~] , že " b) " je žádoucí, Rovněž souhlasíme s tím, že:

c) Je vhodnější, aby naše skripty fail před provedením pomocí nesprávné verze tlumočníka mohly potenciálně dosáhnout nekonzistentních výsledků.

Pokud NEVYDÁVÁME , že „ b) “ záleží, bude stačit jakýkoli nalezený tlumočník.

Testování:

Protože použití logické cesty - _/usr/bin/env_ k tlumočníkovi v třesku je nejrozšířenější řešení umožňující stejnému skriptu Po úspěšném provedení na cílových hostitelích s různými cestami ke stejnému tlumočníkovi to vyzkoušíme pomocí Python kvůli jeho 'popularite - abychom zjistili, zda splňuje naše kritéria.

  1. Žije _/usr/bin/env_ v předvídatelné a konzistentní poloze na [~ # ~] populárním [~ # ~] (nikoli „ každých") Operační systémy? Ano:

    • RHEL 7.5
    • Ubuntu 18.04
    • Raspbian 10 („Buster“)
    • OSX 10.15.02
  2. Níže Python skript provedený uvnitř i vně virtuálních obálek ( Pipenv) během testů:

    #!/usr/bin/env pythonX.x import sys print(sys.version) print('Hello, world!')

  3. Úder ve skriptu byl přepnut podle Python požadované číslo verze (všechny nainstalované na stejném hostiteli):

    • #!/usr/bin/env python2
    • #!/usr/bin/env python2.7
    • #!/usr/bin/env python3
    • #!/usr/bin/env python3.5
    • #!/usr/bin/env python3.6
    • #!/usr/bin/env python3.7
  4. Očekávané výsledky: že print(sys.version) = _env pythonX.x_. Pokaždé, když bylo _./test1.py_ provedeno pomocí jiné nainstalované Python verze, byla vytištěna správná verze uvedená ve třesku).

  5. Poznámky k testování:

    • Testy byly omezeny výhradně na Python
    • Perl: Stejně jako Python - ) [~ # ~] musí [~ # ~] žít v _/usr/bin_ podle FHS
    • Nezkoušel jsem každou možnou kombinaci na každém možném počtu operačních systémů Linux/Unixy a verzí každého operačního systému.

Závěr:

I když je PRAVDA, že _#!/usr/bin/env python_ použije první verzi Python, kterou najde v cestě uživatele), můžeme toto chování zmírnit zadáním čísla verze, například _#!/usr/bin/env pythonX.x_. Vývojáři se opravdu nestarají o to, který interpret je nalezen „ první“, vše, na čem jim záleží, je to, že jejich kód je spuštěn pomocí zadaného tlumočníka, o kterém ví, že je kompatibilní s jejich kódem, aby zajistil konzistentní výsledky - kdekoli, co může žít v souborovém systému...

Pokud jde o přenositelnost/flexibilitu, použijte logický - _/usr/bin/env_ - spíše než absolutní cesta nejen splňuje požadavky a), b) a c) z mého testování s různými verzemi Pythonu , ale má také výhodu fuzzy-logic najít tlumočník stejné verze, i když žijí na různých cestách na různých operačních systémech. A i když [~ # ~] většina [~ # ~] distros respektuje FHS, ne všichni ano .

Takže pokud skript selže [~ # ~] selže [~ # ~] , pokud binární žije v jiném absolutním cesta pak zadaná v Shebangu, stejný skript pomocí logické cesty [~ # ~] uspěje [~ # ~] , protože neustále pokračuje, dokud nenajde shodu, a tím nabízí větší spolehlivost a rozšiřitelnost napříč platformami.

1
F1Linux

To nemusí být nutně lepší.

Výhodou #!/usr/bin/env python Je to, že bude používat cokoli spustitelný soubor python, který se objeví první v uživatelském $PATH.

Nevýhodou z #!/usr/bin/env python Je to, že bude používat cokoli spustitelný soubor python, který se objeví první v uživatelském $PATH.

To znamená, že se skript mohl chovat odlišně v závislosti na tom, kdo ho spouští. Pro jednoho uživatele může použít /usr/bin/python, Který byl nainstalován v operačním systému. Jinak by mohlo použít experimentální /home/phred/bin/python, Který nefunguje úplně správně.

A pokud je python nainstalován pouze v /usr/local/bin, Uživatel, který nemá /usr/local/bin V $PATH, Nebude moci skript spustit. (To pravděpodobně není příliš pravděpodobné u moderních systémů, ale mohlo by se to snadno stát pro nejasnějšího tlumočníka.)

Zadáním #!/usr/bin/python Určíte přesně, který interpret bude použit ke spuštění skriptu v konkrétním systému .

Dalším potencionálním problémem je, že trik #!/usr/bin/envnedovolí předat argumenty intrepreter (jiný než název skriptu, který je implicitně předán). Toto obvykle není problém, ale může to být. Mnoho skriptů v Perlu je psáno s #!/usr/bin/Perl -w, Ale use warnings; Je v těchto dnech doporučenou náhradou. Csh skripty by měly používat #!/bin/csh -f - ale csh skripty jsou nedoporučuje se na prvním místě. Mohly by však existovat i jiné příklady.

Mám několik skriptů v jazyce Perl v systému řízení osobních zdrojů, který instaluji, když nastavím účet v novém systému. Používám instalační skript, který upravuje řádek #! Každého skriptu, když jej instaluje do mého $HOME/bin. (V poslední době jsem nemusel používat nic jiného než #!/usr/bin/Perl; To sahá až do doby, kdy Perl nebyl standardně nainstalován.)

Menší bod: trik #!/usr/bin/env Je pravděpodobně zneužitím příkazu env, který byl původně určen (jak název napovídá) vyvolat příkaz se změněným prostředím. Navíc některé starší systémy (včetně SunOS 4, pokud si dobře pamatuji) neměly příkaz env v /usr/bin. Ani jedno z nich pravděpodobně nebude mít velký význam. env funguje tímto způsobem, mnoho skriptů používá trik #!/usr/bin/env A poskytovatelé OS pravděpodobně neudělají nic, aby jej porušili. Může to být problém , pokud chcete, aby váš skript fungoval na opravdu starém systému, ale pravděpodobně ho budete muset přesto upravit.

Dalším možným problémem (díky Sopalajo de Arrierez za to, že na to poukázal v komentářích) je to, že cronové úlohy běží s omezeným prostředím. Zejména $PATH Je obvykle něco jako /usr/bin:/bin. Pokud se tedy adresář obsahující tlumočník nenachází v jednom z těchto adresářů, i když je ve výchozím nastavení $PATH V uživatelském prostředí, trik /usr/bin/env Nebude práce. Můžete zadat přesnou cestu, nebo můžete přidat do svého crontabu řádek a nastavit $PATH (man 5 crontab Pro podrobnosti).

491
Keith Thompson

Protože/usr/bin/env dokáže interpretovat vaše $PATH, díky kterému jsou skripty přenositelnější.

#!/usr/local/bin/python

Skript bude spuštěn pouze v případě, že python je nainstalován v/usr/local/bin).

#!/usr/bin/env python

Bude interpretovat vaše $PATH a najděte python v libovolném adresáři ve vašem $PATH.

Skript je tedy přenosnější a bude fungovat bez úprav na systémech, kde python je nainstalován jako /usr/bin/python nebo /usr/local/bin/python nebo dokonce vlastní adresáře (které byly přidány do $PATH), jako /opt/local/bin/python.

Přenositelnost je jediným důvodem, proč je použití env upřednostněno před pevně určenými cestami.

105
Tim Kennedy

Určení absolutní cesty je v daném systému přesnější. Nevýhodou je, že je to příliš přesné. Předpokládejme, že si uvědomíte, že instalace systému Perl je pro vaše skripty příliš stará a chcete místo toho použít vlastní: pak musíte skripty upravit a změnit #!/usr/bin/Perl to #!/home/myname/bin/Perl. Horší je, pokud máte Perla v /usr/bin na některých strojích, /usr/local/bin na ostatních a /home/myname/bin/Perl na dalších strojích, pak byste si měli ponechat tři oddělené kopie skriptů a na každém stroji provést odpovídající kopii.

#!/usr/bin/env se zlomí, pokud je PATH špatné, ale dělá to téměř cokoli. Pokus o práci se špatným PATH je velmi zřídka užitečný a naznačuje, že o systému, ve kterém je skript spuštěn, víte velmi málo, takže se nemůžete spolehnout na žádnou absolutní cestu.

Existují dva programy, na jejichž umístění se můžete spolehnout téměř na každou unixovou variantu: /bin/sh a /usr/bin/env. Některé obskurní a většinou vysloužilé unixové varianty měly /bin/env bez nutnosti /usr/bin/env, ale je nepravděpodobné, že se s nimi setkáte. Moderní systémy mají /usr/bin/env právě kvůli jeho rozšířenému použití v shebangs. /usr/bin/env je něco, na co se můžete spolehnout.

Na rozdíl od /bin/sh, jediný čas, kdy byste měli použít absolutní cestu v Shebangu, je, když váš skript nemá být přenosný, takže můžete počítat se známým umístěním tlumočníka. Například bash skript, který funguje pouze na Linuxu, může bezpečně použít #!/bin/bash. Skript, který má být používán pouze interně, se může spoléhat na konvence lokalizačního tlumočníka.

#!/usr/bin/env má nevýhody. Je to flexibilnější než zadání absolutní cesty, ale stále vyžaduje znát jméno tlumočníka. Někdy můžete chtít spustit tlumočníka, který není v $PATH, například v umístění vzhledem ke skriptu. V takových případech můžete často vytvořit polyglot skript , který může být interpretován jak standardním Shell, tak požadovaným interpretem. Například učinit skript Python 2 přenosný skript do systémů, kde python je Python 3 a python2 je Python 2, a do systémů, kde python je Python 2 a python2 neexistuje:

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

Konkrétně pro Perl pomocí #!/usr/bin/env je špatný nápad ze dvou důvodů.

Za prvé, není přenosný. Na některých temných platformách není env v/usr/bin. Za druhé, jak poznamenal Keith Thompson , může to způsobit potíže s předáváním argumentů na linii Shebang. Maximálně přenosné řešení je toto:

#!/bin/sh
exec Perl -x "$0" "[email protected]"
#!Perl

Podrobnosti o tom, jak to funguje, viz 'perldoc perlrun' a co říká o argumentu -x.

23
DrHyde

Důvodem je rozdíl mezi těmito dvěma je kvůli tomu, jak jsou skripty prováděny.

Používání /usr/bin/env (Které, jak je uvedeno v jiných odpovědích, není v některých operačních systémech v /usr/bin), Je vyžadováno, protože po #! Nelze vložit spustitelný název - musí to být absolutní cesta. Je to proto, že mechanismus #! Funguje na nižší úrovni než Shell. Je součástí binárního zavaděče jádra. To lze otestovat. Vložte to do souboru a označte jej jako spustitelný:

#!bash

echo 'foo'

Zjistíte, že při pokusu o spuštění vytiskne takovou chybu:

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

Pokud je soubor označen jako spustitelný a začíná znakem #!, Jádro (které neví o $PATH Nebo aktuální adresář: jedná se o koncepty uživatelských zemí) vyhledá soubor pomocí absolutní cesta. Protože použití absolutní cesty je problematické (jak je uvedeno v jiných odpovědích), někdo přišel s trikem: Můžete spustit /usr/bin/env (Což je téměř vždy na tomto místě) a něco spustit pomocí $PATH .

16
Tiffany Bennett

S použitím #!/usr/bin/env Jsou další dva problémy

  1. To neřeší problém určení úplné cesty k tlumočníkovi, pouze jej přesune na env.

    env není zaručeno, že bude v /usr/bin/env než bash je zaručeno, že bude v /bin/bash nebo python v /usr/bin/python.

  2. env přepíše ARGV [0] jménem tlumočníka (např. bash nebo python).

    To zabrání tomu, aby se jméno vašeho skriptu objevilo např. Ve výstupu ps (nebo změnilo, jak/kde se objeví) a znemožňuje jej najít např. Pomocí ps -C scriptname.sh

[aktualizace 2016-06-04]

A třetí problém:

  1. Změna vaší cesty je více práce, než jen úprava prvního řádku skriptu, zejména když skriptování takových úprav je triviální. např:

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

    Přidání nebo předběžné přijetí adresáře na $ PATH je poměrně snadné (i když stále musíte upravit soubor, aby byl trvalý - váš ~/.profile Nebo cokoli - a není snadné skript THAT upravovat, protože PATH by mohl být umístěna kdekoli ve skriptu, nikoli v prvním řádku).

    Změna pořadí adresářů PATH je podstatně obtížnější .... a mnohem těžší, než jen úprava řádku #!.

    A stále máte všechny další problémy, které vám přináší použití #!/usr/bin/env.

    @jlliagre v komentáři navrhuje, že #!/usr/bin/env je užitečný pro testování skriptu s více verzemi tlumočníků, „pouze změnou jejich pořadí PATH/PATH“

    Pokud to potřebujete udělat, je mnohem jednodušší mít v horní části skriptu několik řádků #! (Jsou to jen komentáře kdekoli kromě prvního řádku) a vyjmout/zkopírovat a vložit jeden chcete použít hned na první řádek.

    V vi by to bylo stejně jednoduché jako přesunutí kurzoru na požadovaný řádek #!, Poté zadejte dd1GP Nebo Y1GP. I s tak triviálním editorem jako nano by kopírování a vkládání pomocí myši trvalo několik sekund.


Výhody používání #!/usr/bin/env Jsou celkově přinejlepším minimální a rozhodně se nepřibližují nevýhodám. Dokonce i výhoda „pohodlí“ je do značné míry iluzorní.

IMO, je to hloupá myšlenka propagovaná určitým druhem programátora, který si myslí, že operační systémy nejsou něco, s čím by se mělo pracovat, jsou problém, který se má obejít (přinejmenším ignorovat).

PS: Zde je jednoduchý skript pro změnu tlumočníka více souborů najednou.

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 "[email protected]" ; do
  printf "%s\n" 1 i "$Shebang" . w | ed "$f"
done

Spusťte jej jako např. change-Shebang.sh python2.7 *.py Nebo change-Shebang.sh $HOME/bin/my-experimental-Ruby *.rb

11
cas

Zde přidám další příklad:

Použití env je také užitečné, když chcete například sdílet skripty mezi více rvm prostředími.

Po spuštění na řádku cmd se zobrazí, která Ruby bude použita, když #!/usr/bin/env Ruby se používá uvnitř skriptu:

env Ruby --version

Proto při použití env můžete použít různé Ruby prostřednictvím rvm, aniž byste museli měnit skripty).

9
Not Now

Pokud píšete čistě pro sebe nebo pro svou práci a místo tlumočníka, kterého voláte, je vždy na stejném místě, v každém případě použijte přímou cestu. Ve všech ostatních případech použijte #!/usr/bin/env.

Zde je důvod: Ve vaší situaci byl tlumočník python na stejném místě bez ohledu na syntaxi, kterou jste použili, ale pro mnoho lidí se mohl nainstalovat na jiné místo. Ačkoli většina hlavních tlumočníků programování je umístěna v /usr/bin/ spousta novějšího softwaru je nastavena na /usr/local/bin/.

Dokonce bych tvrdil, že vždy používám #!/usr/bin/env protože pokud máte nainstalováno více verzí stejného tlumočníka a nevíte, na co bude vaše Shell výchozí, měli byste to pravděpodobně opravit.

5
Mauvis Ledford

Pro přenositelnost a kompatibilitu je lepší použít

#!/usr/bin/env bash

namísto

#!/usr/bin/bash

Existuje mnoho možností, kde binární může být umístěno v systému Linux/Unix. Podrobný popis hierarchie systému souborů najdete na stránce hier (7).

FreeBSD například nainstaluje veškerý software, který není součástí základního systému, do /usr/local /. Protože bash není součástí základního systému, bash binární je nainstalován na /usr/local/bin/bash.

Pokud chcete přenosný bash/csh/Perl/jakýkoli skript, který běží pod většinou linuxových distribucí a FreeBSD, měli byste použít #!/Usr/bin/env.

Také si všimněte, že většina instalací Linuxu také (tvrdě) propojila env binární s/bin/env nebo softlinked/usr/bin do/bin, který by měl být ne použit v Shebangu. Nepoužívejte tedy #!/Bin/env.

3
Mirko Steiner