Dlaczego lepiej używać „#! / Usr / bin / env NAME” zamiast „#! / Path / to / NAME” jako mojego shebang?


458

Zauważam, że niektóre skrypty, które nabyłem od innych, mają shebang, #!/path/to/NAMEpodczas gdy inne (używając tego samego narzędzia, NAME) mają shebang #!/usr/bin/env NAME.

Oba wydają się działać poprawnie. W samouczkach (na przykład w języku Python) wydaje się, że istnieje sugestia, że ​​ten ostatni shebang jest lepszy. Ale nie do końca rozumiem, dlaczego tak jest.

Zdaję sobie sprawę z tego, że aby użyć tego ostatniego szangletu, NAZWA musi znajdować się w ŚCIEŻCE, podczas gdy pierwszy szejk nie ma tego ograniczenia.

Wydaje mi się także (dla mnie), że pierwszy byłby lepszym shebangiem, ponieważ precyzuje dokładnie, gdzie znajduje się NAME. W takim przypadku, jeśli istnieje wiele wersji NAME (np. / Usr / bin / NAME, / usr / local / bin / NAME), pierwszy przypadek określa, którego użyć.

Moje pytanie brzmi: dlaczego pierwszy shebang jest lepszy od drugiego?


12
Zobacz tę odpowiedź ...
jasonwryan

@ TheGeeko61: W moim przypadku coś się zepsuło, a niektórych zmiennych nie było w env. Proponuję więc użyć tego shebang, aby sprawdzić, czy env jest poprawnie załadowany.
Gigamegs,

Odpowiedzi:


462

To niekoniecznie lepsze.

Zaletą #!/usr/bin/env pythonjest to, że użyje dowolnego pythonpliku wykonywalnego, który pojawi się jako pierwszy w użytkowniku $PATH.

Wadą z #!/usr/bin/env pythonjest to, że będzie używać cokolwiek pythonwykonywalny pierwszy pojawia się w instrukcji $PATH.

Oznacza to, że skrypt może zachowywać się inaczej w zależności od tego, kto go uruchamia. W przypadku jednego użytkownika może korzystać z /usr/bin/pythonzainstalowanego z systemem operacyjnym. Po drugie, może użyć eksperymentu /home/phred/bin/python, który nie działa poprawnie.

A jeśli pythonjest zainstalowany tylko /usr/local/binużytkownik, który nie ma /usr/local/binna $PATHnie nawet w stanie uruchomić skrypt. (Prawdopodobnie nie jest to zbyt prawdopodobne w nowoczesnych systemach, ale może się to zdarzyć w przypadku bardziej niejasnego tłumacza.)

Określając #!/usr/bin/python, dokładnie określasz, który interpreter będzie używany do uruchomienia skryptu w określonym systemie .

Innym potencjalnym problemem jest to, że #!/usr/bin/envsztuczka nie pozwala ci przekazywać argumentów do interentera (innych niż nazwa skryptu, która jest przekazywana niejawnie). To zwykle nie jest problemem, ale może być. Wiele skryptów Perla jest napisanych przy użyciu #!/usr/bin/perl -w, ale obecnie use warnings;jest to zalecany zamiennik. Skrypty #!/bin/csh -fcsh powinny być używane - ale skrypty csh niezalecane . Ale mogą być inne przykłady.

Mam wiele skryptów Perla w osobistym systemie kontroli źródła, które instaluję, kiedy zakładam konto w nowym systemie. Używam skryptu instalatora, który modyfikuje #!wiersz każdego skryptu, gdy instaluje go w moim $HOME/bin. (Nie musiałem używać niczego innego niż #!/usr/bin/perlostatnio; to sięga czasów, gdy Perl często nie był domyślnie instalowany).

Drobna uwaga: #!/usr/bin/envsztuczka jest prawdopodobnie nadużyciem envpolecenia, które pierwotnie miało (jak sama nazwa wskazuje) wywoływać polecenie w zmienionym środowisku. Ponadto niektóre starsze systemy (w tym SunOS 4, jeśli dobrze pamiętam) nie miały tego envpolecenia /usr/bin. Żaden z nich prawdopodobnie nie będzie stanowić poważnego problemu. envdziała w ten sposób, wiele skryptów używa tej #!/usr/bin/envsztuczki, a dostawcy systemów operacyjnych prawdopodobnie nie zrobią nic, aby ją złamać. Może to być problem, jeśli chcesz, aby skrypt działał na naprawdę starym systemie, ale prawdopodobnie i tak będziesz musiał go zmodyfikować.

