¿Por qué [AZ] coincide con letras minúsculas en bash?

42

En todos los shells que conozco, rm [A-Z]*elimina todos los archivos que comienzan con una letra mayúscula, pero con bash esto elimina todos los archivos que comienzan con una letra.

Como este problema existe en Linux y Solaris con bash-3 y bash-4, no puede ser un error causado por un emparejador de patrones con errores en libc o una definición de configuración regional mal configurada.

¿Se pretende este comportamiento extraño y arriesgado o es solo un error que existe sin reparar desde hace muchos años?

astuto
fuente
3
¿Qué da localesalida? No puedo reproducir esto ( touch foo; echo [A-Z]*genera el patrón literal, no "foo", en un directorio vacío).
chepner
44
Teniendo en cuenta cuántas personas han dicho que funciona para ellos, o han mostrado ejemplos de cómo LC_COLLATE afecta esto, tal vez podría editar su pregunta para agregar una sesión de muestra de bash que ilustra exactamente el escenario sobre el que está preguntando. Incluya la versión bash que está utilizando.
Kenster
Si leyó todo el texto aquí, sabría qué versión de bash uso y qué hice desde que ya publiqué la solución a mi pregunta. Permítanme repetir la solución: bash no administra su propia configuración regional, por lo que la configuración LC_COLLATE no cambia nada hasta que comience otro proceso de bash con el nuevo entorno.
schily
1
Consulte también ¿LC_COLLATE (debería) afectar los rangos de caracteres? (pero esa pregunta no era específicamente sobre bash)
Gilles 'SO- deja de ser malvado'
"configurar LC_COLLATE no cambia nada hasta que inicie otro proceso bash con el nuevo entorno". Eso no coincide con el comportamiento que veo con bash-4 en Solaris. Está cambiando el comportamiento en el shell en ejecución. # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Respuestas:

67

Tenga en cuenta que cuando se usan expresiones de rango como [az], se pueden incluir letras del otro caso, dependiendo de la configuración de LC_COLLATE.

LC_COLLATE es una variable que determina el orden de clasificación utilizado al ordenar los resultados de la expansión del nombre de ruta y determina el comportamiento de las expresiones de rango, las clases de equivalencia y las secuencias de clasificación dentro de la expansión del nombre de ruta y la coincidencia de patrones.


Considera lo siguiente:

$ 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

Observe que cuando echo [a-z]se llama al comando , el resultado esperado sería todos los archivos con caracteres en minúscula. Además, con echo [A-Z], se esperarían archivos con caracteres en mayúscula.


Las intercalaciones estándar con configuraciones regionales como las que en_UStienen el siguiente orden:

aAbBcC...xXyYzZ
  • Entre ay z(in [a-z]) están TODAS las letras mayúsculas, excepto Z.
  • Entre Ay Z(in [A-Z]) están TODAS las letras minúsculas, excepto a.

Ver:

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

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

Si cambia la LC_COLLATEvariable a la que Cse ve como se esperaba:

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

Entonces, no es un error , es un problema de cotejo .


En lugar de expresiones de rango, puede usar clases de caracteres definidas POSIX , como uppero lower. Funcionan también con diferentes LC_COLLATEconfiguraciones e incluso con caracteres acentuados :

$ echo [[:lower:]]
a b c x y z à è é
$ echo [[:upper:]]
A B C X Y Z
caos
fuente
Si este comportamiento era controlable por las variables de entorno LC_ *, no lo pregunté. Trabajo en el comité estándar POSIX y sé de cotejar problemas con, por ejemplo, tresto es lo que verifiqué primero.
schily
@schily No puedo reproducir tu problema con un viejo bash-3 o un bash-4; ambos son controlables a través de lo LC_COLLATEcual también se documenta en el manual.
caos
Lo siento, no puedo reproducir lo que crees, pero veo mi propia respuesta ... De las ideas en esta discusión descubrí la razón del problema.
schily
25

[A-Z]en bashcoincide con todos los elementos de clasificación (los caracteres, pero la llamada también es una secuencia de caracteres como Dszen las configuraciones regionales húngaras) que se ordenan después Ay antes Z. En su localidad, cprobablemente clasifique entre B y C.

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

Entonces, co zsería igualado por [A-Z], pero no o 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

En la configuración regional C, el orden sería:

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

