Warum stimmt [AZ] mit Kleinbuchstaben in Bash überein?


42

In allen mir bekannten Shells werden rm [A-Z]*alle Dateien entfernt, die mit einem Großbuchstaben beginnen. Mit bash werden jedoch alle Dateien entfernt, die mit einem Buchstaben beginnen.

Da dieses Problem unter Linux und Solaris mit bash-3 und bash-4 besteht, kann es sich nicht um einen Fehler handeln, der durch einen fehlerhaften Mustervergleich in libc oder eine falsch konfigurierte Gebietsschemadefinition verursacht wird.

Ist dieses seltsame und riskante Verhalten beabsichtigt oder handelt es sich nur um einen Fehler, der seit vielen Jahren nicht mehr behoben wurde?


3
Was wird localeausgegeben? Ich kann das nicht reproduzieren ( touch foo; echo [A-Z]*gibt das wörtliche Muster, nicht "foo", in einem ansonsten leeren Verzeichnis aus).
Chepner

4
Wenn man bedenkt, wie viele Leute gesagt haben, dass es für sie funktioniert, oder Beispiele gezeigt haben, wie LC_COLLATE dies beeinflusst, könnten Sie Ihre Frage möglicherweise bearbeiten, um eine Beispiel-Bash-Sitzung hinzuzufügen, die genau das Szenario darstellt, nach dem Sie fragen. Bitte geben Sie die von Ihnen verwendete Bash-Version an.
Kenster

Wenn Sie den gesamten Text hier gelesen hätten, würden Sie wissen, welche Bash-Version ich verwende und was ich getan habe, da ich die Lösung für meine Frage bereits gepostet habe. Lassen Sie mich die Lösung wiederholen: bash verwaltet kein eigenes Gebietsschema, sodass die Einstellung von LC_COLLATE nichts ändert, bis Sie einen weiteren Bash-Prozess mit der neuen Umgebung starten.
Schily

1
Siehe auch Betrifft (sollte) LC_COLLATE Zeichenbereiche? (aber diese Frage bezog sich nicht speziell auf Bash)
Gilles 'SO - hör auf, böse zu sein'

"Das Setzen von LC_COLLATE ändert nichts, bis Sie einen weiteren Bash-Prozess mit der neuen Umgebung starten." Das stimmt nicht mit dem Verhalten überein, das ich mit bash-4 unter Solaris sehe. Es ändert das Verhalten in der laufenden Shell. # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Antworten:


67

Beachten Sie, dass bei der Verwendung von Bereichsausdrücken wie [az] in Abhängigkeit von der Einstellung von LC_COLLATE möglicherweise Buchstaben des anderen Falls eingeschlossen werden.

LC_COLLATE ist eine Variable, die die Sortierreihenfolge bestimmt, die beim Sortieren der Ergebnisse der Pfadnamenerweiterung verwendet wird, und das Verhalten von Bereichsausdrücken, Äquivalenzklassen und Sortierfolgen innerhalb der Pfadnamenerweiterung und des Mustervergleichs bestimmt.


Folgendes berücksichtigen:

$ 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

Beachten Sie, dass beim Aufrufen des Befehls echo [a-z]als Ausgabe alle Dateien mit Kleinbuchstaben erwartet werden. Außerdem werden mit echo [A-Z]Dateien mit Großbuchstaben erwartet.


Standardkollatierungen mit Gebietsschemas en_UShaben die folgende Reihenfolge:

aAbBcC...xXyYzZ
  • Zwischen aund z(in [a-z]) stehen ALLE Großbuchstaben, mit Ausnahme von Z.
  • Zwischen Aund Z(in [A-Z]) stehen ALLE Kleinbuchstaben, mit Ausnahme von a.

Sehen:

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

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

Wenn Sie die LC_COLLATEVariable in ändern , Csieht dies wie erwartet aus:

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

Es ist also kein Fehler , es ist ein Kollationsproblem .


Anstelle von Bereichsausdrücken können Sie POSIX-definierte Zeichenklassen wie upperoder verwenden lower. Sie funktionieren auch mit verschiedenen LC_COLLATEKonfigurationen und sogar mit Akzentzeichen :

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