Innym możliwym problemem (dzięki Sopalajo de Arrierez za wskazanie tego w komentarzach) jest to, że zadania cron działają w ograniczonym środowisku. W szczególności $PATHjest to zwykle coś w rodzaju /usr/bin:/bin. Więc jeśli katalog zawierający interpreter nie znajduje się w jednym z tych katalogów, nawet jeśli jest domyślnie $PATHw powłoce użytkownika, /usr/bin/envsztuczka nie zadziała. Możesz podać dokładną ścieżkę lub dodać linię do swojego crontab, aby ustawić $PATH( man 5 crontabszczegółowe informacje).


4
Jeśli / usr / bin / perl ma wartość perl 5.8, $ HOME / bin / perl ma wartość 5.12, a skrypt wymagający kodów twardych 5.12 / usr / bin / perl w sekwensach, uruchomienie skrypt może być dużym utrudnieniem. Rzadko zdarza mi się, aby posiadanie / usr / bin / env perl grab perl z PATH było problemem, ale często jest bardzo pomocne. I jest o wiele ładniejszy niż hack exec!
William Pursell,

8
Warto zauważyć, że jeśli chcesz użyć konkretnej wersji interpretera, / usr / bin / env jest jeszcze lepszy. po prostu dlatego, że na twoim komputerze zwykle jest zainstalowanych wiele wersji interpretera o nazwie perl5, perl5.12, perl5.10, python3.3, python3.32 itd. i jeśli twoja aplikacja została przetestowana tylko na tej konkretnej wersji, nadal możesz określić #! / usr / bin / env perl5.12 i bądź w porządku, nawet jeśli użytkownik zainstalował go w nietypowym miejscu. Z mojego doświadczenia wynika, że ​​„python” jest zwykle tylko dowiązaniem symbolicznym do standardowej wersji systemu (niekoniecznie najnowszej wersji w systemie).
rootowanie

6
@root: Dla Perl, use v5.12;służy niektóre z tych celów. I #!/usr/bin/env perl5.12zawiedzie, jeśli system ma Perla 5.14, ale nie 5.12. Dla Python 2 vs. 3 #!/usr/bin/python2i #!/usr/bin/python3prawdopodobnie będą działać.
Keith Thompson,

3
@GoodPerson: Załóżmy, że piszę skrypt Perla do zainstalowania /usr/local/bin. Zdarza mi się wiedzieć, że działa poprawnie /usr/bin/perl. Nie mam pojęcia, czy działa z dowolnym perlplikiem wykonywalnym, który przypadkowo ma u niego jakiś przypadkowy użytkownik $PATH. Może ktoś eksperymentuje z jakąś starożytną wersją Perla; ponieważ określiłem #!/usr/bin/perl, mój skrypt (którego użytkownik niekoniecznie zna lub obchodzi się ze skryptem Perla) nie przestanie działać.
Keith Thompson

5
Jeśli to nie zadziała /usr/bin/perl, dowiem się bardzo szybko, a obowiązkiem właściciela / administratora systemu jest aktualizowanie go. Jeśli chcesz uruchomić mój skrypt z własnym perlem, możesz pobrać i zmodyfikować kopię lub wywołać ją za pośrednictwem perl foo. (I możesz wziąć pod uwagę możliwość, że 55 osób, które głosowały za odpowiedzią, również wiedzą coś lub dwa. Z pewnością możliwe jest, że masz rację i oni wszyscy się mylą, ale nie tak się zakładam).
Keith Thompson

101

Ponieważ / usr / bin / env może interpretować twoje $PATH, co czyni skrypty bardziej przenośnymi.

#!/usr/local/bin/python

Uruchomi skrypt tylko, jeśli Python jest zainstalowany w / usr / local / bin.

#!/usr/bin/env python

Zinterpretuje twój $PATHi znajdzie python w dowolnym katalogu w twoim $PATH.

Więc twój skrypt jest bardziej mobilny i będzie działać bez żadnych modyfikacji w systemach, w których zainstalowany jest jak pyton /usr/bin/python, lub /usr/local/bin/python, lub nawet niestandardowych katalogów (które zostały dodane do $PATH), jak /opt/local/bin/python.

Przenośność jest jedynym powodem, dla którego użycie envjest preferowane zamiast ścieżek zakodowanych na stałe.


19
Niestandardowe katalogi pythonplików wykonywalnych są szczególnie powszechne wraz ze virtualenvwzrostem użycia.
Xiong Chiamiov

1
A #! pythondlaczego nie jest to używane?
kristianp

