Pourquoi [AZ] correspond-il aux minuscules en bash?

42

Dans tous les shells dont je suis au courant, rm [A-Z]*supprime tous les fichiers commençant par une lettre majuscule, mais avec bash, tous les fichiers commençant par une lettre sont supprimés.

Comme ce problème existe sous Linux et Solaris avec bash-3 et bash-4, il ne peut pas s'agir d'un bogue causé par un matcher de motif de buggy dans libc ou par une définition de paramètres régionaux mal configurée.

Ce comportement étrange et risqué est-il prévu ou s'agit-il simplement d'un bug qui existe depuis plusieurs années?

schily
la source
3
Qu'est-ce que la localesortie? Je ne peux pas reproduire ceci ( touch foo; echo [A-Z]*sort le motif littéral, pas "foo", dans un répertoire autrement vide).
Chepner
4
Compte tenu du nombre de personnes ayant déclaré que cela fonctionnait pour elles ou montrant des exemples de l'impact de LC_COLLATE sur ce point, vous pouvez peut-être modifier votre question pour ajouter un exemple de session bash illustrant exactement le scénario que vous demandez. S'il vous plaît inclure la version bash que vous utilisez.
Kenster le
Si vous lisiez tout le texte ici, vous sauriez quelle version de bash j'utilise et ce que j'ai fait depuis que j'ai déjà posté la solution à ma question. Permettez-moi de répéter la solution: bash ne gère pas ses propres paramètres régionaux. Par conséquent, la définition de LC_COLLATE ne change rien jusqu'à ce que vous démarriez un autre processus bash avec le nouvel environnement.
Schily
1
Voir aussi LC_COLLATE (devrait-il) affecter les plages de caractères? (Mais cette question ne concernait pas spécifiquement bash)
Gilles 'SO- Arrête d'être méchant'
"Le réglage de LC_COLLATE ne change rien tant que vous n'avez pas démarré un autre processus bash avec le nouvel environnement." Cela ne correspond pas au comportement que je vois avec bash-4 sous Solaris. Cela change le comportement dans le shell en cours d'exécution. # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Réponses:

67

Notez que lorsque vous utilisez des expressions de plage telles que [az], les lettres de l'autre cas peuvent être incluses, en fonction du paramètre de LC_COLLATE.

LC_COLLATE est une variable qui détermine l'ordre de classement utilisé lors du tri des résultats de l'expansion du chemin, et détermine le comportement des expressions de plage, des classes d'équivalence et des séquences de classement lors du développement du chemin et de la correspondance de motifs.


Considérer ce qui suit:

$ 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

Notez que lorsque la commande echo [a-z]est appelée, la sortie attendue serait tous les fichiers avec des caractères minuscules. En outre, avec echo [A-Z], les fichiers avec des caractères majuscules sont attendus.


Les classements standard avec des paramètres régionaux, tels que en_USl'ordre suivant:

aAbBcC...xXyYzZ
  • Entre aet z(en [a-z]) sont TOUTES les lettres majuscules, sauf pour Z.
  • Entre Aet Z(en [A-Z]) sont TOUTES les lettres minuscules, à l'exception de a.

Voir:

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

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

Si vous modifiez la LC_COLLATEvariable pour Cqu'elle ressemble comme prévu:

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

Donc, ce n'est pas un bug , c'est un problème de classement .


Au lieu des expressions de plage, vous pouvez utiliser les classes de caractères définies par POSIX , telles que upperou lower. Ils fonctionnent aussi avec différentes LC_COLLATEconfigurations et même avec des caractères accentués :

$ echo [[:lower:]]
a b c x y z à è é
$ echo [[:upper:]]
A B C X Y Z
le chaos
la source
Si ce comportement était contrôlable par les variables d’environnement LC_ *, je ne l’ai pas demandé. Je travaille dans le comité de standard POSIX et je connais des problèmes de classement avec, par exemple, trc’est ce que j’ai vérifié en premier.
Schily
@schily je ne peux pas reproduire votre problème ni avec un ancien bash-3 ni avec un bash-4; les deux sont contrôlables via LC_COLLATEce qui est également documenté dans le manuel.
chaos
Désolé, je ne peux pas reproduire ce que vous croyez, mais voyez ma propre réponse ... À partir des idées de cette discussion, j'ai découvert la raison du problème.
Schily
25

[A-Z]in bashcorrespond à tous les éléments de classement (les caractères mais appellent également une séquence de caractères comme Dszdans les paramètres régionaux hongrois) qui trient après Aet trient avant Z. Dans votre région, les ctris probablement entre B et C.

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

Donc cou zserait assorti par [A-Z], mais pas ou 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