Así [A-Z]se correspondería A, B, C, Z, pero no Çy aún así no .

Si desea hacer coincidir las letras mayúsculas (en cualquier secuencia de comandos), puede usar [[:upper:]]en su lugar. No hay una manera integrada de bashhacer coincidir solo las letras mayúsculas en el alfabeto latino (excepto al enumerarlas individualmente).

Si desea que coincida con el Ade Z Inglés letras sin signos diacríticos, puede utilizar cualquiera [A-Z]o [[:upper:]]sino en la Cconfiguración regional (suponiendo que los datos no están codificados en los juegos de caracteres como Big5 o GB18030 que tiene varios personajes cuya codificación contiene la codificación de esas cartas) o lista ellos individualmente ( [ABCDEFGHIJKLMNOPQRSTUVWXYZ]).

Tenga en cuenta que hay alguna variación entre los depósitos.

For zsh, bash -O globasciiranges(opción extrañamente nombrada introducida en bash-4.3), schily-shy yash, [A-Z]coincide con los caracteres cuyo punto de código está entre el de Ay el de Z, por lo que sería equivalente al comportamiento de bashen la configuración regional de C.

Para cenizas, mksh y conchas antiguas, igual que el zshanterior pero limitado a conjuntos de caracteres de un solo byte. Es decir, en un entorno local UTF-8, por ejemplo, [É-Ź]no coincidiría Ó, pero como eso [<c3><89>-<c5><b9>], ¡coincidiría con los valores de bytes 0x89 a 0xc5!

ksh93se comporta como, bashexcepto que trata como rangos de casos especiales cuyos extremos comienzan con letras minúsculas o mayúsculas. En ese caso, solo coincide en elementos de clasificación que se clasifican entre esos extremos, pero que son (o su primer carácter para elementos de clasificación de varios caracteres) también minúsculas (o mayúsculas respectivamente). Por [A-Z]lo tanto , coincidiría con É, pero no con elo eque se clasifica entre Ay Zpero no es mayúscula como Ay Z.

Para fnmatch()patrones (como en find -name '[A-Z]') o expresiones regulares del sistema (como en grep '[A-Z]'), depende del sistema y la configuración regional. Por ejemplo, en un sistema GNU aquí, [A-Z]no coincide xen la en_GB.UTF-8configuración regional, pero sí en la th_TH.UTF-8. No me queda claro qué información utiliza para determinar eso, pero aparentemente se basa en una tabla de búsqueda derivada de los datos de la configuración regional LC_COLLATE ).

POSIX permite todos los comportamientos, ya que POSIX deja el comportamiento de los rangos sin especificar en configuraciones regionales distintas de la configuración regional C. Ahora podemos discutir sobre los beneficios de cada enfoque.

bashEl enfoque tiene mucho sentido ya que con [C-G], queremos los caracteres entre Cy G. Y usar el orden de clasificación del usuario para determinar qué es lo intermedio es el enfoque más lógico.

Ahora, el problema es que rompe las expectativas de muchas personas, especialmente aquellas personas acostumbradas al comportamiento tradicional de pre-Unicode, incluso los días previos a la internacionalización. Si bien desde un usuario normal, que tiene sentido de mayo que [C-I]incluye hcomo la hcarta es entre Cy Iy que [A-g]no incluye Z, es un asunto diferente para las personas de haber tratado con ASCII solamente durante décadas.

Ese bashcomportamiento también es diferente de la [A-Z]coincidencia de rango en otras herramientas de GNU como en las expresiones regulares de GNU (como en grep/ sed...) o fnmatch()como en find -name.

También significa que lo que [A-Z]coincide varía con el entorno, con el sistema operativo y con la versión del sistema operativo. El hecho de que [A-Z]coincida con Á pero no con Ź también es subóptimo.

Para zsh/ yash, utilizamos un orden de clasificación diferente. En lugar de confiar en la noción de orden de caracteres del usuario, utilizamos los valores del código de punto de carácter. Tiene el beneficio de ser fácil de entender, pero desde un punto práctico de pocos, fuera de ASCII, no es muy útil. [A-Z]coincide con las 26 letras mayúsculas del inglés de EE. UU., [0-9]coincide con los dígitos decimales. Hay puntos de código en Unicode que siguen el orden de algunos alfabetos, pero eso no está generalizado y no puede generalizarse, ya que de todos modos las diferentes personas que usan un mismo script no necesariamente están de acuerdo con el orden de las letras.

