Czy podwójny nawias kwadratowy [[]] jest lepszy niż pojedynczy nawias kwadratowy [] w Bash?

573

Współpracownik stwierdził ostatnio w przeglądzie kodu, że [[ ]]konstrukt ma być preferowany [ ]w podobnych konstrukcjach

if [ "`id -nu`" = "$someuser" ] ; then 
     echo "I love you madly, $someuser"
fi

Nie mógł podać uzasadnienia. Czy jest jeden

Leonard
źródło
19
Bądź elastyczny, czasem pozwól sobie na wysłuchanie porady bez konieczności jej głębokiego wyjaśnienia :) Co do [[tego, kod jest dobry i przejrzysty, ale pamiętaj o tym dniu, kiedy przeniesiesz swoje skrypty do systemu z domyślną powłoką, która nie jest, bashlub ksh, itp. [jest brzydszy, nieporęczny, ale działa jak AK-47w każdej sytuacji.
wieża
178
@rook Możesz posłuchać porady bez głębokiego wyjaśnienia, jasne. Ale kiedy poprosisz o wyjaśnienie, ale go nie rozumiesz, zwykle jest to czerwona flaga. „Ufaj, ale weryfikuj” i tak dalej.
Josip Rodin
6
Zobacz także: Jaka jest różnica między operatorami Bash [[vs [vs (vs ((? W Unix i Linux SE.
codeforester
1
@rook, innymi słowy „rób to, co ci kazano i nie zadawaj pytań”
glif
@rook To nie miało sensu.
Rakib

Odpowiedzi:

606

[[ma mniej niespodzianek i jest ogólnie bezpieczniejszy w użyciu. Ale nie jest przenośny - POSIX nie określa, co robi i tylko niektóre powłoki go obsługują (oprócz bash, słyszałem, że ksh też to obsługuje). Na przykład możesz to zrobić

[[ -e $b ]]

aby sprawdzić, czy plik istnieje. Ale z [, musisz zacytować $b, ponieważ dzieli argument i rozwija rzeczy takie jak "a*"(gdzie [[bierze dosłownie). Ma to również związek z tym, jak [może być programem zewnętrznym i odbiera swój argument normalnie jak każdy inny program (chociaż może to być także wbudowany program, ale nadal nie ma specjalnej obsługi).

[[ma także kilka innych fajnych funkcji, takich jak dopasowanie wyrażeń regularnych =~wraz z operatorami, takimi jak są znane w językach podobnych do C. Oto dobra strona na ten temat: Jaka jest różnica między testem, [a[[ ? i testy bashowe

Johannes Schaub - litb
źródło
21
Biorąc pod uwagę, że bash jest obecnie wszędzie, myślę, że jest cholernie przenośny. Jedynym częstym wyjątkiem jest dla mnie na platformach busybox - ale prawdopodobnie i tak chciałbyś podjąć specjalny wysiłek, biorąc pod uwagę sprzęt, na którym działa.
pistolety
14
@guns: Rzeczywiście. Sugerowałbym, aby twoje drugie zdanie obaliło twoje pierwsze; jeśli weźmiesz pod uwagę rozwój oprogramowania jako całości, „bash jest wszędzie”, a „wyjątkiem są platformy busybox” są całkowicie niekompatybilne. busybox jest szeroko rozpowszechniony w programowaniu wbudowanym.
Wyścigi lekkości na orbicie
11
@guns: Nawet jeśli bash jest zainstalowany na moim urządzeniu, może nie uruchamiać skryptu (mogę mieć / bin / sh symlinkowany do jakiegoś wariantu dash, tak jak to jest standardowo w FreeBSD i Ubuntu). W Busybox jest też dodatkowa dziwność: przy obecnych standardowych opcjach kompilacji analizuje, [[ ]]ale interpretuje to jako takie samo jak [ ].
dubiousjim
59
@dubiousjim: Jeśli używasz konstrukcji typu bash (takich jak ten), powinieneś mieć #! / bin / bash, a nie #! / bin / sh.
Nick Matteo
16
Dlatego używam skryptów, #!/bin/shale używam ich, #!/bin/bashgdy tylko polegam na pewnej funkcji BASH, co oznacza, że ​​nie jest to już przenośna powłoka Bourne'a.
Anthony
151

Różnice w zachowaniu

Niektóre różnice w Bash 4.3.11:

  • Rozszerzenie POSIX vs Bash:

  • regularne dowodzenie kontra magia

    • [ to zwykłe polecenie o dziwnej nazwie.

      ] jest tylko argumentem [ który uniemożliwia użycie dalszych argumentów.

      Ubuntu 16.04 faktycznie ma dla niego plik wykonywalny /usr/bin/[podany przez coreutils , ale wersja wbudowana bash ma pierwszeństwo.

      Nic nie zmienia w sposobie, w jaki Bash analizuje polecenie.

      W szczególności <jest przekierowywaniem &&i ||łączeniem wielu poleceń, ( )generuje podpowłoki, chyba że jest to poprzedzone znakiem ucieczki \, a rozwijanie słów odbywa się jak zwykle.

    • [[ X ]]to pojedynczy konstrukt, który sprawia, że Xanalizuje się go magicznie. <, &&, ||I() są traktowane specjalnie, oraz zasady podziału na słowa są różne.

      Istnieją również dalsze różnice, takie jak =i=~ .

      W języku bashese: [jest wbudowanym poleceniem i [[jest słowem kluczowym: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && i ||

    • [[ a = a && b = b ]]: prawda, logika i
    • [ a = a && b = b ]: błąd składniowy, &&analizowany jako separator poleceń ANDcmd1 && cmd2
    • [ a = a -a b = b ]: równoważne, ale przestarzałe przez POSIX³
    • [ a = a ] && [ b = b ]: POSIX i niezawodny odpowiednik
  • (

    • [[ (a = a || a = b) && a = b ]]: fałszywe
    • [ ( a = a ) ]: błąd składni, () jest interpretowany jako podpowłoka
    • [ \( a = a -o a = b \) -a a = b ]: równoważne, ale () jest przestarzałe przez POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] Odpowiednik POSIX⁵
  • dzielenie słów i generowanie nazw plików po rozwinięciach (split + glob)

    • x='a b'; [[ $x = 'a b' ]]: prawda, cytaty nie są potrzebne
    • x='a b'; [ $x = 'a b' ]: błąd składniowy, rozwija się do [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: błąd składni, jeśli w bieżącym katalogu jest więcej niż jeden plik.
    • x='a b'; [ "$x" = 'a b' ]: Odpowiednik POSIX
  • =

    • [[ ab = a? ]]: true, ponieważ dopasowuje wzorce (* ? [ są magiczne). Nie jest rozwijany globalnie do plików w bieżącym katalogu.
    • [ ab = a? ]: a? glob rozszerza się. Może być więc prawdą lub fałszem w zależności od plików w bieżącym katalogu.
    • [ ab = a\? ]: false, nie globalna ekspansja
    • =i ==są takie same w obu [i [[, ale ==jest rozszerzeniem Bash.
    • case ab in (a?) echo match; esac: Odpowiednik POSIX
    • [[ ab =~ 'ab?' ]]: false⁴, traci magię z ''
    • [[ ab? =~ 'ab?' ]]: prawdziwe
  • =~

    • [[ ab =~ ab? ]]: prawda, rozszerzone dopasowanie wyrażeń regularnych POSIX , ?nie rozwija się globalnie
    • [ a =~ a ]: błąd składni. Brak odpowiednika bash.
    • printf 'ab\n' | grep -Eq 'ab?': Odpowiednik POSIX (tylko dane jednowierszowe)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': Odpowiednik POSIX.

Zalecenie : zawsze używaj [].

Istnieją odpowiedniki POSIX dla każdego [[ ]]konstruktu, który widziałem.

Jeśli korzystasz z [[ ]]ciebie:

  • stracić przenośność
  • zmusić czytelnika do poznania zawiłości innego rozszerzenia bash. [to zwykłe polecenie o dziwnej nazwie, nie wymaga specjalnej semantyki.

¹ Inspirowany równoważnym [[...]]konstruktem w skorupie Korna

², ale zawodzi w przypadku niektórych wartości alub b(jak +lub index) i dokonuje porównania numerycznego, jeśli ai bwygląda jak liczba całkowita dziesiętna. expr "x$a" '<' "x$b"działa w obu przypadkach.

³, a także zawodzi w przypadku niektórych wartości alub bpodobnych !lub (.

⁴ w wersji bash 3.2 i nowszych oraz pod warunkiem, że zgodność z wersją bash 3.1 nie jest włączona (jak w przypadku BASH_COMPAT=3.1)

⁵ że ugrupowanie (w tym przypadku z {...;}grupy polecenia, a (...)co byłoby niepotrzebne powłoki w tle), nie jest konieczne, jak ||i &&powłoki operatorów (w przeciwieństwie do ||i && [[...]]operatorów lub -o/ -a [operatorów) mają tę samą siłę. Więc [ a = a ] || [ a = b ] && [ a = b ]byłoby równoważne.

Ciro Santilli
źródło
4
Dobre wyjaśnienia. Używam [z tych samych powodów.
Gordon
2
W języku bashese :) ha. Ładne wyjaśnienie różnic i powodów, dla których warto pozostać przy POSIX.
Jonathan Komar
56

[[ ]]ma więcej funkcji - proponuję zajrzeć do Advanced Bash Scripting Guide, aby uzyskać więcej informacji, w szczególności sekcję o rozszerzonym poleceniu testu w rozdziale 7. Testy .

Nawiasem mówiąc, jak zauważa przewodnik, [[ ]]został wprowadzony w ksh88 (wersja powłoki Korn z 1988 roku).

Nils von Barth
źródło
5
Jest to dalekie od bashizmu, zostało po raz pierwszy wprowadzone w powłoce Korna.
Henk Langeveld,
6
@Thomas, ABS jest w wielu kręgach uważany za bardzo kiepski odnośnik; chociaż ma wiele dokładnych informacji, stara się nie przykładać dużej wagi do tego, by nie pokazywać złych praktyk w swoich przykładach, i spędził dużą część życia bez opieki.
Charles Duffy
@CharlesDuffy dzięki za komentarz, czy możesz podać dobrą alternatywę. Nie jestem ekspertem od skryptów powłoki, szukam przewodnika, z którym mogę skonsultować się co do pisania co pół roku.
Thomas
9
@Thomas, Wooledge BashFAQ i powiązane wiki są tym, czego używam; są aktywnie utrzymywane przez mieszkańców kanału Freenode #bash (którzy czasami kłujący, bardzo dbają o poprawność). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 to strona bezpośrednio związana z tym pytaniem.
Charles Duffy
13

Z którego komparatora, testu, nawiasów lub nawiasów podwójnych jest najszybszy? ( http://bashcurescancer.com )

Podwójny nawias kwadratowy jest „poleceniem złożonym”, w którym jako test i pojedynczy nawias są wbudowane w powłokę (w rzeczywistości są to te same polecenia). Zatem pojedynczy nawias i podwójny nawias wykonują inny kod.

Test i pojedynczy wspornik są najbardziej przenośne, ponieważ istnieją jako osobne i zewnętrzne polecenia. Jeśli jednak używasz dowolnej zdalnie nowoczesnej wersji BASH, obsługiwany jest podwójny nawias.

f3lix
źródło
7
O co chodzi z obsesją najszybszych skryptów powłoki? Chcę, aby był jak najbardziej przenośny i nie martwiłem się, że poprawa [[może przynieść. Ale potem jestem oldschoolowym pierdnięciem :-)
Jens
1
Myślę, że wiele powłok sprawdza się we wbudowanych wersjach [i testchociaż istnieją również wersje zewnętrzne.
dubiousjim
4
@Jens ogólnie Zgadzam się: głównym celem skryptów jest (była?) Przenośność (w przeciwnym razie kodujemy i kompilujemy, a nie skrypt) ... dwa wyjątki, o których mogę myśleć to: (1) uzupełnianie tabulatorów (gdzie skrypty ukończenia mogą być naprawdę długie z dużą ilością logiki warunkowej); i (2) superpytania ( PS1=...crazy stuff...i / lub $PROMPT_COMMAND); dla tych nie chcę żadnego zauważalnego opóźnienia w wykonaniu skryptu.
Michael
1
Część obsesji na punkcie najszybszego to po prostu styl. Wszystkie pozostałe są jednakowe, dlaczego nie włączyć bardziej wydajnej konstrukcji kodu do domyślnego stylu, zwłaszcza jeśli ta konstrukcja zapewnia również większą czytelność? Jeśli chodzi o przenośność, wiele zadań, do których nadaje się bash, z natury nie jest przenośnych. Na przykład muszę uruchomić, apt-get updatejeśli minęło więcej niż X godzin od ostatniego uruchomienia. To wielka ulga, kiedy można pominąć przenośność na zbyt długiej liście ograniczeń kodu.
Ron Burk
5

Jeśli podoba Ci się przewodnik po stylu Google :

Testuj [i[[

[[ ... ]]redukuje błędy, ponieważ nie dochodzi do rozwijania nazw ścieżek ani dzielenia słów między [[i ]], i[[ ... ]] umożliwia dopasowanie wyrażeń regularnych tam, gdzie [ ... ]nie.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi
crizCraig
źródło
1
Google napisał „ nie ma rozszerzenia nazwy ścieżki ... ma miejsce ”, ale [[ -d ~ ]]zwraca true (co oznacza, że ~został rozwinięty /home/user). Myślę, że Google powinien być bardziej precyzyjny w pisaniu.
JamesThomasMoon1979
2

Typowa sytuacja, w której nie można użyć, [[to skrypt auto.ooling config.ac, nawiasy mają specjalne i inne znaczenie, więc będziesz musiał użyć testzamiast [lub [[- Zauważ, że test i [to ten sam program.

Vicente Bolea
źródło
2
Biorąc pod uwagę, że narzędzia automatyczne nie są powłoką POSIX, dlaczego miałbyś oczekiwać, [że zostanie zdefiniowany jako funkcja powłoki POSIX?
Gordon
Ponieważ skrypt autoconf wygląda jak skrypt powłoki i tworzy skrypt powłoki, a większość poleceń powłoki działa w nim.
vy32
-1

[[]] podwójne nawiasy klamrowe są nieobsługiwane w niektórych wersjach SunOS i całkowicie nieobsługiwane deklaracje funkcji wewnętrznych przez: GNU bash, wersja 2.02.0 (1) -release (sparc-sun-solaris2.6)

padlinożerca
źródło
1
bardzo prawdziwe i wcale nieistotne. Należy wziąć pod uwagę przenośność bash w starszych wersjach. Ludzie mówią, że „bash jest wszechobecny i przenośny, z wyjątkiem może (wstaw tutaj ezoteryczny system operacyjny) ” - ale z mojego doświadczenia wynika, że ​​solaris jest jedną z tych platform, na których należy zwrócić szczególną uwagę na przenośność: nie tylko rozważać starsze wersje bash na nowszy system operacyjny, problemy / błędy w tablicach, funkcjach itp .; ale nawet narzędzia (używane w skryptach), takie jak tr, sed, awk, tar, mają osobliwości i osobliwości na solaris, które trzeba obejść.
Michael
2
masz rację ... tyle narzędzi innych niż POSIX na Solarisie, spójrz na dane wyjściowe i argumenty "df" ... Wstydź się na Sun. Mam nadzieję, że znika stopniowo (z wyjątkiem Kanady).
padlinożerca
7
Solaris 2.6 poważnie? Został wydany w 1997 roku, a zakończył wsparcie w 2006 roku. Myślę, że jeśli nadal go używasz, masz inne problemy! Nawiasem mówiąc, używał Bash v2.02, który wprowadził podwójne nawiasy, więc powinien działać nawet na czymś tak starym. Solaris 10 z 2005 roku używał Bash 3.2.51, a Solaris 11 z 2011 roku używa Bash 4.1.11.
peterh
1
Ponowna przenośność skryptu. W systemach Linux jest na ogół tylko edycja każdego narzędzia i jest to edycja GNU. W systemie Solaris zazwyczaj masz wybór między wersją natywną dla systemu Solaris a wersją GNU (powiedzmy, że Solaris tar vs GNU tar). Jeśli zależysz od rozszerzeń GNU, musisz podać to w skrypcie, aby było przenośne. W Solarisie robisz to przez prefiks „g”, np. „Ggrep”, jeśli chcesz GNU grep.
peterh
-2

W skrócie, [[jest lepszy, ponieważ nie rozwidla innego procesu. Żaden nawias ani pojedynczy nawias nie jest wolniejszy niż nawias podwójny, ponieważ wymaga innego procesu.

unix4linux
źródło
8
Test i [to nazwy tego samego wbudowanego polecenia w bash. Spróbuj użyć, type [aby to zobaczyć.
AB
1
@alberge, to prawda, ale [[w odróżnieniu od tego [jest składnią interpretowaną przez interpretera wiersza poleceń bash. W bash spróbuj pisać type [[. unix4linux ma rację, że chociaż klasyczne [testy powłoki Bourne'a wykreślają nowy proces w celu ustalenia wartości prawdy, [[składnia (zapożyczona z ksh przez bash, zsh itp.) nie.
Tim Gilbert
2
@ Tim, nie jestem pewien, o której powłoce Bourne mówisz, ale [jest ona wbudowana zarówno w Bash, jak i Dash ( /bin/shwe wszystkich dystrybucjach Linuksa pochodzących z Debiana).
AB
1
Och, rozumiem, co masz na myśli, to prawda. Myślałem o czymś takim jak, powiedzmy, / bin / sh na starszych systemach Solaris lub HP / UX, ale oczywiście, jeśli chcesz być kompatybilny z tymi, których nie będziesz używać [[albo.
Tim Gilbert
1
@alberge Bourne shell nie jest Bash (alias Bourne Again SHell ).
kiamlaluno