Dlaczego [AZ] dopasowuje małe litery w bash?


42

We wszystkich znanych mi powłokach rm [A-Z]*usuwa wszystkie pliki rozpoczynające się na wielką literę, ale w przypadku bash powoduje to usunięcie wszystkich plików rozpoczynających się na literę.

Ponieważ ten problem występuje w systemach Linux i Solaris w wersjach bash-3 i bash-4, nie może to być błąd spowodowany błędnym dopasowaniem wzorca w libc lub błędnie skonfigurowaną definicją ustawień regionalnych.

Czy to dziwne i ryzykowne zachowanie jest zamierzone, czy to tylko błąd, który istnieje od wielu lat?


3
Co daje localewynik? Nie mogę tego odtworzyć ( touch foo; echo [A-Z]*wypisuje dosłowny wzorzec, a nie „foo”, w innym pustym katalogu).
chepner

4
Biorąc pod uwagę, ile osób powiedziało, że to dla nich działa, lub pokazało przykłady, w jaki sposób LC_COLLATE na to wpływa, być może możesz edytować swoje pytanie, aby dodać przykładową sesję bash, która dokładnie obrazuje pytany scenariusz. Dołącz używaną wersję bash.
Kenster,

Jeśli przeczytałeś cały tekst tutaj, wiedziałbyś, jakiej wersji bash używam i co zrobiłem, odkąd już opublikowałem rozwiązanie mojego pytania. Powtórzę rozwiązanie: bash nie zarządza własnymi ustawieniami narodowymi, więc ustawienie LC_COLLATE niczego nie zmieni, dopóki nie uruchomisz kolejnego procesu bash w nowym środowisku.
schily,

1
Zobacz także Czy (powinien) LC_COLLATE wpływa na zakresy znaków? (ale to pytanie nie dotyczyło konkretnie uderzenia)
Gilles „SO- przestań być zły”

„ustawienie LC_COLLATE niczego nie zmienia, dopóki nie uruchomisz kolejnego procesu bash w nowym środowisku.” To nie pasuje do zachowania, które widzę w bash-4 w systemie Solaris. Zmienia zachowanie działającej powłoki. # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Odpowiedzi:


67

Zauważ, że podczas używania wyrażeń zakresowych, takich jak [az], mogą być dołączone litery drugiego przypadku, w zależności od ustawienia LC_COLLATE.

LC_COLLATE jest zmienną, która określa kolejność sortowania używaną podczas sortowania wyników interpretacji nazw ścieżek oraz determinuje zachowanie wyrażeń zakresowych, klas równoważności i sekwencji zestawiania w ramach interpretacji nazw ścieżek i dopasowywania wzorców.


Rozważ następujące:

$ touch a A b B c C x X y Y z Z
$ ls
a  A  b  B  c  C  x  X  y  Y  z  Z
$ echo [a-z] # Note the missing uppercase "Z"
a A b B c C x X y Y z
$ echo [A-Z] # Note the missing lowercase "a"
A b B c C x X y Y z Z

Zwróć uwagę, że po echo [a-z]wywołaniu polecenia oczekiwanymi danymi wyjściowymi byłyby wszystkie pliki zawierające małe litery. Ponadto przy echo [A-Z]plikach zawierających wielkie litery można się spodziewać.


Standardowe zestawienia z ustawieniami regionalnymi, takie jak, en_USmają następującą kolejność:

aAbBcC...xXyYzZ
  • Pomiędzy ai z(w [a-z]) są WSZYSTKIE wielkie litery, z wyjątkiem Z.
  • Pomiędzy Ai Z(w [A-Z]) są WSZYSTKIE małe litery, z wyjątkiem a.

Widzieć:

     aAbBcC[...]xXyYzZ
     |              |
from a      to      z

     aAbBcC[...]xXyYzZ
      |              |
from  A     to       Z

Jeśli zmienisz LC_COLLATEzmienną, Cbędzie wyglądać zgodnie z oczekiwaniami:

$ export LC_COLLATE=C
$ echo [a-z]
a b c x y z
$ echo [A-Z]
A B C X Y Z

Więc to nie jest błąd , to problem z sortowaniem .


Zamiast wyrażeń zakresu możesz użyć klas znaków zdefiniowanych w POSIX , takich jak upperlub lower. Działają również w różnych LC_COLLATEkonfiguracjach, a nawet z akcentowanymi postaciami :

$ echo [[:lower:]]
a b c x y z à è é
$ echo [[:upper:]]
A B C X Y Z