Para los shells y mksh tradicionales, el guión está roto (ahora que la mayoría de las personas usa caracteres de varios bytes), pero principalmente porque todavía no tienen soporte para varios bytes. Agregar soporte de varios bytes a shells como bashy zshha sido un gran esfuerzo y aún está en curso. yash(un shell japonés) se diseñó inicialmente con soporte de múltiples bytes desde el principio.

El enfoque de ksh93 tiene el beneficio de ser coherente con las expresiones regulares del sistema o fnmatch () (o al menos parece al menos en los sistemas GNU). Allí, no rompe las expectativas de algunas personas, ya [A-Z]que no incluye letras minúsculas, [A-Z]incluye É(y Á, pero no Ź). No es consistente con el orden en sortgeneral strcoll().

Stéphane Chazelas
fuente
1
Si tenía razón, esto podría controlarse mediante las variables LC_ *. Parece que hay una razón diferente.
schily
1
@cuonglm, más como mksh(ambos derivados de pdksh). posh -c $'case Ó in [É-Ź]) echo yes; esac'no devuelve nada
Stéphane Chazelas
2
@schily, menciono sortporque los bashglobos se basan en el orden de clasificación de caracteres. Actualmente no tengo acceso a una versión tan antigua de bash, pero puedo verificar más tarde. ¿Fue diferente entonces?
Stéphane Chazelas
1
Permítanme mencionar nuevamente: zsh, POSIX-ksh88, ksh93t + Bourne Shell, todos se comportan de la misma manera que esperaba. Bash es el único shell que se comporta de manera diferente y bash no es controlable a través de la configuración regional en este caso.
schily
2
@schily, tenga en cuenta que \xFFexiste el byte 0xFF, no el carácter U + 00FF (en ÿsí mismo codificado como 0xC3 0xBF). \xFFsolo no forma un carácter válido, así que no puedo ver por qué debería coincidir con él [É-Ź].
Stéphane Chazelas
9

Está destinado y documentado en la bashdocumentación, sección de coincidencia de patrones . La expresión de rango [X-Y]incluirá cualquier carácter entre Xy Yusando la secuencia de clasificación y el conjunto de caracteres de la configuración regional actual:

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

Puede ver, bordenado entre Ay Zen la en_US.utf8configuración regional.

Tiene algunas opciones para evitar este comportamiento:

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

o habilitar globasciiranges(con bash 4.3 y superior):

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

Observé este comportamiento en una nueva instancia de Amazon EC2. Como el OP no ofreció un MCVE , publicaré uno:

$ 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

Entonces, no tener mi LC_*set leads bash 4.1.2 (1) -lanzamiento en Linux para producir un comportamiento aparentemente extraño. Puedo alternar de manera confiable el comportamiento extraño configurando y desarmando las variables locales respectivas. Como era de esperar, este comportamiento parece consistente a través de la exportación:

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

Mientras veo que bash se comporta como Stéphane "Shellshock" Chazelas respondió , creo que la documentación de bash sobre la coincidencia de patrones tiene errores:

Por ejemplo, en la configuración regional C predeterminada , '[a-dx-z]' es equivalente a '[abcdxyz]'

Leí esa oración (el énfasis es mío) como "si las variables locales relevantes no están establecidas, entonces bash pasará a la configuración regional C". Bash no parece estar haciendo eso. En cambio, parece estar predeterminado en un entorno local donde los caracteres se ordenan en orden de diccionario con plegado diacrítico:

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

Creo que sería bueno para bash documentar cómo se comportará cuando LC_*(específicamente LC_CTYPEy LC_COLLATE) no estén definidos. Pero mientras tanto, compartiré algo de sabiduría :

... debe tener mucho cuidado con [rangos de caracteres] porque no producirán los resultados esperados a menos que estén configurados correctamente. Por ahora, debe evitar usarlos y usar clases de caracteres en su lugar.

y

Si realmente es correcto y / o está haciendo scripts para un entorno de múltiples ubicaciones, probablemente sea mejor asegurarse de saber cuáles son sus variables de configuración regional cuando está haciendo coincidir archivos, o asegurarse de que está codificando en un forma completamente genérica


Actualización Basado en el comentario de @ G-Man, echemos un vistazo más profundo a lo que está sucediendo:

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