Ob dieses Verhalten von LC_ * -Umgebungsvariablen gesteuert werden kann, habe ich nicht gefragt. Ich arbeite im POSIX-Standardkomitee und weiß, wie man Probleme zusammenstellt, tralso habe ich dies zuerst überprüft.
Schily

@schily Ich kann Ihr Problem weder mit einer alten Bash-3 noch mit einer Bash-4 reproduzieren. beide sind steuerbar, LC_COLLATEwas auch im Handbuch dokumentiert ist.
Chaos

Entschuldigung, ich kann nicht wiedergeben, was Sie glauben, aber meine eigene Antwort sehen ... Aus den Ideen in dieser Diskussion habe ich den Grund für das Problem herausgefunden.
Schily

25

[A-Z]In bashÜbereinstimmungen werden alle Sortierelemente (Zeichen, aber auch Zeichenfolgen wie Dszin ungarischen Gebietsschemata) angezeigt, die nach Aund vor sortieren Z. Sortiert in Ihrem Gebietsschema cwahrscheinlich zwischen B und C.

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

So coder so zwürde es passen [A-Z], aber nicht oder 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

In der Ländereinstellung C wäre die Reihenfolge:

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

So [A-Z]würde passen A, B, C, Z, aber nicht Çund noch nicht .

Wenn Sie in Großbuchstaben (in einem Skript) übereinstimmen möchten, können Sie [[:upper:]]stattdessen verwenden. Es gibt keinen eingebauten Weg, bashum nur Großbuchstaben in der lateinischen Schrift abzugleichen (außer indem sie einzeln aufgelistet werden ).

Wenn Sie das übereinstimmen soll Aauf Z Englisch Buchstaben ohne diakritische Zeichen, können Sie entweder [A-Z]oder [[:upper:]]aber in der Clocale (die Daten unter der Annahme nicht in Zeichensätze wie BIG5 oder GB18030 codiert , die mehrere Zeichen , deren Codierung hat enthält die Kodierung dieser Briefe) oder Liste sie einzeln ( [ABCDEFGHIJKLMNOPQRSTUVWXYZ]).

Beachten Sie, dass zwischen den Schalen einige Unterschiede bestehen.

Für zsh, bash -O globasciiranges(seltsam benannte Option in bash-4.3 eingeführt), schily-shund yash, [A-Z]Ergebnisse auf dem aktuelle Zeichen Punkt , dessen Code zwischen dem von Aund der Zwürde so sein , entspricht das Verhalten bashin der C - locale.

Für Asche, MKSH und alte Muscheln, wie zshoben, jedoch auf Einzelbyte-Zeichensätze beschränkt. Das heißt, in einem UTF-8-Gebietsschema [É-Ź]würde dies beispielsweise nicht mit "on" übereinstimmen Ó, aber da dies so ist [<c3><89>-<c5><b9>], würde dies mit den Bytewerten 0x89 bis 0xc5 übereinstimmen!

ksh93Verhalten wie mit der bashAusnahme, dass es als Sonderfallbereiche behandelt wird, deren Enden beide mit Kleinbuchstaben oder Großbuchstaben beginnen. In diesem Fall werden nur Sortierelemente berücksichtigt, die zwischen diesen Enden sortiert sind, bei denen es sich jedoch (oder bei Sortierelementen mit mehreren Zeichen um das erste Zeichen) auch um Kleinbuchstaben (bzw. Großbuchstaben) handelt. So [A-Z]würde es passen auf É, aber nicht auf eso etut Art zwischen Aund Zist aber nicht groß geschrieben wie Aund Z.

Bei fnmatch()Mustern (wie in find -name '[A-Z]') oder regulären Systemausdrücken (wie in grep '[A-Z]') hängt dies vom System und der Ländereinstellung ab. Zum Beispiel [A-Z]stimmt auf einem GNU-System hier nicht xdas en_GB.UTF-8Gebietsschema überein , sondern das Gebietsschema th_TH.UTF-8. Es ist mir unklar, welche Informationen verwendet werden, um dies festzustellen, aber es basiert anscheinend auf einer Nachschlagetabelle, die aus den Gebietsschemadaten von LC_COLLATE abgeleitet wurde .

Alle Verhaltensweisen werden von POSIX zugelassen, da POSIX das Verhalten von Bereichen in anderen Gebietsschemas als dem C-Gebietsschema nicht angibt. Jetzt können wir über die Vorteile jedes Ansatzes streiten.

