Nieoczekiwane zachowanie z echo [[: digit:]]

10

Chciałbym zapytać:

Dlaczego jest echo {1,2,3}rozwinięty do 1 2 3, co jest oczekiwanym zachowaniem, podczas gdy echo [[:digit:]]zwraca , podczas [[:digit:]]gdy spodziewałem się, że wydrukuje wszystkie cyfry od 0do 9?

AbdAllah Talaat
źródło
Uważaj na: unix.stackexchange.com/q/347950/117549
Jeff Schaller

Odpowiedzi:

34

Ponieważ są to dwie różne rzeczy. {1,2,3}Jest przykładem ekspansji usztywniającym . {1,2,3}Konstrukt jest rozszerzony przez powłokę , zanim echojeszcze widzi. Możesz zobaczyć, co się stanie, jeśli użyjesz set -x:

$ set -x
$ echo {1,2,3}
+ echo 1 2 3
1 2 3

Jak widać, polecenie echo {1,2,3}jest rozwinięte do:

echo 1 2 3

Jednak [[:digit:]]jest klasa znaków POSIX . Gdy go podasz echo, powłoka najpierw przetwarza go, ale tym razem jest przetwarzana jako glob powłoki . działa tak samo jak podczas uruchamiania, echo *który wydrukuje wszystkie pliki w bieżącym katalogu. Ale [[:digit:]]jest globem powłoki, który będzie pasował do dowolnej cyfry. Teraz, bash, jeśli glob powłoki nie pasuje do niczego, zostanie rozwinięty do siebie:

$ echo /this*matches*no*files
+ echo '/this*matches*no*files'
/this*matches*no*files

Jeśli glob pasuje do czegoś, co zostanie wydrukowane:

$ echo /e*c
+ echo /etc
/etc

W obu przypadkach echopo prostu wypisuje wszystko, co powłoka nakazuje wydrukować, ale w drugim przypadku, ponieważ glob pasuje do czegoś ( /etc), poleca się wydrukować coś.

Ponieważ nie masz żadnych plików ani katalogów, których nazwa składa się z dokładnie jednej cyfry (co [[:digit:]]by się zgadzało), glob rozszerza się do siebie i otrzymujesz:

$ echo [[:digit:]]
[[:digit:]]

Teraz spróbuj utworzyć plik o nazwie 5i uruchomić to samo polecenie:

$ echo [[:digit:]]
5

A jeśli istnieje więcej niż jeden pasujący plik:

$ touch 1 5       
$ echo [[:digit:]]
1 5

Jest to (w pewnym sensie) udokumentowane w man bashobjaśnieniu nullglobopcji, które wyłączają to zachowanie:

nullglob
    If  set,  bash allows patterns which match no files (see
    Pathname Expansion above) to expand to  a  null  string,
    rather than themselves.

Jeśli ustawisz tę opcję:

$ rm 1 5
$ shopt -s nullglob
$ echo [[:digit:]]  ## prints nothing

$ 
terdon
źródło
4
Zobacz także, shopt -s failglobaby uzyskać bardziej przydatne zachowanie podobne do współczesnych powłok, takich jak zshlub fish.
Stéphane Chazelas,
Zgadzam się ze Stéphane, użyj failglob. nullglobmoże powodować nieoczekiwane problemy, np. podczas wklejania adresu URL, który ma ?.
Kevin
1
Jasne, wspomniałem tylko, nullglobaby pokazać, że wzór jest interpretowany przez powłokę jako glob.
terdon
14

{1,2,3}jest rozwinięciem nawiasów , rozszerza się na wyrazy wymienione bez względu na ich znaczenie.

[...]to grupa znaków, używana w rozszerzaniu nazw plików (lub symbolach wieloznacznych lub globalnych) podobnie jak gwiazdka *i znak zapytania ?. Pasuje do każdego pojedynczego znaku na liście lub znaków należących do nazwanych grup, takich jak [:digit:]te, które są na liście. Domyślnym zachowaniem większości powłok jest pozostawienie znaku wieloznacznego bez zmian, jeśli nie ma pasujących plików.

(Zauważ, że tak naprawdę nie można przekształcić symbolu wieloznacznego / wzoru w zestaw pasujących łańcuchów. Gwiazdka może pasować do dowolnego łańcucha o dowolnej długości, więc rozwinięcie dowolnego zawierającego go wzoru spowodowałoby nieskończoną listę łańcuchów.)

Więc:

$ bash -c 'echo [[:digit:]]'           # bash leaves it as-is
[[:digit:]]
$ zsh -c 'echo [[:digit:]]'            # zsh by default complains if no match
zsh:1: no matches found: [[:digit:]]
$ touch 1 3 d i g t
$ bash -c 'echo [[:digit:]]'           # now there are two matches
1 3                                    # note that d, i, g and t do NOT match

Ale nadal:

$ bash -c 'echo {1,2,3}'
1 2 3

Oba są rozszerzone przez powłokę , nie ma znaczenia, czy uruchomione polecenie to ls, echolub rm. Pamiętaj też, że jeśli którykolwiek z nich jest cytowany, nie zostaną one rozwinięte:

$ bash -c 'echo "[[:digit:]]"'         # even though matching files still exist
[[:digit:]]
$ bash -c 'echo "{1,2,3}"'
{1,2,3}
ilkkachu
źródło
dziękuję za twoją odpowiedź, jestem nowy w systemie Linux, więc pozwól, że zapytam cię, jak echo jest związane z plikami 1 3, jego funkcją jest wydrukowanie argumentów na standardowe wyjście, nie szukając plików według mojej wiedzy
AbdAllah Talaat
1
@AbdAllahTalaat, to nie ma nic wspólnego z echem. Powłoka (np. Bash) „rozszerzy się” [[:digit:]] przed przekazaniem jej echo, więc echonigdy nie widzi [[:digit:]], tylko widzi 1 3. Możesz to zobaczyć w działaniu poprzez uruchomienie, set -xktóre wypisze uruchomione polecenia (uruchom, set +xaby je ponownie wyłączyć).
terdon
@AbdAllahTalaat, echonie szuka plików, powłoka robi to przed uruchomieniem echo.
ilkkachu
Zwłaszcza, że ​​myślę, że w DOS / Windows narzędzia rozszerzają symbole wieloznaczne, a nie powłokę. (Może się mylę)
ilkkachu
przepraszam chłopaki, przesunąłem poprawną odpowiedź na odpowiedź tedron, ponieważ jego komentarz zawierał znaczenie tego, że bash jest tym, czego praca nie odbija się echem ... jego odpowiedź również zawierała to znaczenie ... wszyscy pomogliście mi ... chciałbym móc poprawna odpowiedź na wszystkie twoje odpowiedzi i komentarze
AbdAllah Talaat
4

{1,2,3}(i np. {1..3}rozszerzeniami nawiasów klamrowych . Są one interpretowane przez powłokę przed wykonaniem polecenia.

[[:digit:]]jest tokenem pasującym do wzorca , ale nie używasz go w lokalizacji z plikami pasującymi do tego wzorca. Jeśli użyjesz dopasowania wzorca, które nie ma dopasowań, rozwija się ono do siebie:

$ echo [[:digit:]]; touch 3; echo [[:digit:]]
[[:digit:]]
3
DopeGhoti
źródło
Nie, jak inne odpowiedzi poprawnie wskazują, wzorzec jest dopasowywany do nazw plików.
Toby Speight