Jeśli to zachowanie było kontrolowane przez zmienne środowiskowe LC_ *, nie pytałem. Pracuję w standardowym komitecie POSIX i wiem, że np. Zestawiam problemy, trwięc sprawdziłem to najpierw.
schily

@schily Nie mogę odtworzyć twojego problemu ani ze starym bash-3, ani bash-4; oba są sterowalne, LC_COLLATEco jest również udokumentowane w instrukcji.
chaos

Przepraszam, nie mogę odtworzyć tego, w co wierzysz, ale widzę własną odpowiedź ... Z pomysłów w tej dyskusji odkryłem przyczynę problemu.
schily,

25

[A-Z]w bashdopasowuje wszystkie elementy zestawiające (znaki, ale wywołanie może być również ciągiem znaków jak Dszw węgierskich ustawieniach regionalnych), które sortują po, Aa sortują przed Z. W twoim regionie cprawdopodobnie sortuje się pomiędzy B i C.

$ printf '%s\n' A a á b B c C Ç z Z  | sort
a
A
á
b
B
c
C
Ç
z
Z

Tak club zbyłoby pasować [A-Z], ale nie lub a.

$ printf '%s\n' A a á b B c C Ç z Z  |
pipe>  bash -c 'while IFS= read -r x; do case $x in [A-Z]) echo "$x"; esac; done'
A
á
b
B
c
C
Ç
z
Z

W ustawieniach regionalnych C kolejność byłaby następująca:

$ printf '%s\n' A a á b B c C Ç z Z  | LC_COLLATE=C sort
A
B
C
Z
a
b
c
z
Ç
á

Więc [A-Z]będzie pasować A, B, C, Z, ale nie Çi nadal nie .

Jeśli chcesz dopasować wielkie litery (w dowolnym skrypcie), możesz użyć [[:upper:]]zamiast tego. Nie ma wbudowanego sposobu, bashaby dopasowywać tylko wielkie litery w skrypcie łacińskim (z wyjątkiem listowania ich indywidualnie).

Jeśli chcesz, aby dopasować Asię do Z angielskich liter bez znaków diakrytycznych, można użyć [A-Z]albo [[:upper:]]ale w Cregionie (przy założeniu, że dane nie są kodowane w zestawach znaków, takich jak BIG5 lub GB18030 który ma kilka znaków, których kodowanie zawiera kodowanie tych liter) lub listy je indywidualnie ( [ABCDEFGHIJKLMNOPQRSTUVWXYZ]).

Zauważ, że istnieją pewne różnice między powłokami.

For zsh, bash -O globasciiranges(dziwnie nazwana opcja wprowadzona w bash-4.3) schily-shi yash, [A-Z]pasuje do znaków, których punkt kodowy znajduje się między tym Aa tym Z, więc byłoby to równoważne zachowaniu bashw ustawieniach regionalnych C.

Dla popiołu, mksza i starożytnych pocisków, takich samych jak zshpowyżej, ale ograniczonych do jednobajtowych zestawów znaków. Oznacza to, że na przykład w ustawieniach regionalnych UTF-8 [É-Ź]nie pasowałoby Ó, ale ponieważ tak [<c3><89>-<c5><b9>], pasowałyby do wartości bajtów 0x89 do 0xc5!

ksh93zachowuje się tak bash, ale traktuje jako zakresy przypadków specjalnych, których końce zaczynają się małymi lub dużymi literami. W takim przypadku dopasowuje się tylko w elementach zestawiających, które sortują między tymi końcami, ale które (lub ich pierwszy znak w przypadku elementów zestawiających wiele znaków) są również pisane małymi literami (lub odpowiednio dużymi literami). Więc [A-Z]nie będzie pasować É, ale nie na ejak erobi porządek między Aa Z, ale nie jest wielka, jak Ai Z.

W przypadku fnmatch()wzorców (jak w find -name '[A-Z]') lub systemowych wyrażeń regularnych (jak w grep '[A-Z]') zależy to od systemu i ustawień regionalnych. Na przykład w systemie GNU tutaj [A-Z]nie pasuje xw en_GB.UTF-8ustawieniach regionalnych, ale w tym th_TH.UTF-8jednym. Nie jest dla mnie jasne, jakich informacji używa, aby to ustalić, ale najwyraźniej opiera się na tabeli odnośników pochodzącej z danych regionalnych LC_COLLATE ).

Wszystkie zachowania są dozwolone przez POSIX, ponieważ POSIX pozostawia zachowanie zakresów nieokreślonych w ustawieniach regionalnych innych niż ustawienia regionalne C. Teraz możemy spierać się o zalety każdego podejścia.

