Jaka jest różnica między „#! / Usr / bin / env bash” i „#! / Usr / bin / bash”?

371

W nagłówku skryptu Bash jaka jest różnica między tymi dwiema instrukcjami:

  1. #!/usr/bin/env bash

  2. #!/usr/bin/bash

Kiedy przejrzałem env stronę podręcznika , otrzymałem następującą definicję:

 env - run a program in a modified environment

Co to znaczy?

tarrsalah
źródło
7
Zobacz to pytanie i moją odpowiedź .
Keith Thompson
6
Kto może mi powiedzieć, dlaczego to pytanie jest zamknięte, „związane z programowaniem lub tworzeniem oprogramowania”, prawda?
tarrsalah
1
Zgadzam się, że nie jest to nie na temat, ale prawdopodobnie jest duplikatem kilku innych pytań, takich jak to .
Keith Thompson
16
To pytanie nie powinno być oznaczone jako nie na temat. Potrzebuje tylko 5 osób z wynikiem powyżej 3000, aby oznaczyć go jako „na temat” i można go ponownie otworzyć. To pytanie - konkretnie dotyczące programowania.
Danijel-James W
3
Jestem zszokowany. Zszokowany faktem, że dokumentacja Linuksa jest pełna tautologii. xkcd.com/703 git-man-page-generator.lokaltog.net
allyourcode

Odpowiedzi:

323

Uruchamianie pomocy komendy /usr/bin/envma korzyść szuka cokolwiek domyślna wersja programu jest w aktualnej env ironment.

W ten sposób nie musisz szukać go w określonym miejscu w systemie, ponieważ ścieżki te mogą znajdować się w różnych lokalizacjach w różnych systemach. Tak długo, jak będzie na twojej ścieżce, będzie ją znajdować.

Jednym minusem jest to, że nie będziesz w stanie przekazać więcej niż jednego argumentu (np. Nie będziesz mógł pisać /usr/bin/env awk -f), jeśli chcesz obsługiwać Linuksa, ponieważ POSIX jest niejasny w kwestii interpretacji linii, a Linux interpretuje wszystko po pierwszym spacja oznaczająca pojedynczy argument. Możesz użyć /usr/bin/env -Sniektórych wersji, envaby obejść ten problem, ale wtedy skrypt stanie się jeszcze mniej przenośny i zepsuje się na dość niedawnych systemach (np. Nawet Ubuntu 16.04, jeśli nie później).

Inną wadą jest to, że ponieważ nie wywołujesz jawnego pliku wykonywalnego, może to powodować błędy, a także problemy z bezpieczeństwem systemów wielu użytkowników (na przykład, jeśli ktoś zdoła wywołać plik wykonywalny bashna twojej ścieżce).

#!/usr/bin/env bash #lends you some flexibility on different systems
#!/usr/bin/bash     #gives you explicit control on a given system of what executable is called

W niektórych sytuacjach pierwsza może być preferowana (np. Uruchamianie skryptów Pythona z wieloma wersjami Pythona, bez konieczności przerabiania wiersza wykonywalnego). Ale w sytuacjach, w których najważniejsze jest bezpieczeństwo, to drugie byłoby preferowane, ponieważ ogranicza możliwości wstrzykiwania kodu.

Alec Bennett
źródło
22
Kolejną wadą jest to, że nie można przekazać dodatkowego argumentu tłumaczowi.
Keith Thompson
1
@KeithThompson: Niepoprawne informacje .. Możesz przekazać opcje do tłumacza za pomocą / usr / bin / env!
Gaurav Agarwal
4
@GauravAgarwal: Nie w moim systemie. Skrypt zawierający tylko ten pojedynczy wiersz: #!/usr/bin/env echo Hellonarzeka: /usr/bin/env: echo Hello: No such file or directory. Najwyraźniej traktuje to echo Hellojako pojedynczy argument przeciwko /usr/bin/env.
Keith Thompson
1
@ AndréLaszlo: envPolecenie z pewnością pozwala na przekazanie argumentów do polecenia. Problemem jest semantyka #!linii, która zależy od jądra. Najnowsze jądra Linuksa pozwalają na takie rzeczy #!/usr/bin/env command args, ale starsze jądra Linuksa i inne systemy nie.
Keith Thompson,
1
Dlaczego w bloku kodu występują backtyki? Czy nie należy ich usuwać?
Benjamin W.
63