6
#! pythonnie jest używany, ponieważ musiałbyś znajdować się w tym samym katalogu co plik binarny Pythona, ponieważ słowo pythonto jest interpretowane jako pełna ścieżka do pliku. Jeśli nie masz pliku binarnego Python w bieżącym katalogu, pojawi się błąd typu bash: ./script.py: python: bad interpreter: No such file or directory. To tak, jakbyś używał#! /not/a/real/path/python
Tim Kennedy

47

Określenie ścieżki bezwzględnej jest bardziej precyzyjne w danym systemie. Minusem jest to, że jest zbyt precyzyjne. Załóżmy, że zdajesz sobie sprawę, że systemowa instalacja Perla jest zbyt stara dla twoich skryptów i zamiast tego chcesz użyć własnej: wtedy musisz edytować skrypty i zmienić #!/usr/bin/perlna #!/home/myname/bin/perl. Co gorsza, jeśli masz Perla /usr/binna niektórych komputerach, /usr/local/binna innych i /home/myname/bin/perljeszcze na innych komputerach, będziesz musiał zachować trzy osobne kopie skryptów i wykonać odpowiedni na każdym komputerze.

#!/usr/bin/envpsuje się, jeśli PATHjest źle, ale robi prawie wszystko. Próba działania ze złym PATHjest bardzo rzadko przydatna i wskazuje, że wiesz bardzo mało o systemie, w którym działa skrypt, więc nie możesz polegać na żadnej bezwzględnej ścieżce.

Istnieją dwa programy, w których lokalizacji można polegać na prawie każdym wariancie uniksowym: /bin/shi /usr/bin/env. Niektóre niejasne i przeważnie wycofane warianty uniksowe miały je /bin/envbez /usr/bin/env, ale raczej ich nie spotkasz. Nowoczesne systemy mają /usr/bin/envwłaśnie swoje szerokie zastosowanie w shebangach. /usr/bin/envjest coś, na co możesz liczyć.

Poza /bin/shtym jedyną sytuacją, w której powinieneś użyć bezwzględnej ścieżki w shebang, jest to, że twój skrypt nie ma być przenośny, więc możesz liczyć na znaną lokalizację dla tłumacza. Na przykład można bezpiecznie używać skryptu bash, który działa tylko w systemie Linux #!/bin/bash. Skrypt przeznaczony wyłącznie do użytku wewnętrznego może polegać na konwencjach lokalizacji interpretera domowego.

#!/usr/bin/envma wady. Jest bardziej elastyczny niż określenie ścieżki bezwzględnej, ale nadal wymaga znajomości nazwy tłumacza. Czasami możesz chcieć uruchomić interpreter, którego nie ma $PATH, na przykład w lokalizacji względem skryptu. W takich przypadkach często można utworzyć skrypt poliglota, który może być interpretowany zarówno przez standardową powłokę, jak i przez żądanego tłumacza. Na przykład, aby uczynić skrypt Python 2 przenośnym zarówno dla systemów, w których pythonjest Python 3 i python2Python 2, jak i dla systemów, w których pythonPython 2 i python2nie istnieje:

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

Używanie w Perlu #!/usr/bin/envjest złym pomysłem z dwóch powodów.

Po pierwsze, nie jest przenośny. Na niektórych niejasnych platformach env nie znajduje się w / usr / bin. Po drugie, jak zauważył Keith Thompson , może powodować problemy z przekazywaniem argumentów w linii shebang. Maksymalnie przenośne rozwiązanie to:

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

Aby uzyskać szczegółowe informacje na temat tego, jak to działa, zobacz „perldoc perlrun” i co mówi o argumencie -x.


Dokładnie anser, którego szukałem: jak napisać porowatą „shebang” dla skryptu perl, który pozwoli na przekazanie dodatkowych argumentów do perla (ostatnia linia w twoim przykładzie akceptuje dodatkowe argumenty).
AmokHuginnsson

15

Różnica między nimi wynika z tego, w jaki sposób skrypty są wykonywane.

Użycie /usr/bin/env(które, jak wspomniano w innych odpowiedziach, nie jest dostępne w /usr/binniektórych systemach operacyjnych) jest wymagane, ponieważ nie można po prostu umieścić nazwy pliku wykonywalnego po #!- musi to być ścieżka bezwzględna. Wynika to z faktu, że #!mechanizm działa na niższym poziomie niż powłoka. Jest częścią binarnego modułu ładującego jądra. Można to przetestować. Umieść to w pliku i zaznacz jako plik wykonywalny:

#!bash

echo 'foo'

Przekonasz się, że drukuje taki błąd podczas próby uruchomienia:

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

