Dlaczego #! / Usr / bin / env bash jest lepszy od #! / Bin / bash?

212

Widziałem w wielu miejscach, w tym w zaleceniach na tej stronie ( jaki jest preferowany shebang Bash? ), Których należy używać #!/usr/bin/env bashprzed #!/bin/bash. Widziałem nawet jedną przedsiębiorczą osobę, która sugerowała, że ​​użycie #!/bin/bashbyło złe i funkcjonalność basha zostałaby utracona.

To powiedziawszy, używam bash w ściśle kontrolowanym środowisku testowym, w którym każdy dysk w obiegu jest zasadniczo klonem pojedynczego dysku głównego. Rozumiem argument dotyczący przenośności, choć niekoniecznie ma on zastosowanie w moim przypadku. Czy istnieje jakikolwiek inny powód, aby preferować #!/usr/bin/env bashalternatywne rozwiązania, a przy założeniu, że problemem była przenośność, czy istnieje jakiś powód, dla którego można by ją zepsuć?

spugm1r3
źródło
7
To niekoniecznie lepsze. Zobacz to pytanie i moją odpowiedź na unix.stackexchange.com. (Głosowałbym za zamknięciem tego jako duplikatu, ale nie sądzę, że można to zrobić w różnych witrynach).
Keith Thompson
2
Oprócz odpowiedzi @ zigg, envmoże nie znajdować się w /usr/bin. Komentarze Shebang są złym pomysłem IMHO. Jeśli domyślny interpreter skryptów nie obsługuje komentarzy shebang, jest to tylko komentarz. Jeśli jednak wiesz, że interpreter skryptu może obsłużyć komentarze shebang i znasz ścieżkę do bashu, nie ma powodu, aby nie wywoływać go przy użyciu jego ścieżki bezwzględnej, chyba że ścieżka jest za długa (mało prawdopodobna), lub możesz przenieść skrypt do systemu, w którym bash nie znajduje się w / bin. Z drugiej strony, wcześniej wspomniane zastrzeżenia mają zastosowanie w tym przypadku, ponieważ wiąże się z przenośnością.
1
@KeithThompson, dzięki za link. Być może moje wyszukiwanie odpowiedzi przed opublikowaniem pytania było trochę wąskie. Moje podejście do tego wszystkiego: (1) linux / unix / posix / etc ... jest szary, i (2) każdy, kto twierdzi, że ma absolutnie właściwą odpowiedź, absolutnie ma właściwą odpowiedź dla swojego konkretnego scenariusza.
spugm1r3
3
Zachowanie wielu rzeczy w POSIX / Unix jest dobrze zdefiniowane. Lokalizacje nie zawsze są tak wyraźne. Coś musi istnieć jak /etclub /bin/sh. bashjest dodatkiem do większości systemów uniksowych. Tylko Linux bashjest gwarantowany /bini najprawdopodobniej również jako /bin/sh. Odkąd Linux stał się nowoczesnym de facto Uniksem dla wielu ludzi, zapomniano o istnieniu systemów innych niż Linux. W mojej własnej odpowiedzi poniżej założyłem Linuxa, ponieważ tak powiedziałeś bash. Wiele urządzeń BSD, z którymi pracowałem, nawet nie ma zainstalowanych.
Sean Perry
2
@Keith - W przypadku Bash (w przeciwieństwie do Pythona w innych pytaniach) ... OpenBSD nie ma /bin/bash. Bash nie jest domyślnie instalowany. Jeśli chcesz, musisz pkg install bash. Po zainstalowaniu znajduje się pod adresem /usr/local/bin/bash. Na /bin/bashOpenBSD nie ma nic zainstalowanego . Szereg #!/bin/bashwoli popełni błąd i #!/usr/bin/env bashodniesie sukces.
jww

Odpowiedzi:

229

#!/usr/bin/envwyszukiwania PATHdla bash, a bashnie zawsze jest /bin, szczególnie w systemach innych niż Linux. Na przykład w moim systemie OpenBSD jest on włączony/usr/local/bin , ponieważ został zainstalowany jako opcjonalny pakiet.

Jeśli jesteś absolutnie pewien, że bashjest /bini zawsze będzie, umieszczenie go bezpośrednio w swoim shebang nie zaszkodzi - ale odradzam to, ponieważ skrypty i programy mają życie dłuższe niż początkowo sądzimy, że będą.