bashDer Ansatz von ist sehr sinnvoll, da [C-G]wir die Zeichen zwischen Cund haben wollen G. Und die Sortierreihenfolge des Benutzers zu verwenden, um zu bestimmen, was dazwischen liegt, ist der logischste Ansatz.

Das Problem ist nun, dass es die Erwartungen vieler Menschen verletzt, insbesondere derjenigen, die an das traditionelle Verhalten vor Unicode gewöhnt sind, sogar an Tage vor der Internationalisierung. Während von einem normalen Benutzer, kann es sinnvoll ist , das [C-I]umfasst hder als hBrief zwischen Cund Iund das [A-g]beinhaltet nicht Z, dann ist es eine andere Sache für die Menschen seit Jahrzehnten nur mit ASCII behandelt zu haben.

Dieses bashVerhalten unterscheidet sich auch von der [A-Z]Bereichsübereinstimmung in anderen GNU-Werkzeugen wie in regulären GNU-Ausdrücken (wie in grep/ sed...) oder fnmatch()in find -name.

Dies bedeutet auch, dass die [A-Z]Übereinstimmungen mit der Umgebung, dem Betriebssystem und der Version des Betriebssystems variieren. Die Tatsache, dass [A-Z]Á, aber nicht matches übereinstimmt, ist ebenfalls suboptimal.

Für zsh/ verwenden yashwir eine andere Sortierreihenfolge. Anstatt sich auf die Zeichenreihenfolge des Benutzers zu verlassen, verwenden wir die Zeichenpunktcodewerte. Das hat den Vorteil, dass es leicht zu verstehen ist, aber außerhalb von ASCII ist es aus praktischer Sicht nicht sehr nützlich. [A-Z]Entspricht den 26 US-englischen Großbuchstaben und den [0-9]Dezimalstellen. Es gibt Codepunkte in Unicode, die der Reihenfolge einiger Alphabete folgen, aber nicht verallgemeinert sind und nicht verallgemeinert werden können, da sich verschiedene Personen, die dasselbe Skript verwenden, nicht notwendigerweise auf die Reihenfolge der Buchstaben einigen.

Bei traditionellen Shells und mksh, dash, ist es defekt (da die meisten Leute jetzt Multi-Byte-Zeichen verwenden), aber vor allem, weil sie noch keine Multi-Byte-Unterstützung haben. Das Hinzufügen von Multi-Byte-Unterstützung zu Shells wie bashund zshwar eine enorme Anstrengung und dauert noch an. yash(eine japanische Shell) wurde ursprünglich von Anfang an mit Multi-Byte-Unterstützung entwickelt.

Der Ansatz von ksh93 hat den Vorteil, dass er mit den regulären Ausdrücken oder fnmatch () des Systems konsistent ist (oder zumindest auf GNU-Systemen zu sein scheint). Dort wird die Erwartungshaltung einiger Leute nicht verletzt, da [A-Z]Kleinbuchstaben, [A-Z]Includes É(und Á, aber nicht Ź) nicht enthalten sind. Es ist nicht konsistent mit sortoder allgemeiner strcoll()Ordnung.


1
Wenn Sie recht hätten, könnte dies über LC_ * -Variablen gesteuert werden. Es scheint einen anderen Grund zu geben.
Schily

1
@ Cuonglm, eher wie mksh(beide von pdksh abgeleitet). posh -c $'case Ó in [É-Ź]) echo yes; esac'gibt nichts zurück.
Stéphane Chazelas

2
@schily, ich erwähne, sortweil bashGlobs auf der Sortierreihenfolge der Zeichen basieren. Ich habe derzeit keinen Zugriff auf eine so alte Version von bash, kann sie aber später überprüfen. War es damals anders?
Stéphane Chazelas

1
Lassen Sie mich noch einmal erwähnen: zsh, POSIX-ksh88, ksh93t + Bourne Shell, alle verhalten sich so, wie ich es erwartet habe. Bash ist die einzige Shell, die sich anders verhält und bash ist in diesem Fall nicht über das Gebietsschema steuerbar.
Schily

2
@schily, beachte, dass \xFFes das Byte 0xFF gibt, nicht das Zeichen U + 00FF ( ÿselbst codiert als 0xC3 0xBF). \xFFAlleine bildet kein gültiges Zeichen, daher kann ich nicht erkennen, warum es mit übereinstimmen sollte [É-Ź].
Stéphane Chazelas