Dans les paramètres régionaux C, l'ordre serait:

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

Alors [A-Z]correspondrait A, B, C, Z, mais pas Çet toujours pas .

Si vous voulez faire correspondre les lettres majuscules (dans n'importe quel script), vous pouvez utiliser à la [[:upper:]]place. Il n'y a pas de moyen intégré de bashfaire correspondre uniquement les lettres majuscules dans le script latin (sauf en les listant individuellement).

Si vous voulez faire correspondre l' Aà Z anglais lettres sans signes diacritiques, vous pouvez utiliser [A-Z]ou [[:upper:]]mais dans le Clieu ( en supposant que les données ne sont pas codées dans les jeux de caractères comme GRAND5 ou GB18030 qui a plusieurs caractères dont le codage contient l'encodage de ces lettres) ou de la liste individuellement ( [ABCDEFGHIJKLMNOPQRSTUVWXYZ]).

Notez qu'il existe certaines variations entre les coquilles.

For zsh, bash -O globasciiranges(option étrangement nommée introduite dans bash-4.3), schily-shet yash, les [A-Z]correspondances sur les caractères dont le point de code est compris entre celui de Aet celui de Z, seraient donc équivalentes au comportement de bashla locale C.

Pour les cendres, les mksh et les coquilles anciennes, comme zshci-dessus, mais limité aux jeux de caractères codés sur un octet. C’est-à-dire que, dans un environnement local UTF-8 par exemple, [É-Ź]ne correspondrait pas à Ó, mais puisque cela [<c3><89>-<c5><b9>]correspond aux valeurs d’octets 0x89 à 0xc5!

ksh93se comporte comme bashsi ce n’était les cas spéciaux dont les extrémités commençaient par des lettres minuscules ou des lettres majuscules. Dans ce cas, seuls les éléments de classement triés entre ces extrémités sont appariés, mais qui sont (ou leur premier caractère pour les éléments de classement multi-caractères) également en minuscule (ou majuscule, respectivement). Donc, [A-Z]il y aurait correspondance É, mais pas ecomme etrie entre Aet Zmais n'est pas comme majuscule Aet Z.

Pour les fnmatch()modèles (comme dans find -name '[A-Z]') ou les expressions régulières système (comme dans grep '[A-Z]'), cela dépend du système et des paramètres régionaux. Par exemple, sur un système GNU ici, [A-Z]ne correspond pas xà l' en_GB.UTF-8environnement local, mais au contraire th_TH.UTF-8. Les informations utilisées pour déterminer cela ne sont pas claires, mais elles sont apparemment basées sur une table de consultation dérivée des données de paramètres régionaux LC_COLLATE ).

POSIX autorise tous les comportements, car POSIX laisse le comportement des plages non spécifiées dans des paramètres régionaux autres que les paramètres régionaux C. Nous pouvons maintenant discuter des avantages de chaque approche.

bashCette approche a beaucoup de sens car [C-G]nous voulons que les personnages se situent entre Cet G. Et l'utilisation de l'ordre de tri de l'utilisateur pour déterminer ce qui est intermédiaire est l'approche la plus logique.

Le problème, c’est que cela répond aux attentes de beaucoup de gens, en particulier de ceux qui sont habitués au comportement traditionnel du pré-Unicode, même des jours qui précédaient l’internationalisation. Bien que d'un utilisateur normal, il est logique mai qui [C-I]comprend hque la hlettre est entre Cet Iet [A-g]ne comprend pas Z, il est une autre affaire pour les personnes ayant traité avec ASCII seulement pendant des décennies.

Ce bashcomportement est également différent de la [A-Z]correspondance de plage dans d'autres outils GNU, comme dans les expressions régulières GNU (comme dans grep/ sed...) ou fnmatch()comme dans find -name.

Cela signifie également que ce qui [A-Z]correspond à l'environnement, au système d'exploitation et à la version du système d'exploitation. Le fait de [A-Z]correspondre à Á mais pas à Ź est également sous-optimal.

Pour zsh/ yash, nous utilisons un ordre de tri différent. Au lieu de s’appuyer sur la notion d’ordre des caractères de l’utilisateur, nous utilisons les valeurs de code des points de caractère. Cela a l'avantage d'être facile à comprendre, mais d'un point de vue pratique, en dehors de l'ASCII, ce n'est pas très utile. [A-Z]correspond aux 26 lettres majuscules anglais anglais, [0-9]correspond aux chiffres décimaux. Il existe des points de code dans Unicode qui suivent l'ordre de certains alphabets mais ce n'est pas généralisé et ne peut pas être généralisé car de toute façon, différentes personnes utilisant un même script ne sont pas nécessairement d'accord sur l'ordre des lettres.