zigg
źródło
29
co z envlokalizacją? POSIX tego nie wymusza.
Julio Guerra
1
@JioioGuerra Podobnie jak posiadanie /usr/lib/sendmail(lub ostatnio /usr/sbin/sendmail) pliku binarnego dostępnego do przetwarzania poczty, leży to w najlepszym interesie systemu uniksowego, /usr/bin/envponieważ shevang env jest tak powszechną praktyką. Jest to de facto standardowy interfejs.
zigg
8
@zigg To jest tak UN * X ... <-: Mam na myśli, że w ich najlepszym interesie jest mieć standardową lokalizację env, ale jakoś nie mieć standardowej lokalizacji (która może być tylko miękkim linkiem) bash. Nie wspominając już o tym, dlaczego hashbang nie akceptuje tylko #!bashi używa PATH, zamiast robić dokładnie to samo z env. Chyba nie dość mylące dla początkujących.
ddekany
1
@JulioGuerra Według wikipedii masz rację: nie jest to „gwarantowane”. Zamiast tego wydaje się „bardziej prawdopodobne”. Wikipedia mówi This mostly works because the path /usr/bin/env is commonly used for the env utilitytutaj en.wikipedia.org/wiki/Shebang_(Unix) - Musimy zwierzyć się z envbycia tam „prawdopodobnie” we wszystkich systemach.
Xavi Montero
2
@XaviMontero: Użyłem systemu, w którym envbył /bin, a nie w /usr/bin(nie jestem pewien, który to prawdopodobnie SunOS 4). W dzisiejszych czasach jest bardzo prawdopodobne, /usr/bin/envże będzie dostępny, tylko ze względu na popularność #!/usr/bin/envhacka.
Keith Thompson
39

Standardowa lokalizacja bash to /bini podejrzewam, że tak jest we wszystkich systemach. Co jednak, jeśli nie spodoba ci się ta wersja bash? Na przykład chcę użyć wersji bash 4.2, ale wersja bash na moim komputerze Mac to 3.2.5.

Mógłbym spróbować ponownie zainstalować bash, /binale to może być zły pomysł. Jeśli zaktualizuję system operacyjny, zostanie on zastąpiony.

Mogę jednak zainstalować bash /usr/local/bin/bashi skonfigurować ŚCIEŻKĘ do:

PATH="/usr/local/bin:/bin:/usr/bin:$HOME/bin"

Teraz, jeśli sprecyzuję bash, nie dostanę starego, szorstkiego /bin/bash, ale nowszego, bardziej błyszczącego /usr/local/bin. Miły!

Tyle że moje skrypty powłoki mają ten !# /bin/bashshebang. Tak więc, kiedy uruchamiam skrypty powłoki, otrzymuję starą i kiepską wersję basha, która nawet nie ma tablic asocjacyjnych.

Użycie /usr/bin/env bashspowoduje użycie wersji bash z mojej ŚCIEŻKI. Jeśli skonfiguruję ŚCIEŻKĘ, to tak/usr/local/bin/bash została wykonana, to będzie bash, którego użyją moje skrypty.

Rzadko widuje się to podczas bash, ale jest znacznie bardziej powszechne w Perlu i Pythonie:

  • Niektóre wydania Uniksa / Linuksa, które koncentrują się na stabilności, są czasem daleko w tyle za wydaniem tych dwóch języków skryptowych. Niedawno Perl RHEL miał 5,8 - ośmioletnia wersja Perla! Jeśli ktoś chciał korzystać z bardziej nowoczesnych funkcji, musiałeś zainstalować własną wersję.
  • Programy takie jak Perlbrew i Pythonbrew pozwalają instalować wiele wersji tych języków. Zależą od skryptów, które manipulują ŚCIEŻKĄ, aby uzyskać żądaną wersję. Twarde kodowanie ścieżki oznacza, że ​​nie mogę uruchomić skryptu w trakcie przygotowywania .
  • Nie tak dawno temu (dobrze, dawno temu) Perl i Python nie były standardowymi pakietami zawartymi w większości systemów uniksowych. Oznaczało to, że nie wiesz, gdzie zostały zainstalowane te dwa programy. Czy to było pod /bin? /usr/bin? /opt/bin? Kto wie? Używanie #! /usr/bin/env perloznaczało, że nie musiałem wiedzieć.

A teraz dlaczego nie powinieneś używać #! /usr/bin/env bash