Ah, ja! Eso explica la recopilación vista anteriormente. Eliminemos todas las variables locales:

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

Aquí vamos. Ahora bash opera de manera consistente con respecto a la documentación en este sistema Linux. Si ninguna de las variables de entorno local se fijan ( LANGUAGE, LANG, LC_COLLATE, LC_CTYPE, LC_ALL, etc.) entonces Bash utiliza aquellos de acuerdo con su manual. De lo contrario, bash vuelve a caer a C.

Las preguntas frecuentes de Wooledge bash tienen esto que decir:

En sistemas GNU recientes, las variables se usan en este orden. Si LANGUAGE está configurado, úselo, a menos que LANGUAGE esté configurado en C, en cuyo caso se ignora LANGUAGE. Además, algunos programas simplemente no usan IDIOMA en absoluto. De lo contrario, si se establece LC_ALL, úselo. De lo contrario, si se establece la variable LC_ * específica que cubre este uso, úsela. (Por ejemplo, LC_MESSAGES cubre mensajes de error). De lo contrario, use LANG.

Por lo tanto, el problema aparente, tanto en la operación como en la documentación, puede explicarse observando la suma total de todas las variables de manejo locales.

obispo
fuente
Si no hay una variable LC_variable y bash no se comporta según lo documentado para la Cconfiguración regional, esto es un error.
schily
1
@bishop: (1) Typo: MVCE debería ser MCVE. (2) Si desea que su ejemplo esté completo, debe agregar env | grep LANGo echo "$LANG".
G-Man dice 'Restablecer a Monica' el
@schily Una investigación adicional me convenció de que no hay ningún error en la documentación u operación en este sistema Linux.
obispo
@ G-Man Gracias! Me había olvidado de LANG. Con esa pista, todo está explicado.
obispo
LANG fue introducido alrededor de 1988 por Sun para los primeros intentos de localización, antes de que descubrieran que una sola variable no es suficiente. Hoy se usaba como reserva y LC_ALL se usa como sobrescritura forzada.
schily
3

La configuración regional puede cambiar los caracteres que coinciden [A-Z]. Utilizar

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

para eliminar la influencia. (Usé una subshell para localizar el cambio).

choroba
fuente
Esto no funciona, todavía coincide con todas las letras
schily
77
Esto no funcionará porque glob se realizó antes de ejecutar rm. Intenta export LC_ALL=Cprimero.
cuonglm
Lo sentimos, no entiendes la pregunta relacionada con bash y no con rm.
schily
@schily: Sí, me equivoqué, tienes que separar las declaraciones. Revisa la actualización.
choroba
2

Como ya se ha dicho, este es un problema de "orden de clasificación".

El rango az puede contener letras mayúsculas en algunas configuraciones regionales:

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

La solución correcta desde bash 4.3 es establecer la opción globasciiranges:

shopt -s globasciiranges

hacer que bash actúe como si LC_COLLATE=Cse hubiera establecido en rangos globales .


fuente
-6

Parece que encontré la respuesta correcta a mi propia pregunta:

Bash tiene errores, ya que no gestiona su propia configuración regional. Por lo tanto, configurar LC_ * en un proceso bash no tiene efecto en ese proceso de shell.

Si configura LC_COLLATE = C y luego inicia otra bash, el globing funciona como se espera en el nuevo proceso de bash.

astuto
fuente
2
No en ninguno de mis golpes.
caos
2
No reproduzco esto en ninguna versión de bash en mi máquina, parece que no lo hiciste exportcorrectamente.
Chris Down
¿Entonces cree que algo que se exporta correctamente, de modo que afecta a un nuevo proceso de bash, no se exporta correctamente?
schily
44
El manejo del entorno por parte de Solaris es notoriamente deficiente, por lo que no me sorprendería si el "error" en bash fuera la falta de una solución específica para Solaris.
hobbs
1
@schily: ¿Tiene una cita sobre dónde se requiere cambiar las variables LC_ * dentro de un shell para que actualice su propio estado de configuración regional? Yo pensaría exactamente lo contrario. En particular, para un shell que ejecuta un script, cambiar la configuración regional a mitad de camino mediante el análisis / ejecución del script ni siquiera tendría un comportamiento bien definido, ya que el script es un archivo de texto y el "archivo de texto" solo es significativo dentro del contexto de un codificación de un solo carácter.
R ..