Użycie #!/usr/bin/env NAMEpowoduje, że powłoka szuka pierwszego dopasowania NAME w zmiennej środowiskowej $ PATH. Może być przydatny, jeśli nie znasz ścieżki bezwzględnej lub nie chcesz jej szukać.

Dolphiniac
źródło
9
Przynajmniej musisz wiedzieć, gdzie jest env :).
ᐅ devrimbaris
1
Doskonała odpowiedź. Wyjaśnia zwięźle, co robi env shebang, zamiast mówić „wybiera program na podstawie konfiguracji systemu”
De Novo,
13

Zamiast jawnego zdefiniowania ścieżki do interpretera za /usr/bin/bash/pomocą polecenia env, interpreter jest wyszukiwany i uruchamiany z dowolnego miejsca, w którym został znaleziony. Ma to zarówno zalety, jak i wady

bezpieczny
źródło
W większości systemów będą funkcjonalnie takie same, ale zależy to od lokalizacji plików wykonywalnych bash i env. Nie jestem jednak pewien, jak wpłynie to na zmienne środowiskowe.
sejf
2
„Możliwe jest określenie interpretera bez użycia env, podając pełną ścieżkę do interpretera. Problemem jest to, że w różnych systemach komputerowych dokładna ścieżka może być inna. Zamiast env, interpreter jest wyszukiwany i zlokalizowany w czas uruchomienia skryptu. To sprawia, że ​​skrypt jest bardziej przenośny, ale zwiększa również ryzyko wyboru niewłaściwego interpretera, ponieważ szuka on dopasowania w każdym katalogu na wykonywalnej ścieżce wyszukiwania. Ma również ten sam problem, ponieważ ścieżka do pliku binarnego env może być również różna dla poszczególnych komputerów. ”- Wikipedia
Mike Clark
10

Jeśli skrypty powłoki zaczynają się od #!/bin/bash, zawsze będą działać bashod /bin. Jeśli jednak zaczną od #!/usr/bin/env bash, będą wyszukiwać bashw, $PATHa następnie zaczną od pierwszego, jaki znajdą.

Dlaczego miałoby to być przydatne? Załóżmy, że chcesz uruchamiać bashskrypty, które wymagają wersji bash 4.x lub nowszej, ale twój system ma tylko bashzainstalowaną wersję 3.x, a obecnie twoja dystrybucja nie oferuje nowszej wersji lub nie jesteś administratorem i nie możesz zmienić tego, co jest zainstalowane w tym systemie .

Oczywiście możesz pobrać kod źródłowy bash i zbudować własny bash od zera, umieszczając go ~/binna przykład. Możesz także zmodyfikować $PATHzmienną w swoim .bash_profilepliku, aby uwzględnić ją ~/binjako pierwszy wpis ( PATH=$HOME/bin:$PATHponieważ ~nie będzie się rozwijał $PATH). Jeśli teraz zadzwonisz bash, powłoka najpierw poszuka go $PATHw kolejności, więc zaczyna się od miejsca ~/bin, w którym znajdzie twój bash. To samo dzieje się, jeśli skrypty szukają bashużycia #!/usr/bin/env bash, więc te skrypty będą teraz działać w systemie przy użyciu niestandardowej bashkompilacji.

Jednym minusem jest to, że może to prowadzić do nieoczekiwanego zachowania, np. Ten sam skrypt na tej samej maszynie może działać z różnymi interpretatorami dla różnych środowisk lub użytkowników z różnymi ścieżkami wyszukiwania, powodując różnego rodzaju bóle głowy.