bashPodejście to ma wiele sensu [C-G], ponieważ chcemy, aby postacie były pomiędzy Ca G. I stosując porządek użytkownika za to, co określa, co w międzyczasie jest najbardziej logicznym rozwiązaniem.

Problem polega na tym, że przełamuje oczekiwania wielu ludzi, zwłaszcza tych, którzy przywykli do tradycyjnego zachowania przed Unicode, nawet dni poprzedzających internacjonalizację. Choć od normalnego użytkownika, to sprawia, maja poczucie, że [C-I]zawiera hjako hlist jest między Ca Ii że [A-g]nie obejmuje Z, to inna sprawa dla osób mających do czynienia z ASCII tylko przez dziesięciolecia.

To bashzachowanie różni się również od [A-Z]dopasowania zakresu w innych narzędziach GNU, takich jak wyrażenia regularne GNU (jak w grep/ sed...) lub fnmatch()jak w find -name.

Oznacza to również, że to, co [A-Z]pasuje, różni się w zależności od środowiska, systemu operacyjnego i wersji systemu operacyjnego. Fakt, że [A-Z]pasuje do Á, ale nie Ź, jest również nieoptymalny.

Dla zsh/ yashużywamy innego porządku sortowania. Zamiast polegać na pojęciu kolejności znaków przez użytkownika, używamy wartości kodu punktu znakowego. Ma to tę zaletę, że jest łatwe do zrozumienia, ale z praktycznego punktu widzenia niewielu, poza ASCII, nie jest bardzo przydatne. [A-Z]dopasowuje 26 wielkich amerykańskich liter w języku amerykańskim, [0-9]dopasowuje cyfry dziesiętne. Istnieją punkty kodowe w Unicode, które są zgodne z kolejnością niektórych alfabetów, ale nie są uogólnione i nie mogą być uogólnione, ponieważ w każdym razie różni ludzie używający tego samego skryptu niekoniecznie zgadzają się na kolejność liter.

W przypadku tradycyjnych powłok i mksh, myślnik jest zepsuty (teraz, gdy większość ludzi używa znaków wielobajtowych), ale przede wszystkim dlatego, że nie ma jeszcze obsługi wielu bajtów. Dodanie obsługi wielu bajtów do powłok takich jak bashi zshbyło dużym wysiłkiem i wciąż trwa. yash(japońska powłoka) od samego początku była projektowana z obsługą wielu bajtów.

Podejście ksh93 ma tę zaletę, że jest spójne z wyrażeniami regularnymi systemu lub fnmatch () (lub przynajmniej wydaje się, że przynajmniej w systemach GNU). Nie łamie to oczekiwań niektórych osób, ponieważ [A-Z]nie zawiera małych liter, [A-Z]obejmuje É(i Á, ale nie Ź). To nie jest zgodne z sortlub ogólnie strcoll()zamówienie.


1
Jeśli miałeś rację, można to kontrolować za pomocą zmiennych LC_ *. Wydaje się, że jest inny powód.
schily

1
@cuonglm, bardziej podobny mksh(oba pochodzą z pdksh). posh -c $'case Ó in [É-Ź]) echo yes; esac'nic nie zwraca.
Stéphane Chazelas,

2
@schily, wspominam, sortponieważ bashglobusy są oparte na kolejności sortowania znaków. Obecnie nie mam dostępu do tak starej wersji bash, ale mogę to sprawdzić później. Czy wtedy było inaczej?
Stéphane Chazelas,

1
Wspomnę jeszcze raz: zsh, POSIX-ksh88, ksh93t + Bourne Shell, wszystkie zachowują się tak, jak się spodziewam. Bash jest jedyną powłoką, która zachowuje się inaczej, w tym przypadku nie można kontrolować za pomocą ustawień regionalnych.
schily,

2
@schily, zauważ, że \xFFistnieje bajt 0xFF, a nie znak U + 00FF ( ÿsam kodowany jako 0xC3 0xBF). \xFFsam nie tworzy prawidłowej postaci, więc nie rozumiem, dlaczego powinna być dopasowana [É-Ź].
Stéphane Chazelas

9

Jest przeznaczony i udokumentowany w bashdokumentacji, sekcji dopasowania wzorców . Wyrażenie zakresu [X-Y]będzie zawierać dowolne znaki pomiędzy Xi Yprzy użyciu kolejności zestawiania i zestawu znaków w bieżącym locale:

LC_ALL=en_US.utf8 bash -c 'case b in [A-Z]) echo yes; esac' 
yes