Jeśli plik jest oznaczony jako wykonywalny i zaczyna się od a #!, jądro (które nie wie o tym $PATHlub o bieżącym katalogu: są to koncepcje użytkownika-użytkownika) szuka pliku przy użyciu bezwzględnej ścieżki. Ponieważ używanie ścieżki bezwzględnej jest problematyczne (jak wspomniano w innych odpowiedziach), ktoś wpadł na podstęp: możesz uruchomić /usr/bin/env(który prawie zawsze jest w tej lokalizacji), aby uruchomić coś przy użyciu $PATH.


11

Istnieją jeszcze dwa problemy z używaniem #!/usr/bin/env

  1. Nie rozwiązuje problemu określenia pełnej ścieżki do tłumacza, po prostu ją przenosi env.

    envwięcej nie gwarantuje się w /usr/bin/envniż bashjest gwarancją w /bin/bashlub w pyton /usr/bin/python.

  2. env zastępuje ARGV [0] nazwą interpretera (np. bash lub python).

    Zapobiega to pojawianiu się nazwy skryptu np. W psdanych wyjściowych (lub zmienia sposób / gdzie pojawia się) i uniemożliwia znalezienie go za pomocą np.ps -C scriptname.sh

[aktualizacja 2016-06-04]

I trzeci problem:

  1. Zmiana ŚCIEŻKI to więcej pracy niż tylko edytowanie pierwszego wiersza skryptu, szczególnie gdy skryptowanie takich edycji jest banalne. na przykład:

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

    Dołączanie lub oczekiwanie na katalog do $ PATH jest dość łatwe (chociaż nadal musisz edytować plik, aby stał się trwały - twój ~/.profilelub cokolwiek innego - i daleko mu do łatwego skryptu TEGO edytowania, ponieważ PATH można ustawić w dowolnym miejscu skryptu , nie w pierwszym wierszu).

    Zmiana kolejności katalogów PATH jest znacznie trudniejsza ... i znacznie trudniejsza niż tylko edycja #!wiersza.

    I nadal masz wszystkie inne problemy, które #!/usr/bin/envdaje ci używanie .

    @jlliagre sugeruje w komentarzu #!/usr/bin/envprzydatnym do testowania skryptu z wieloma wersjami interpreterów, „zmieniając tylko ich kolejność ŚCIEŻKA / ŚCIEŻKA”

    Jeśli musisz to zrobić, o wiele łatwiej jest po prostu mieć kilka #!wierszy u góry skryptu (są to tylko komentarze w dowolnym miejscu, ale nie pierwszy wiersz) i wyciąć / skopiować i wkleić ten, którego chcesz teraz użyć, aby pierwsza linia.

    W vito byłoby tak proste, jak przesunięcie kursora do żądanej #!linii, a następnie wpisanie dd1GPlub Y1GP. Nawet przy tak trywialnym edytorze nanoużycie myszki do skopiowania i wklejenia zajęłoby kilka sekund.


Ogólnie rzecz biorąc, zalety korzystania #!/usr/bin/envsą w najlepszym razie minimalne, a na pewno nie są nawet większe niż wady. Nawet przewaga „wygody” jest w dużej mierze iluzoryczna.

IMO, to głupi pomysł promowany przez pewnego rodzaju programistę, który uważa, że ​​systemy operacyjne nie są czymś, z czym można pracować, są problemem, który należy obejść (lub w najlepszym razie zignorować).

PS: oto prosty skrypt do zmiany interpretera wielu plików jednocześnie.

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

Uruchom jako np. change-shebang.sh python2.7 *.pyLubchange-shebang.sh $HOME/bin/my-experimental-ruby *.rb


4
Nikt inny tutaj nie wspomniał o problemie ARGV [0]. I nikt nie wspominał o problemie / path / to / env w formie, która bezpośrednio odnosi się do jednego z argumentów przemawiających za jego użyciem (tj. Bash lub perl może znajdować się w nieoczekiwanym miejscu).
cas

2
Problem ścieżki interpretera można łatwo naprawić przez administratora systemu z dowiązaniami symbolicznymi lub przez użytkownika edytującego swój skrypt. Z pewnością nie jest to wystarczająco znaczący problem, aby zachęcić ludzi do pogodzenia się z wszystkimi innymi problemami spowodowanymi użyciem env na linii shebang, które są wymienione tutaj i na inne pytania. To promuje złe rozwiązanie w taki sam sposób, jak zachęcanie do cshpisania skryptów promuje złe rozwiązanie: to działa, ale istnieją znacznie lepsze alternatywy.
cas