Pour les shells traditionnels et mksh, dash, c'est brisé (maintenant que la plupart des gens utilisent des caractères multi-octets), mais principalement parce qu'ils ne disposent pas encore d'une prise en charge multi-octets. Ajout d'un support multi-octets à coquilles comme bashet zsha été un énorme effort et est toujours en cours. yash(un shell japonais) a été initialement conçu avec une prise en charge multi-octets.

L'approche de ksh93 présente l'avantage d'être compatible avec les expressions régulières du système ou avec fnmatch () (ou du moins, du moins sur les systèmes GNU). Là, cela ne brise pas l'attente de certaines personnes, car il [A-Z]n'inclut pas les lettres minuscules, [A-Z]inclut É(et Á, mais pas). Ce n'est pas compatible avec sortou généralement l' strcoll()ordre.

Stéphane Chazelas
la source
1
Si vous aviez raison, cela pourrait être contrôlé via les variables LC_ *. Il semble y avoir une raison différente.
Schily
1
@cuonglm, plus semblable à mksh(tous deux dérivés de pdksh). posh -c $'case Ó in [É-Ź]) echo yes; esac'ne renvoie rien.
Stéphane Chazelas
2
@schily, je mentionne sortparce que les bashglobs sont basés sur l'ordre de tri des caractères. Je n'ai actuellement pas accès à une version aussi ancienne de bash, mais je peux vérifier plus tard. Était-ce différent alors?
Stéphane Chazelas
1
Permettez-moi de mentionner à nouveau: zsh, POSIX-ksh88, ksh93t + Bourne Shell, se comportent tous de la même manière que ce à quoi je m'attendais. Bash est le seul shell qui se comporte différemment et bash n'est pas contrôlable via les paramètres régionaux dans ce cas.
Schily
2
@schily, notez qu'il \xFFy a l' octet 0xFF, pas le caractère U + 00FF ( ÿlui-même codé en tant que 0xC3 0xBF). \xFFseul ne forme pas un caractère valide, je ne vois donc pas pourquoi il devrait être assorti [É-Ź].
Stéphane Chazelas
9

Il est prévu et documenté dans la bashdocumentation, section de correspondance de modèle . L'expression de plage [X-Y]comprendra tous les caractères compris entre Xet Yutilisant la séquence et le jeu de caractères de la locale en cours:

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

Vous pouvez voir, btriés entre Aet Zdans les en_US.utf8paramètres régionaux.

Vous avez plusieurs choix pour empêcher ce problème:

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

ou activer globasciiranges(à partir de 4.3 et plus):

bash -O globasciiranges -c 'echo [A-Z]*'
cuonglm
la source
6

J'ai observé ce comportement sur une nouvelle instance Amazon EC2. Puisque le PO n’a pas offert de MCVE , je vais en poster un:

$ 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

Donc, ne pas avoir mon LC_*jeu entraîne la sortie de bash 4.1.2 (1) sur Linux pour produire un comportement apparemment étrange. Je peux basculer de manière fiable le comportement étrange en définissant et en désactivant les variables de paramètres régionaux respectives. Sans surprise, ce comportement semble être cohérent lors de l'exportation:

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

Bien que bash se comporte comme si Stéphane "Shellshock" répondait Chazelas , je pense que la documentation de bash sur la correspondance des modèles est un buggy:

Par exemple, dans les paramètres régionaux C par défaut , "[a-dx-z]" est équivalent à "[abcdxyz]".

J'ai lu cette phrase (l'emphase mienne) comme suit: "si les variables de paramètres régionaux pertinentes ne sont pas définies, bash utilisera par défaut les paramètres régionaux C". Bash ne semble pas le faire. Au lieu de cela, il semble que les paramètres régionaux par défaut soient triés dans l'ordre du dictionnaire avec le repliement diacritique:

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

Je pense qu'il serait bon que bash documente comment il se comportera quand LC_*(spécifiquement LC_CTYPEet LC_COLLATE) ne sont pas définis. Mais dans le même temps, je partagerai un peu de sagesse :

... vous devez être très prudent avec les [plages de caractères], car elles ne produiront pas les résultats attendus si elles ne sont pas correctement configurées. Pour le moment, évitez de les utiliser et utilisez plutôt des classes de caractères.

et

Si vous êtes vraiment approprié et / ou que vous écrivez des scripts pour un environnement multi-local, il est probablement préférable de vous assurer de connaître vos variables locales lorsque vous faites correspondre les fichiers, ou bien de coder dans un fichier. manière complètement générique.


Mise à jour Sur la base du commentaire de @ G-Man, examinons ce qui se passe:

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