Widać, bklasyfikowane między Aoraz Zw en_US.utf8lokalizacji.

Masz kilka możliwości, aby temu zapobiec:

# Setting LC_ALL or LC_COLLATE to C
LC_ALL=C bash -c 'echo [A-Z]*'

# Or using POSIX character class
LC_ALL=C bash -c 'echo [[:upper:]]*'

lub włącz globasciiranges(w wersji bash 4.3 i nowszej):

bash -O globasciiranges -c 'echo [A-Z]*'

6

Zauważyłem to zachowanie w nowej instancji Amazon EC2. Ponieważ OP nie zaoferował MCVE , opublikuję jeden:

$ cd $(mktemp -d)
$ touch foo
$ echo [A-Z]*     # prepare for a surprise!
foo

$ echo $BASH_VERSION
4.1.2(1)-release
$ uname -a
Linux spinup-tmp12 3.14.27-25.47.amzn1.x86_64 #1 SMP Wed Dec 17 18:36:15 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

$ env | grep LC_  # no locale, let's set one
$ LC_ALL=C
$ echo [A-Z]*
[A-Z]*

$ unset LC_ALL    # ok, good. what if we go back to no locale?
$ echo [A-Z]*
foo

Więc nie mając mojego LC_*zestawu prowadzi bash 4.1.2 (1) -release w Linuksie, aby wywołać pozornie dziwne zachowanie. Mogę niezawodnie przełączać nieparzyste zachowanie, ustawiając i odznaczając odpowiednie zmienne regionalne. Nic dziwnego, że zachowanie to wydaje się spójne podczas eksportowania:

$ export LC_ALL=C
$ bash
$ echo [A-Z]*
[A-Z]*
$ exit
$ echo $SHLVL
1
$ unset LC_ALL
$ bash
$ echo [A-Z]*
foo

Podczas gdy widzę, że bash zachowuje się tak, jak odpowiedział Chazelas Stéphane „Shellshock” , myślę, że dokumentacja bash na temat dopasowania wzorca jest błędna:

Na przykład w domyślnych ustawieniach regionalnych C „[a-dx-z]” jest równoważne z „[abcdxyz]”

Czytam to zdanie (moje podkreślenie) jako „jeśli odpowiednie zmienne ustawień regionalnych nie są ustawione, wówczas bash domyślnie ustawi się na ustawienia regionalne języka C”. Bash chyba tego nie robi. Zamiast tego wydaje się, że domyślnie ustawiony jest język, w którym znaki są sortowane w kolejności słownikowej ze składaniem znaków diakrytycznych:

$ echo [A-E]*
[A-E]*
$ echo [A-F]*
foo
$ touch "évocateur"
$ echo [A-F]*
foo évocateur

Myślę, że dobrze byłoby, gdyby bash udokumentował, jak się zachowa, gdy LC_*(konkretnie LC_CTYPEi LC_COLLATE) nie zostanie zdefiniowany. Ale tymczasem podzielę się mądrością :

... musisz być bardzo ostrożny z [zakresami postaci], ponieważ nie przyniosą one oczekiwanych rezultatów, jeśli nie zostaną odpowiednio skonfigurowane. Na razie powinieneś unikać ich używania i zamiast tego używać klas postaci.

i

Jeśli naprawdę masz rację i / lub skryptujesz w środowisku z wieloma lokalizacjami, prawdopodobnie najlepiej upewnij się, że wiesz, jakie są zmienne ustawień regionalnych, gdy dopasowujesz pliki, lub upewnij się, że kodujesz w całkowicie ogólny sposób.


Aktualizacja Na podstawie komentarza @ G-Man przyjrzyjmy się bliżej temu, co się dzieje:

$ env | grep LANG
LANG=en_US.UTF-8

Ach, ha! To wyjaśnia zestawienie widoczne wcześniej. Usuńmy wszystkie zmienne regionalne:

$ unset LANG LANGUAGE LC_ALL
$ env | grep 'LC_|LANG'
$ echo [A-Z]*
[A-Z]*

No to jedziemy. Teraz bash działa konsekwentnie w odniesieniu do dokumentacji tego systemu Linux. Jeżeli każdy ze zmiennych lokalizacji są ustawione ( LANGUAGE, LANG, LC_COLLATE, LC_CTYPE, LC_ALL, itd.), A następnie wykorzystuje te atakujących zgodnie z instrukcją. W przeciwnym razie bash wraca do C.

Wooledge bash FAQ ma do powiedzenia:

