Jaka jest różnica między podwójnymi i pojedynczymi nawiasami kwadratowymi w bash?

426

Zastanawiałem się, jaka dokładnie jest różnica

[[ $STRING != foo ]]

i

[ $STRING != foo ]

jest poza tym, że ten ostatni jest zgodny z POSIX-em, znajduje się w sh, a ten pierwszy jest rozszerzeniem znalezionym w bash.

0x89
źródło
1
W przypadku, gdy zastanawiasz się również nad brakiem nawiasów, np. W kontekście ifinstrukcji, zobacz mywiki.wooledge.org/BashPitfalls#if_.5Bgrep_foo_myfile.5D
Kev
także z dokumentacji Ubuntu: wiki.ubuntu.com/…
radistao

Odpowiedzi:

309

Istnieje kilka różnic. Moim zdaniem kilka najważniejszych to:

  1. [jest wbudowany w Bash i wiele innych nowoczesnych powłok. Wbudowane [jest podobne do testdodatkowego wymogu zamknięcia ]. W builtins [i testnaśladować funkcjonalność /bin/[i /bin/testwraz z ich ograniczeniami, tak aby skrypty byłoby wstecznie kompatybilne. Oryginalne pliki wykonywalne nadal istnieją głównie dla zgodności z POSIX i kompatybilności wstecznej. Uruchomienie polecenia type [w Bash oznacza, że [domyślnie jest interpretowane jako wbudowane. (Uwaga: which [szuka tylko plików wykonywalnych na ścieżce i jest równoważne type -p [)
  2. [[nie jest tak kompatybilny, niekoniecznie będzie działał z dowolnymi /bin/shpunktami. Podobnie [[jest z bardziej nowoczesną opcją Bash / Zsh / Ksh.
  3. Ponieważ [[jest wbudowany w powłokę i nie ma starszych wymagań, nie musisz się martwić dzieleniem słów na podstawie zmiennej IFS , aby zepsuć zmienne, które mają łańcuch znaków ze spacjami. Dlatego tak naprawdę nie trzeba umieszczać zmiennej w podwójnych cudzysłowach.

W przeważającej części reszta to tylko ładniejsza składnia. Aby zobaczyć więcej różnic, polecam ten link do odpowiedzi na najczęściej zadawane pytania: Jaka jest różnica między testem, [i [[? . W rzeczywistości, jeśli poważnie podchodzisz do skryptów bash, polecam przeczytanie całej wiki , w tym FAQ, pułapek i przewodnika. Sekcja testowa z sekcji przewodnika wyjaśnia również te różnice i dlaczego autor (autorzy) uważają, że [[jest lepszym wyborem, jeśli nie musisz się martwić, że jesteś tak przenośny. Główne powody to:

  1. Nie musisz się martwić o cytowanie lewej strony testu, aby faktycznie został odczytany jako zmienna.
  2. Nie musisz uciekać mniej niż i więcej niż < >z odwrotnymi ukośnikami, aby nie zostały ocenione jako przekierowanie wejściowe, co może naprawdę popsuć niektóre rzeczy przez zastąpienie plików. To znów wraca do [[bycia wbudowanym. Jeśli [(test) jest programem zewnętrznym, powłoka musiałaby zrobić wyjątek w swojej ocenie <i >tylko wtedy, gdy /bin/testzostanie wywołana, co tak naprawdę nie miałoby sensu.
Kyle Brandt
źródło
5
Dzięki, szukałem linku do bash FAQ (nie wiedziałem o tej stronie, dzięki).
0x89,
2
Zredagowałem twój post z tymi informacjami, ale [i test są wykonywane jako wbudowane. Wbudowane zostały zaprojektowane w celu zastąpienia / bin / [i / bin / test, ale musiały również odtworzyć ograniczenia plików binarnych. Polecenie „type [” sprawdza, czy wbudowane jest używane. ”, który [wyszukuje tylko pliki wykonywalne na PATH i jest równoważny z„ type -P [”
klynch
133

W skrócie:

[jest bash Wbudowany

[[]] to słowa kluczowe bash

Słowa kluczowe: słowa kluczowe są podobne do wbudowanych, ale główna różnica polega na tym, że mają do nich zastosowanie specjalne reguły analizy. Na przykład [to wbudowane bash, a [[to słowo kluczowe bash. Oba są używane do testowania rzeczy, ale ponieważ [[jest słowem kluczowym, a nie wbudowanym, korzysta z kilku specjalnych reguł analizy, które znacznie ułatwiają:

  $ [ a < b ]
 -bash: b: No such file or directory
  $ [[ a < b ]]

Pierwszy przykład zwraca błąd, ponieważ bash próbuje przekierować plik b do polecenia [a]. Drugi przykład faktycznie robi to, czego się spodziewasz. Znak <nie ma już specjalnego znaczenia dla operatora przekierowywania plików.

Źródło: http://mywiki.wooledge.org/BashGuide/CommandsAndArguments

abhiomkar
źródło
3
[jest poleceniem powłoki POSIX; nie musi być wbudowany. ]jest tylko argumentem, którego szuka ta komenda, aby składnia była zrównoważona. Polecenie jest synonimem, z testwyjątkiem tego, testże nie szuka zamknięcia ].
Kaz.
81

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 sposobu, 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: https://askubuntu.com/questions/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ładniowy, ()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 5
  • 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ładniowy, jeśli w bieżącym katalogu jest więcej niż jeden plik.
    • x='a b'; [ "$x" = 'a b' ]: Odpowiednik POSIX
  • =

    • [[ ab = a? ]]: prawda, ponieważ dopasowuje wzory ( * ? [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 4 , traci magię z''
    • [[ ab? =~ 'ab?' ]]: prawdziwe
  • =~

    • [[ ab =~ ab? ]]: true, 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 wykorzystasz [[ ]]:

  • 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 (.

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

5 chociaż grupowanie (tutaj z {...;}grupą komend zamiast (...)której uruchamiałaby niepotrzebną podpowłokę) nie jest konieczne, ponieważ operatory ||i &&powłoki (w przeciwieństwie do operatorów ||i && [[...]]lub operatorów -o/ -a [) mają równy priorytet. Więc [ a = a ] || [ a = b ] && [ a = b ]byłoby równoważne.

Ciro Santilli
źródło
Jak korzystać z printf 'ab' | grep -Eq 'ab?'wewnątrz if [ … ]?
meeDamian
1
@meeDamian if ( printf 'ab' | grep -Eq 'a' ); then echo 'a'; fi. []to polecenie jak grep. To polecenie ()może nie być potrzebne w tym poleceniu. Nie jestem pewien: dodałem je z powodu |, zależy od tego, jak Bash analizuje różne rzeczy. Jeśli nie było |, jestem pewien, że możesz po prostu pisać if cmd arg arg; then.
Ciro Santilli 25 改造 中心 法轮功 六四 事件
1
@meeDamian tak, nie ma takiej potrzeby (): stackoverflow.com/questions/8965509/...
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
1
Ładna lista! Zobacz także: wiki.ubuntu.com/…
radistao
5

Pojedynczy nawias, tzn. []Jest zgodny z powłoką POSIX, aby zawrzeć wyrażenie warunkowe.

Podwójny nawias, tzn. [[]]Jest ulepszoną (lub rozszerzoną) wersją standardowej wersji POSIX, obsługiwaną przez bash i inne powłoki (zsh, ksh).

W bash, dla porównania numerycznym używamy eq, ne, lti gt, z podwójnych nawiasach dla porównania możemy wykorzystać ==, !=, <,i >dosłownie.

  • [jest synonimem polecenia testowego. Nawet jeśli jest wbudowany w powłokę, tworzy nowy proces.
  • [[ to nowa, ulepszona wersja, która jest słowem kluczowym, a nie programem.

na przykład:

[ var1 lt var2] #works
[ var1 < var2] #error: var2 No such file or directory 
[ var1 \< var2] #works with escape
[[ var1 < var2]] #works
Premraj
źródło
4

W oparciu o szybki odczyt odpowiednich sekcji strony podręcznika podstawowa różnica wydaje się polegać na tym, że operatory ==i !=pasują do wzorca, a nie do literału, a także, że istnieje =~operator porównania wyrażeń regularnych.

womble
źródło