Ah ah! Cela explique le classement vu précédemment. Supprimons toutes les variables de locale:

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

Nous y voilà. Maintenant, bash fonctionne de manière cohérente en ce qui concerne la documentation sur ce système Linux. Si l' une des variables locale sont définies ( LANGUAGE, LANG, LC_COLLATE, LC_CTYPE, LC_ALL, etc.) , puis Bash utilise celles selon son manuel. Sinon, bash retombe à C.

La FAQ de Wooledge bash dit ceci:

Sur les systèmes GNU récents, les variables sont utilisées dans cet ordre. Si LANGUAGE est défini, utilisez-le, sauf si LANG est défini sur C, auquel cas LANGUAGE est ignoré. En outre, certains programmes n’utilisent tout simplement pas LANGUE. Sinon, si LC_ALL est défini, utilisez-le. Sinon, si la variable LC_ * spécifique qui couvre cette utilisation est définie, utilisez-la. (Par exemple, LC_MESSAGES couvre les messages d'erreur.) Sinon, utilisez LANG.

Ainsi, le problème apparent, à la fois en termes de fonctionnement et de documentation, peut être expliqué en regardant la somme totale de toutes les variables de contrôle des paramètres régionaux.

évêque
la source
Si aucune variable_Variable n'est présente et que bash ne se comporte pas comme documenté pour les Cparamètres régionaux, il s'agit d'un bogue.
Schily
1
@bishop: (1) Typo: MVCE devrait être MCVE. (2) Si vous souhaitez que votre exemple soit complet, vous devez ajouter env | grep LANGou echo "$LANG".
G-Man dit 'Réintégrez Monica' le
@schily Une enquête plus poussée m'a convaincu qu'il n'y avait pas de bogue dans la documentation ou le fonctionnement de ce système Linux.
évêque
@ G-Man Merci! J'ai oublié LANG. Avec cet indice, tout est expliqué.
évêque
LANG a été introduit vers 1988 par Sun pour les premières tentatives de localisation, avant que celles-ci ne découvrent qu'une seule variable ne suffit pas. Aujourd'hui, il est utilisé comme solution de secours et LC_ALL est utilisé comme écriture forcée.
Schily
3

Les paramètres régionaux peuvent modifier les caractères correspondants [A-Z]. Utilisation

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

éliminer l'influence. (J'ai utilisé un sous-shell pour localiser le changement).

choroba
la source
Cela ne fonctionne pas, il correspond toujours à toutes les lettres
schily
7
Cela ne fonctionnera pas parce que glob a été fait avant l'exécution de rm. Essayez d' export LC_ALL=Cabord.
jeudi
Désolé, vous avez mal compris la question liée à bash et non à rm.
Schily
@schily: Oui, je me suis trompé, vous devez séparer les déclarations. Vérifiez la mise à jour.
Choroba
2

Comme cela a déjà été dit, il s’agit d’un problème d’ordre de classement.

La plage az peut contenir des lettres majuscules dans certaines langues:

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

La solution correcte depuis bash 4.3 consiste à définir l’option suivante globasciiranges:

shopt -s globasciiranges

faire que bash agisse comme si elle LC_COLLATE=Cavait été définie dans des gammes globales .


la source
-6

Il semble que j'ai trouvé la bonne réponse à ma propre question:

Bash est un bug car il ne gère pas ses propres paramètres régionaux. Donc, définir LC_ * dans un processus bash est sans effet dans ce processus shell.

Si vous définissez LC_COLLATE = C, puis démarrez un autre bash, la suppression fonctionne comme prévu dans le nouveau processus bash.

schily
la source
2
Pas dans mes bashes.
chaos
2
Je ne le répète dans aucune version de bash sur ma machine, il semblerait que vous ne l'ayez pas exportfait correctement.
Chris Down
Donc, vous pensez que quelque chose qui est correctement exporté, de sorte qu'il affecte un nouveau processus bash, n'est pas correctement exporté?
Schily
4
La gestion de l'environnement par Solaris est notoirement déficiente. Je ne serais donc pas surpris que le "bug" de bash soit l'absence d'une solution de contournement spécifique à Solaris.
Hobbs
1
@schily: Avez-vous une citation indiquant qu'il est nécessaire de modifier les variables LC_ * dans un shell pour qu'il mette à jour son propre état de paramètres régionaux? Je penserais exactement le contraire. En particulier pour un shell exécutant un script, le changement de paramètres régionaux à mi-parcours de l'analyse / exécution du script n'aurait même pas un comportement bien défini, car le script est un fichier texte et le "fichier texte" n'a de sens que dans le contexte d'une encodage à un seul caractère.
R ..