Kiedy ścieżka jest zakodowana na sztywno, muszę biec z tym tłumaczem. W ten sposób #! /bin/bashzmusza mnie do korzystania z zainstalowanego domyślną wersję bash. Ponieważ funkcje bash są bardzo stabilne (spróbuj uruchomić wersję 2.x skryptu Python pod Pythonem 3.x), jest bardzo mało prawdopodobne, że mój konkretny skrypt BASH nie będzie działał, a ponieważ mój skrypt bash jest prawdopodobnie używany przez ten system i inne systemy , używanie niestandardowej wersji bash może mieć niepożądane skutki. Jest bardzo prawdopodobne, że chcę się upewnić, że w moim skrypcie powłoki używana jest stabilna standardowa wersja bash. Dlatego prawdopodobnie chcę mocno zakodować ścieżkę w moim shebang.

David W.
źródło
5
Nie jest to prawdą: „Standardową lokalizacją bash jest / bin” (chyba że można przytoczyć standardowy dokument), być może bardziej dokładne jest to, że jest to „zwykła” lokalizacja w większości dystrybucji Linuksa i macos, ale nie jest to standardem w systemach uNIX w ogóle (i nie jest to szczególnie miejsce na większości * BSD).
tesch1
13

Przywoływanie bashto trochę przesada. Chyba że masz wiele bashplików binarnych, takich jak twój własny w ~ / bin, ale oznacza to również, że twój kod zależy od tego, czy $ PATH ma w sobie odpowiednie rzeczy.

Jest to przydatne przy takich rzeczach python. Istnieją skrypty opakowania i środowiska, które prowadzą do pythonużycia alternatywnych plików binarnych.

Ale nic nie jest stracone przez zastosowanie dokładnej ścieżki do pliku binarnego, o ile masz pewność, że jest to plik binarny, którego naprawdę potrzebujesz.

Sean Perry
źródło
Miejsce na python. Straciłem liczbę miejsc, które pythonmożna znaleźć 😀
zigg
2
Zgadzam się, że /usr/bin/envjest to bardziej przydatne dla Pythona, szczególnie jeśli używasz virtualenv.
Dennis
10

Istnieje wiele systemów, które nie zawierają Bash /bin, FreeBSD i OpenBSD, żeby wymienić tylko kilka. Jeśli twój skrypt ma być przenośny dla wielu różnych Uniksów, możesz użyć #!/usr/bin/env bashzamiast niego#!/bin/bash .

Zauważ, że nie dotyczy to sh; Bourne skryptów zgodnych I wyłącznie używać #!/bin/sh, ponieważ myślę, że prawie każdy ma Unix istnieje shw /bin.

James Ko
źródło
/binWidzę w Ubuntu 18.04 reż sh -> dash. Symbolicznie powiązany z dash, ujawniając naturę Debiana Ubuntu. Uruchomić te trzy ciągi poleceń, aby uświadomić sobie, że wszystko sprowadza się do indywidualnych preferencji: which bashwtedy which shpotem which dash.
noobninja
0

Wolałbym zawinąć główny program w skrypt jak poniżej, aby sprawdzić wszystkie bashdostępne w systemie. Lepiej mieć większą kontrolę nad wersją, której używa.

#! /usr/bin/env bash

# This script just chooses the appropriate bash
# installed in system and executes testcode.main

readonly DESIRED_VERSION="5"

declare all_bash_installed_on_this_system
declare bash

if [ "${BASH_VERSINFO}" -ne "${DESIRED_VERSION}" ]
then
    found=0

    all_bash_installed_on_this_system="$(\
        awk -F'/' '$NF == "bash"{print}' "/etc/shells"\
        )"

    for bash in $all_bash_installed_on_this_system
    do
        versinfo="$( $bash -c 'echo ${BASH_VERSINFO}' )"
        [ "${versinfo}" -eq "${DESIRED_VERSION}" ] && { found=1 ; break;}
    done
    if [ "${found}" -ne 1 ]
    then
        echo "${DESIRED_VERSION} not available"
        exit 1
    fi
fi

$bash main_program "$@"
Mihir
źródło
0
 #!/usr/bin/env bash

jest zdecydowanie lepszy, ponieważ znajduje ścieżkę wykonywalną bash ze zmiennej środowiskowej systemu.

Przejdź do powłoki Linux i wpisz

env

Wydrukuje wszystkie zmienne środowiskowe.

Przejdź do skryptu powłoki i wpisz

echo $BASH

Spowoduje to wydrukowanie ścieżki bash (zgodnie z listą zmiennych środowiskowych), której należy użyć do zbudowania poprawnej ścieżki shebang w skrypcie.

kta
źródło