Największym minusem envjest to, że niektóre systemy zezwalają tylko na jeden argument, więc nie można tego zrobić #!/usr/bin/env <interpreter> <arg>, ponieważ systemy będą postrzegane <interpreter> <arg>jako jeden argument (będą traktować to tak, jakby wyrażenie było cytowane), a zatem envbędą szukać tłumacza o nazwie <interpreter> <arg>. Zauważ, że nie jest to problem envsamej komendy, która zawsze pozwalała na przesłanie wielu parametrów, ale z parserem shebang systemu, który analizuje tę linię przed nawet wywołaniem env. Tymczasem zostało to naprawione w większości systemów, ale jeśli twój skrypt chce być bardzo przenośny, nie możesz polegać na tym, że zostało to naprawione w systemie, w którym będziesz działał.

Może nawet mieć wpływ na bezpieczeństwo, np. Jeśli sudonie został skonfigurowany do czyszczenia środowiska lub $PATHzostał wykluczony z czyszczenia. Pokażę to:

Zazwyczaj /binjest to dobrze chronione miejsce, tylko roottam może coś zmienić. Twój katalog domowy nie jest jednak w żadnym programie, który uruchamiasz, może wprowadzać w nim zmiany. Oznacza to, że złośliwy kod może umieścić fałszywy bashw jakimś ukrytym katalogu, zmodyfikować go tak, .bash_profileaby zawierał ten katalog w twoim $PATH, aby wszystkie używane skrypty #!/usr/bin/env bashdziałały z tym fałszywym bash. Jeśli sudotak się stanie $PATH, masz duże kłopoty.

Np. Rozważ narzędzie, które tworzy plik ~/.evil/basho następującej treści:

#!/bin/bash

if [ $EUID -eq 0 ]; then
  echo "All your base are belong to us..."
  # We are root - do whatever you want to do
fi

/bin/bash "$@"

Stwórzmy prosty skrypt sample.sh:

#!/usr/bin/env bash

echo "Hello World"

Dowód koncepcji (w systemie, w którym sudozachowuje $PATH):

$ ./sample.sh
Hello World

$ sudo ./sample.sh
Hello World

$ export PATH="$HOME/.evil:$PATH"

$ ./sample.sh
Hello World

$ sudo ./sample.sh
All your base are belong to us...
Hello World

Zwykle wszystkie powłoki powinny znajdować się w /binśrodku, a jeśli nie chcesz ich tam umieszczać z jakiegokolwiek powodu, naprawdę nie jest problemem umieszczanie dowiązania symbolicznego w /bintych punktach do ich prawdziwych lokalizacji (a może /binsam jest dowiązaniem symbolicznym), więc Zawsze chodziłbym z #!/bin/shi #!/bin/bash. Jest zbyt wiele, co by się zepsuło, gdyby te nie działały. To nie jest tak, że POSIX wymagałby takiej pozycji (POSIX nie standaryzuje nazw ścieżek, a tym samym wcale nie standaryzuje funkcji shebang), ale są one tak powszechne, że nawet jeśli system nie oferowałby /bin/sh, prawdopodobnie by to zrozumiał #!/bin/shi wiedzieć, co z tym zrobić i może to być tylko dla zgodności z istniejącym kodem.

Ale w przypadku bardziej nowoczesnych, niestandardowych, opcjonalnych interpreterów, takich jak Perl, PHP, Python lub Ruby, nie jest tak naprawdę nigdzie określone, gdzie powinny się znajdować. Mogą być /usr/bin, ale mogą także znajdować się w /usr/local/binlub w zupełnie innej branży (hierarchii /opt/..., /Applications/...itp). Dlatego często używają #!/usr/bin/env xxxskładni shebang.

Mecki
źródło
4

Uważam to za przydatne, ponieważ kiedy nie wiedziałem o env, zanim zacząłem pisać skrypt, robiłem to:

type nodejs > scriptname.js #or any other environment

a potem modyfikowałem tę linię w pliku do shebang.
Robiłem to, ponieważ nie zawsze pamiętałem, gdzie jest nodejs na moim komputerze - / usr / bin / lub / bin /, więc dla mnie envjest bardzo przydatny. Może są w tym szczegóły, ale to jest mój powód

sudo97
źródło