Jak mogę zapobiec nieobsługiwanym opcjom „shopt” powodującym błędy w moim .bashrc?

9

Pracuję w stosunkowo heterogenicznym środowisku, w którym mogę uruchamiać różne wersje Bash na różnych węzłach HPC, maszynach wirtualnych lub mojej osobistej stacji roboczej. Ponieważ umieściłem swoje skrypty logowania w repozytorium Git, chciałbym używać tego samego (ish) na .bashrccałym forum, bez zbyt wielu „jeśli ten host, to ...” - niechlujny typ.

Podoba mi się domyślne zachowanie Bash ≤ 4.1, które rozwija cd $SOMEPATHsię cd /the/actual/pathpo naciśnięciu Tabklawisza. W wersji Bash 4.2 i nowszych konieczne byłoby shopt -s direxpandponowne włączenie tego zachowania, które stało się dostępne dopiero w wersji 4.2.29 . To tylko jeden przykład; inna, prawdopodobnie powiązana shoptopcja complete_fullquote(chociaż nie wiem dokładnie, co to robi) mogła również zmienić domyślne zachowanie w wersji 4.2.

Jednak direxpandnie jest rozpoznawane przez wcześniejsze wersje Basha, a jeśli próbuję shopt -s direxpandw moim .bashrc, który powoduje wyświetlenie komunikatu o błędzie, który jest drukowany na konsoli przy każdym logowaniu do węzła ze starszą Bash:

-bash: shopt: direxpand: invalid shell option name

Chciałbym owinąć warunek, shop -s direxpandaby włączyć tę opcję w wersji Bash> 4.1 w niezawodny sposób, bez przecierania starszych wersji Bash ( tj. Nie tylko przekierowując wyjście błędu /dev/null).

TheDudeAbides
źródło
Jak moja odpowiedź nie pomogła?
Luciano Andress Martini
@LucianoAndressMartini Tak, i to jest rozwiązanie, z którego ostatecznie skorzystałem .bashrc. Wciąż potrzebowałem zapisu, w jaki sposób użyć $BASH_VERSINFOdo przesłuchania głównej / podrzędnej wersji działającej powłoki, dla mojej własnej edycji, dlatego skończyłem publikować własną odpowiedź. :)
TheDudeAbides
Spójrz w mojej odpowiedzi, mam coś na temat porównywania wersji programów ze skryptem powłoki.
Luciano Andress Martini

Odpowiedzi:

14

Sprawdź, czy direxpandjest obecny na wyjściu shopti włącz go, jeśli:

shopt | grep -q '^direxpand\b' && shopt -s direxpand
Luciano Andress Martini
źródło
4
Lepiej grep -q '^direxpand\b'spraw, aby w przypadku, gdy w przyszłej wersji lub rozwidleniu bash będzie dostępna opcja, która zawiera to podciąg i usuwa direxpand. Jest mało prawdopodobne w tym konkretnym przypadku, ale solidność nie kosztuje wiele.
Gilles „SO- przestań być zły”
Dzięki Luciano. Chciałbym celu odpowiedzieć na moje własne pytanie, ale I "ll akceptują swoją odpowiedź po moje edycje przejść przeglądu Może można je zatwierdzić siebie.?
TheDudeAbides
4
Bash pozwala zapytać o konkretne opcje powłoki, więc można użyć [ -z "$(shopt -po direxpand 2>&-)" ] || shopt -s direxpand. Nigdy więcej problemów z wyrażeniami regularnymi! :-)
David Foerster,
@DavidFoerster Odwróciłbym logikę: [ -n "blah" ] && shopt blahsposób, w jaki to wyrażasz, mówisz „jeśli direxpand nie jest obsługiwany, to nie rób tego”.
Bogaty
1
@Rich: Większość moich skryptów powłoki znajduje się set -ena górze, więc zwykle używam logiki skrótowej w ten sposób.
David Foerster,
16

Nie widzę, co jest nie tak z błędami przekierowywania /dev/null. Jeśli chcesz, aby Twój kod był niezawodny set -e, użyj wspólnego idiomu … || true:

shopt -s direxpand 2>/dev/null || true

Jeśli chcesz uruchomić kod zastępczy, jeśli opcja nie istnieje, użyj statusu zwracanego shopt:

if shopt -s direxpand 2>/dev/null; then
   # the direxpand option exists
else
   # the direxpand option does not exist
fi

Ale jeśli naprawdę nie lubisz przekierowywać błędu, możesz użyć mechanizmu uzupełniania do przeprowadzenia introspekcji. Zakłada się, że nie masz przestarzałych maszyn z bash ≤ 2.03, które nie miałyby programowalnego zakończenia.

shopt_exists () {
  compgen -A shopt -X \!"$1" "$1" >/dev/null
}
if shopt_exists direxpand; then
  shopt -s direxpand
fi

Ta metoda pozwala uniknąć rozwidlenia, które jest powolne w niektórych środowiskach, takich jak Cygwin. Tak też jest proste 2>/dev/null, nie sądzę, że można to pobić pod względem wydajności.