9

Es ist beabsichtigt und dokumentiert in der bashDokumentation, Abschnitt Mustervergleich . Der Bereichsausdruck enthält [X-Y]alle Zeichen zwischen Xund unter YVerwendung der Sortierfolge und des Zeichensatzes des aktuellen Gebietsschemas:

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

Sie können sehen, bsortiert zwischen Aund Zin en_US.utf8Gebietsschema.

Sie haben einige Möglichkeiten, um dieses Verhalten zu verhindern:

# 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:]]*'

oder aktivieren globasciiranges(mit bash 4.3 und höher):

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

6

Ich habe dieses Verhalten auf einer neuen Amazon EC2-Instanz beobachtet. Da das OP kein MCVE angeboten hat , werde ich eines posten:

$ 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

LC_*Wenn mein Set nicht aktiviert ist, führt die Veröffentlichung von Bash 4.1.2 (1) unter Linux zu einem anscheinend merkwürdigen Verhalten. Ich kann das seltsame Verhalten zuverlässig umschalten, indem ich die jeweiligen Gebietsschemavariablen ein und aussetze. Es überrascht nicht, dass dieses Verhalten beim Exportieren konsistent erscheint:

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

Während ich Bash sehe, wie Stéphane "Shellshock" Chazelas antwortete , denke ich, dass die Bash-Dokumentation zum Pattern Matching fehlerhaft ist:

Beispielsweise in dem Standard - C locale "[a-dx-z] ist äquivalent zu '[abcdxyz]'

Ich las diesen Satz (Hervorhebung meiner) als "Wenn die relevanten Gebietsschemavariablen nicht gesetzt sind, wird bash standardmäßig das Gebietsschema C verwenden". Bash scheint das nicht zu tun. Stattdessen scheint die Standardeinstellung ein Gebietsschema zu sein, in dem die Zeichen in Wörterbuchreihenfolge mit diakritischer Faltung sortiert sind:

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

Ich denke, es wäre gut für Bash, zu dokumentieren, wie es sich verhält, wenn LC_*(speziell LC_CTYPEund LC_COLLATE) undefiniert sind. Aber in der Zwischenzeit werde ich einige Weisheiten teilen :

... Sie müssen sehr vorsichtig mit [Zeichenbereichen] sein, da diese nur dann die erwarteten Ergebnisse liefern, wenn sie ordnungsgemäß konfiguriert sind. Sie sollten sie vorerst vermeiden und stattdessen Zeichenklassen verwenden.

und

Wenn Sie wirklich korrekt sind und / oder Skripte für eine Umgebung mit mehreren Ländereinstellungen erstellen, ist es wahrscheinlich am besten sicherzustellen, dass Sie die Ländereinstellungsvariablen kennen, wenn Sie Dateien abgleichen, oder sicherzustellen, dass Sie in a codieren völlig generisch.


Update Schauen wir uns anhand des @ G-Man-Kommentars genauer an, was passiert:

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

Ah, ha! Das erklärt die zuvor gesehene Zusammenstellung. Lassen Sie uns alle Gebietsschemavariablen entfernen:

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

Na, bitte. Jetzt arbeitet bash konsequent in Bezug auf die Dokumentation auf diesem Linux-System. Wenn eine der locale Variablen gesetzt sind ( LANGUAGE, LANG, LC_COLLATE, LC_CTYPE, LC_ALL, etc.) verwendet dann heftiger Schlag diejenigen entsprechend seiner Anleitung. Andernfalls fällt bash auf C zurück.

Die Wooledge-Bash-FAQ enthält Folgendes :

In neueren GNU-Systemen werden die Variablen in dieser Reihenfolge verwendet. Wenn SPRACHE eingestellt ist, verwenden Sie diese Option, es sei denn, SPRACHE ist auf C eingestellt. In diesem Fall wird SPRACHE ignoriert. Außerdem verwenden einige Programme einfach überhaupt keine SPRACHE. Andernfalls verwenden Sie diese Option, wenn LC_ALL festgelegt ist. Wenn andernfalls die spezifische LC_ * -Variable festgelegt ist, die diese Verwendung abdeckt, verwenden Sie diese. (LC_MESSAGES behandelt beispielsweise Fehlermeldungen.) Verwenden Sie andernfalls LANG.