3
osoby niebędące administratorami systemu mogą poprosić o to swojego administratora. lub mogą po prostu edytować skrypt i zmienić #!wiersz.
cas

2
Właśnie tego env pomaga uniknąć: w zależności od wiedzy technicznej sysadmin lub systemu operacyjnego niezwiązanej z pythonem lub czymkolwiek innym.
jlliagre,

1
Chciałbym móc wypowiedzieć się na ten temat tysiąc razy, ponieważ tak naprawdę jest to świetna odpowiedź w obu przypadkach - jeśli ktoś korzysta /usr/bin/envi muszę się go pozbyć lokalnie, lub jeśli nie korzysta z niego i muszę go dodać. Oba przypadki mogą wystąpić, dlatego skrypty przedstawione w tej odpowiedzi są potencjalnie przydatnym narzędziem w zestawie narzędzi.
jstine

8

Dodając kolejny przykład tutaj:

Korzystanie envjest również przydatne, gdy na przykład chcesz udostępniać skrypty między wieloma rvmśrodowiskami.

Uruchomienie tego w wierszu cmd pokazuje, która wersja Ruby będzie używana, gdy #!/usr/bin/env rubyzostanie użyta w skrypcie:

env ruby --version

Dlatego, kiedy używasz env, możesz używać różnych wersji Ruby przez rvm, bez zmiany skryptów.


1
// , Świetny pomysł. Istotą wielu tłumaczy to, aby nie złamać kod, lub mieć kod zależeć od tego konkretnego tłumacza .
Nathan Basanese

4

Jeśli piszesz wyłącznie dla siebie lub dla swojej pracy, a lokalizacja tłumacza jest zawsze w tym samym miejscu, skorzystaj z bezpośredniej ścieżki. We wszystkich innych przypadkach użyj #!/usr/bin/env.

Oto dlaczego: w twojej sytuacji pythontłumacz był w tym samym miejscu, niezależnie od użytej składni, ale dla wielu osób mógł być zainstalowany w innym miejscu. Chociaż większość głównych tłumaczy programistycznych znajduje się w /usr/bin/wielu nowszych programach domyślnie /usr/local/bin/.

Chciałbym nawet argumentować, aby zawsze używać, #!/usr/bin/envponieważ jeśli masz wiele wersji tego samego interpretera i nie wiesz, jakie ustawienia domyślne ma twoja powłoka, prawdopodobnie powinieneś to naprawić.


5
„We wszystkich innych przypadkach użycie #! / Usr / bin / env” jest zbyt silne. // Jak wskazują @KeithThompson i inni, #! / Usr / bin / env oznacza, że ​​skrypt może zachowywać się inaczej w zależności od tego, kto / jak działa. Czasami to inne zachowanie może być błędem bezpieczeństwa. // Na przykład NIGDY nie używaj „#! / Usr / bin / env python” dla skryptu setuid lub setgid, ponieważ użytkownik wywołujący skrypt może umieścić złośliwe oprogramowanie w pliku o nazwie „python” w PATH. To, czy skrypty setuid / gid są dobrym pomysłem, to inna sprawa - ale z pewnością żaden plik wykonywalny setuid / gid nigdy nie powinien ufać środowisku zapewnionemu przez użytkownika.
Krazy Glew

@KrazyGlew, nie można skonfigurować skryptu w systemie Linux. Robisz to poprzez plik wykonywalny. Pisząc ten plik wykonywalny, dobrą praktyką i powszechnie wykonywaną jest usuwanie zmiennych środowiskowych. Niektóre zmienne środowiskowe są również celowo ignorowane .
MayeulC,

3

Ze względu na przenośność i kompatybilność lepiej jest używać

#!/usr/bin/env bash

zamiast

#!/usr/bin/bash

Istnieje wiele możliwości, gdzie plik binarny może znajdować się w systemie Linux / Unix. Sprawdź stronę podręcznika hier (7), aby uzyskać szczegółowy opis hierarchii systemu plików.

Na przykład FreeBSD instaluje całe oprogramowanie, które nie jest częścią systemu podstawowego, w / usr / local / . Ponieważ bash nie jest częścią systemu podstawowego, plik bash jest instalowany w / usr / local / bin / bash .

Jeśli chcesz mieć przenośny skrypt bash / csh / perl / cokolwiek, który działa pod większością dystrybucji Linuksa i FreeBSD, powinieneś użyć #! / Usr / bin / env .

Zauważ też, że większość instalacji Linuksa również (twardo) połączyła plik binarny env z / bin / env lub linkami / usr / bin do / bin, które nie powinny być używane w shebang. Więc nie używaj #! / Bin / env .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.