Gilles „SO- przestań być zły”
źródło
To nie byłby mój mózg, ale podoba mi się ta compgenpropozycja. To są rzeczy na poziomie uniwerek! Unikanie przekierowania do /dev/nulljest tylko osobistą preferencją. Chciałbym prosić o pozwolenie zamiast wybaczenia, jeśli to ma sens? :)
TheDudeAbides
+1 za całkowicie nieoczekiwane szkolenie w programowalnym zakończeniu Bash, co zmusiło mnie do przejścia do instrukcji, aby odczytać, co w compgen -A shopt -X ...ogóle oznacza.
TheDudeAbides
4
@TheDudeAbides Czytam o używaniu w compgenten sposób w systemach Unix i Linux , nie wiem, kto pierwszy to zaproponował. (Przestałem używać bash jako mojej głównej powłoki, zanim zakończyło się programowanie). W programowaniu zwykle złym pomysłem jest poprosić o pozwolenie, ponieważ istnieje ryzyko, że sprawdzenie uprawnień nie będzie pasować do tego, co faktycznie robisz, albo z powodu kodowania błąd (gdy nie do końca sprawdzasz, co myślisz, że sprawdzasz) lub ponieważ to, co sprawdziłeś, zmieniło się przed użyciem .
Gilles „SO- przestań być zły”
5

Gdy wiesz na pewno, że konkretna shoptopcja jest dostępna w określonym wydaniu Bash w wersji głównej / drugorzędnej / łaty, możesz sprawdzić $BASH_VERSIONzmienną lub elementy $BASH_VERSINFO[]tablicy, aby włączyć ją warunkowo.

Oto test dla wersji Bash 4.2.29 lub nowszej, w której direxpand po raz pierwszy wprowadzono serię 4.2:

if [[ $BASH_VERSION == 4.2.* && ${BASH_VERSINFO[2]} -ge 29 ]] ||
   [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -ge 3 ]] ||
   [[ ${BASH_VERSINFO[0]} -ge 5 ]]; then
    shopt -s direxpand
fi

Edycja: Żeby było jasne, jest to absurdalnie przeprojektowane rozwiązanie, aby po prostu zignorować komunikat o błędzie pochodzący z twoich skryptów logowania, ale ja chciałem to udokumentować niezależnie od mojej własnej edycji.

Zwróć uwagę na nawiasy klamrowe , które wymagane, oraz użycie i , które wykonują liczby całkowite zamiast (zależne od ustawień regionalnych) leksykalne porównania. Jeśli nie jest cytowany , RHS operatora traktowane są jako wzorce „extglob” w Bash / warunkowe, jak zauważono tutaj , co sprawia, że ​​bardziej estetyczne porównanie „zaczyna się od” niż regex, IMO.${BASH_VERSINFO[index]}-eq-gt==[[]]

$BASH_VERSINFOTablica zawiera wszystkie informacje chcesz widzieć na wyjściu bash --version:

bash --version | head -1
# result:
# GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

declare -p BASH_VERSINFO
# result:
# declare -ar BASH_VERSINFO='([0]="4" [1]="3" [2]="48" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")'

Jeśli z dokumentacji nie wynika jasno, shoptw jakich wersjach Bash obsługiwano lub zmieniano ich zachowanie, metoda zaproponowana przez Luciano jest w porządku:

# note the '-q' so that the matched pattern isn't actually printed
shopt | grep -q direxpand && shopt -s direxpand

... podobnie jak rozwiązanie zaproponowane przez Gillesa polegające na ignorowaniu błędu ( shopt -s direxpand 2>/dev/null) i sprawdzeniu, $?czy jest to absolutnie konieczne.

Odniesienia: 1 , 2 , 3
Powiązana lektura: Zestaw i Shopt - dlaczego dwa?

TheDudeAbides
źródło
Może również być w stanie użyć czegoś podobnego if [[ $BASH_VERSION > 4.3 ]];(co odpowiada 4.3.0, 5.0etc., ale też 4.3.0-alphanie wiem, czy późniejszych faktów sprawach.).
ilkkachu
Cześć @ilkkachu. Dziękujemy za edycję obejmującą Bash v5.x. Jednak direxpandopcja ta jest rzeczywiście dostępna dla Bash 4.2; Sprawdziłem to za pomocą obrazu Docker w wersji 4.2.53, uruchamiając docker run --rm bash:4.2 bash -c shopt | grep direxpand(i dla pewności, że rzeczywiście nie jest dostępny w wersji 4.1.17 przez uruchomienie docker run --rm bash:4.1 bash -c shopt | grep direxpand).
TheDudeAbides
Ach, ok, przetestowałem 4.2.0i natknąłem się na fakt, że tam nie działało. Dziennik zmian wspomina również, że został dodany bash-4.3-alpha. Podejrzewam, że wtedy trzeba by ${BASH_VERSINFO[2]}to dokładnie sprawdzić , ale nie wiem, która wersja wydania go dodała ...
ilkkachu
Myślę, że w zasadzie udowodniliśmy punkt, o którym Gilles mówił powyżej; że w rzeczywistości lepiej po prostu spróbować włączyć opcję powłoki, a następnie zająć się błędem (lub pominąć go), jeśli nie jest obsługiwany.
TheDudeAbides