Das offensichtliche Problem sowohl bei der Bedienung als auch bei der Dokumentation kann also durch Betrachten der Gesamtsumme aller lokalen Fahrvariablen erklärt werden.


Wenn keine LC_variable vorhanden ist und bash sich nicht wie für das CGebietsschema dokumentiert verhält , ist dies ein Fehler.
Schily

1
@bishop: (1) Tippfehler: MVCE sollte MCVE sein. (2) Wenn Sie möchten, dass Ihr Beispiel vollständig ist, sollten Sie env | grep LANGoder hinzufügen echo "$LANG".
G-Man sagt, dass Monica

@schily Weitere Untersuchungen haben mich überzeugt, dass es keinen Fehler in der Dokumentation oder im Betrieb dieses Linux-Systems gibt.
Bischof

@ G-Man Danke! Ich habe es vergessen LANG. Mit diesem Hinweis wird alles erklärt.
Bischof

LANG wurde um 1988 von Sun für die ersten Lokalisierungsversuche eingeführt, bevor festgestellt wurde, dass eine einzelne Variable nicht ausreicht. Heute wird es als Fallback und LC_ALL als erzwungenes Überschreiben verwendet.
Schily

3

Das Gebietsschema kann ändern, nach welchen Zeichen gesucht wird [A-Z]. Verwenden

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

den Einfluss zu beseitigen. (Ich habe eine Subshell verwendet, um die Änderung zu lokalisieren.)


Dies funktioniert nicht, es stimmt immer noch mit allen Buchstaben
überein

7
Dies funktioniert nicht, da glob bereits vor der Ausführung von rm ausgeführt wurde. Versuchen Sie es export LC_ALL=Czuerst.
Cuonglm

Entschuldigung, Sie haben die Frage, die mit bash und nicht mit rm zusammenhängt, falsch verstanden.
Schily

@schily: Ja, ich habe mich geirrt, du musst die Aussagen trennen. Überprüfen Sie das Update.
Choroba

2

Wie bereits gesagt, handelt es sich hierbei um eine "Sammelbestellung".

Der Bereich az kann in einigen Ländereinstellungen Großbuchstaben enthalten:

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

Die richtige Lösung seit Bash 4.3 ist das Setzen der Option globasciiranges:

shopt -s globasciiranges

um bash so zu machen, als ob LC_COLLATE=Ces in globalen Bereichen eingestellt wäre.


-6

Es scheint, dass ich die richtige Antwort auf meine eigene Frage gefunden habe:

Bash ist fehlerhaft, da es sein eigenes Gebietsschema nicht verwaltet. Das Setzen von LC_ * in einem Bash-Prozess hat in diesem Shell-Prozess keine Auswirkung.

Wenn Sie LC_COLLATE = C setzen und dann eine weitere Bash starten, funktioniert das Globbing im neuen Bash-Prozess wie erwartet.


2
Nicht in einem meiner Schläge.
Chaos

2
Ich wiederhole das in keiner Version von Bash auf meinem Computer, es klingt, als hättest du es nicht exportrichtig gemacht.
Chris Down

Sie glauben also, dass etwas, das ordnungsgemäß exportiert wird und sich auf einen neuen Bash-Prozess auswirkt, nicht ordnungsgemäß exportiert wird?
Schily

4
Der Umgang mit der Umgebung in Solaris ist bekanntermaßen mangelhaft, sodass ich mich nicht wundern würde, wenn der "Bug" in bash das Fehlen einer Solaris-spezifischen Problemumgehung wäre.
Hobbs

1
@schily: Haben Sie ein Zitat dafür, wo das Ändern der LC_ * -Variablen in einer Shell erforderlich ist, damit sie ihren eigenen Gebietsschemastatus aktualisiert? Ich würde genau das Gegenteil denken. Insbesondere für eine Shell, die ein Skript ausführt, hat das Ändern des Gebietsschemas auf halbem Weg durch Parsen / Ausführen des Skripts nicht einmal ein genau definiertes Verhalten, da das Skript eine Textdatei ist und "Textdatei" nur im Kontext von a von Bedeutung ist Einzelzeichenkodierung.
R ..
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.