W najnowszych systemach GNU zmienne są używane w tej kolejności. Jeśli ustawiono JĘZYK, użyj tego, chyba że JĘZYK ustawiony jest na C, w takim przypadku JĘZYK jest ignorowany. Ponadto niektóre programy po prostu w ogóle nie używają LANGUAGE. W przeciwnym razie, jeśli ustawiono LC_ALL, użyj tego. W przeciwnym razie, jeśli ustawiona jest konkretna zmienna LC_ *, która obejmuje to użycie, użyj tego. (Na przykład LC_MESSAGES obejmuje komunikaty o błędach.) W przeciwnym razie użyj LANG.

Widoczny problem, zarówno w działaniu, jak i dokumentacji, można wyjaśnić, analizując całkowitą sumę wszystkich zmiennych sterujących ustawieniami narodowymi.


Jeśli nie występuje zmienna LC_, a bash nie zachowuje się zgodnie z dokumentacją Custawień regionalnych, oznacza to błąd.
schily

1
@bishop: (1) Literówka: MVCE powinna być MCVE. (2) Jeśli chcesz, aby twój przykład był kompletny, dodaj env | grep LANGlub echo "$LANG".
G-Man mówi „Przywróć Monikę”

@schily Dalsze dochodzenie przekonało mnie, że nie ma błędu w dokumentacji lub działaniu tego systemu Linux.
biskup

@ G-Man Thanks! Zapomniałem o LANG. Dzięki tej podpowiedzi wszystko zostało wyjaśnione.
biskup

LANG został wprowadzony około roku 1988 przez firmę Sun do pierwszych prób lokalizacji, zanim odkryli, że pojedyncza zmienna nie jest wystarczająca. Dziś jest używany jako rezerwowy, a LC_ALL jest używany jako wymuszone zastępowanie.
schily

3

Ustawienia regionalne mogą zmieniać, które znaki są dopasowane [A-Z]. Posługiwać się

(LC_ALL=C; rm [A-Z]*)

aby wyeliminować wpływ. (Użyłem podpowłoki, aby zlokalizować zmianę).


To nie działa, nadal pasuje do wszystkich liter
schily

7
To nie zadziała, ponieważ glob został wykonany przed uruchomieniem rm. Spróbuj export LC_ALL=Cnajpierw.
cuonglm

Przepraszamy, nie rozumiesz pytania dotyczącego bash, a nie rm.
schily

@schily: Tak, myliłem się, musisz rozdzielić stwierdzenia. Sprawdź aktualizację.
choroba

2

Jak już powiedziano, jest to kwestia „kolejności zestawiania”.

Zakres az może zawierać duże litery w niektórych lokalizacjach:

     aAbBcC[...]xXyYzZ
     |              |
from a      to      z

Prawidłowym rozwiązaniem od czasu bash 4.3 jest ustawienie opcji globasciiranges:

shopt -s globasciiranges

aby bash działał tak, jakby LC_COLLATE=Czostał ustawiony w globalnych zakresach.


-6

Wygląda na to, że znalazłem właściwą odpowiedź na moje pytanie:

Bash jest błędny, ponieważ nie zarządza własnymi ustawieniami narodowymi. Zatem ustawienie LC_ * w procesie bash nie ma wpływu na ten proces powłoki.

Jeśli ustawisz LC_COLLATE = C, a następnie uruchomisz kolejny bash, globbing działa zgodnie z oczekiwaniami w nowym procesie bash.


2
Nie w żadnym z moich basów.
chaos

2
Nie repro to w żadnej wersji bash na mojej maszynie, wygląda na to, że nie zrobiłeś exporttego poprawnie.
Chris Down,

Czy wierzysz, że coś, co zostało poprawnie wyeksportowane, aby wpłynęło na nowy proces bash, nie zostało poprawnie wyeksportowane?
schily

4
Obsługa środowiska przez Solaris jest notorycznie niewystarczająca, więc nie zdziwiłbym się, gdyby „błędem” w bash był brak obejścia specyficznego dla Solaris.
hobbs

1
@schily: Czy masz wzmiankę o tym, gdzie wymagana jest zmiana zmiennych LC_ * w powłoce, aby zaktualizować własny stan ustawień narodowych? Myślałbym dokładnie odwrotnie. W szczególności dla powłoki wykonującej skrypt, zmiana ustawień regionalnych w trakcie parsowania / wykonywania skryptu nie miałaby nawet dobrze zdefiniowanego zachowania, ponieważ skrypt jest plikiem tekstowym, a „plik tekstowy” ma znaczenie tylko w kontekście kodowanie jednoznakowe.
R ..
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.