Perché [AZ] corrisponde a lettere minuscole in bash?


42

In tutte le shell di cui sono a conoscenza, rm [A-Z]*rimuove tutti i file che iniziano con una lettera maiuscola, ma con bash ciò rimuove tutti i file che iniziano con una lettera.

Dato che questo problema esiste su Linux e Solaris con bash-3 e bash-4, non può essere un bug causato da un bugger pattern matcher in libc o una definizione di locale configurata per errore.

È previsto questo comportamento strano e rischioso o è solo un bug che non è stato risolto da molti anni?


3
Cosa produce locale? Non riesco a riprodurlo ( touch foo; echo [A-Z]*genera il modello letterale, non "pippo", in una directory altrimenti vuota).
Chepner,

4
Considerando quante persone hanno detto che funziona per loro o hanno mostrato esempi di come LC_COLLATE influisce su questo, forse potresti modificare la tua domanda per aggiungere una sessione bash di esempio che illustra esattamente lo scenario che stai chiedendo. Includi la versione bash che stai utilizzando.
Kenster,

Se leggessi tutto il testo qui sapresti quale versione bash che uso e cosa ho fatto da quando ho già pubblicato la soluzione alla mia domanda. Consentitemi di ripetere la soluzione: bash non gestisce le proprie impostazioni locali in modo che l'impostazione LC_COLLATE non cambi nulla fino a quando non si avvia un altro processo bash con il nuovo ambiente.
schily,


"L'impostazione di LC_COLLATE non cambia nulla finché non si avvia un altro processo bash con il nuovo ambiente." Questo non corrisponde al comportamento che vedo con bash-4 su Solaris. Sta cambiando il comportamento nella shell in esecuzione. # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Risposte:


67

Si noti che quando si usano espressioni di intervallo come [az], è possibile includere lettere dell'altro caso, a seconda dell'impostazione di LC_COLLATE.

LC_COLLATE è una variabile che determina l'ordine di confronto utilizzato durante l'ordinamento dei risultati dell'espansione del percorso e determina il comportamento delle espressioni di intervallo, delle classi di equivalenza e delle sequenze di confronto all'interno dell'espansione del percorso e della corrispondenza del modello.


Considera quanto segue:

$ 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

Si noti quando echo [a-z]viene chiamato il comando , l'output previsto sarebbe tutti i file con caratteri minuscoli. Inoltre, con echo [A-Z], ci si aspetterebbe file con caratteri maiuscoli.


Le regole di confronto standard con locali come en_USil seguente ordine:

aAbBcC...xXyYzZ
  • Tra ae z(in [a-z]) sono TUTTE le lettere maiuscole, ad eccezione di Z.
  • Tra Ae Z(in [A-Z]) sono TUTTE le lettere minuscole, ad eccezione di a.

Vedere:

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

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

Se si modifica la LC_COLLATEvariabile in Ccome appare come previsto:

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

Quindi, non è un bug , è un problema di confronto .


Invece di espressioni di intervallo è possibile utilizzare classi di caratteri definite POSIX , come uppero lower. Funzionano anche con diverse LC_COLLATEconfigurazioni e persino con caratteri accentati :

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

Se questo comportamento era controllabile dalle variabili d'ambiente LC_ *, non l'ho chiesto. Lavoro nel comitato standard POSIX e sono a conoscenza di problemi con ad es. trQuindi questo è ciò che ho verificato per primo.
schily,

@schily Non riesco a riprodurre il tuo problema con un vecchio bash-3 o un bash-4; entrambi sono controllabili tramite il LC_COLLATEquale è anche documentato nel manuale.
caos,

Spiacenti, non riesco a riprodurre ciò in cui credi, ma vedi la mia risposta ... Dalle idee di questa discussione ho scoperto il motivo del problema.
schily

25

[A-Z]nelle bashpartite tutti gli elementi di confronto (i caratteri ma chiamano anche sequenza di caratteri come Dsznelle localizzazioni ungheresi) che ordinano dopo Ae ordinano prima Z. Nel tuo locale, cprobabilmente ordina tra-B e C.

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

Quindi co zsarebbe abbinato da [A-Z], ma non 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

Nella locale C, l'ordine sarebbe:

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

Così [A-Z]sarebbe partita A, B, C, Z, ma non Çe ancora non .

Se si desidera abbinare lettere maiuscole (in qualsiasi script), è possibile utilizzare [[:upper:]]invece. Non esiste un modo integrato bashper abbinare solo lettere maiuscole nello script latino (tranne elencandole singolarmente).

Se si desidera far corrispondere la Adi Z inglese lettere senza segni diacritici, è possibile utilizzare [A-Z]o [[:upper:]], ma nel Clocale (supponendo che i dati non sono codificati in set di caratteri come BIG5 o GB18030 che ha diversi personaggi le cui codifica contiene la codifica di quelle lettere) o lista individualmente ( [ABCDEFGHIJKLMNOPQRSTUVWXYZ]).

Nota che c'è qualche variazione tra le shell.

For zsh, bash -O globasciiranges(opzione stranamente nominata introdotta in bash-4.3), schily-she yash, [A-Z]corrisponde ai caratteri il cui punto di codice è compreso tra quello di Ae quello di Z, quindi sarebbe equivalente al comportamento di bashnella locale C.

Per ash, mksh e conchiglie antiche, come zshsopra ma limitate ai set di caratteri a byte singolo. Cioè, ad esempio in una locale UTF-8, [É-Ź]non corrisponderebbe Ó, ma dato che [<c3><89>-<c5><b9>], corrisponderebbe a valori di byte da 0x89 a 0xc5!

ksh93si comporta come bashse non fosse trattato come intervalli di casi speciali le cui estremità iniziano entrambe con lettere minuscole o lettere maiuscole. In tal caso, corrisponde solo agli elementi di fascicolazione che si ordinano tra quelle estremità, ma che sono (o il loro primo carattere per gli elementi di fascicolazione multi-carattere) anche in minuscolo (o maiuscolo rispettivamente). Quindi [A-Z]ci sarebbe corrispondenza su É, ma non su ecome efa l'ordinamento tra Ae Zma non è maiuscolo come Ae Z.

Per i fnmatch()pattern (come in find -name '[A-Z]') o le espressioni regolari di sistema (come in grep '[A-Z]'), dipende dal sistema e dalle impostazioni locali. Ad esempio, su un sistema GNU qui, [A-Z]non corrisponde xnella en_GB.UTF-8locale, ma lo fa in th_TH.UTF-8quello. Non mi è chiaro quali informazioni utilizzi per determinarlo, ma apparentemente si basa su una tabella di ricerca derivata da dati locali LC_COLLATE ).

Tutti i comportamenti sono consentiti da POSIX poiché POSIX lascia il comportamento degli intervalli non specificato in locali diversi dalla locale C. Ora possiamo discutere dei vantaggi di ciascun approccio.

bashL'approccio ha molto senso, come nel caso [C-G], vogliamo i personaggi tra Ce G. E l'utilizzo dell'ordinamento dell'utente per ciò che determina ciò che sta nel mezzo è l'approccio più logico.

Ora, il problema è che rompe le aspettative di molte persone, in particolare quelle abituate al comportamento tradizionale del pre-Unicode, anche nei giorni pre-internazionalizzazione. Mentre da un utente normale, può sembrare che [C-I]includa hla hlettera tra Ce Ie che [A-g]non includa Z, è una questione diversa per le persone che hanno a che fare con ASCII solo per decenni.

Tale bashcomportamento è anche diverso dalla [A-Z]corrispondenza dell'intervallo in altri strumenti GNU come nelle espressioni regolari GNU (come in grep/ sed...) o fnmatch()come in find -name.

Significa anche che ciò che [A-Z]corrisponde varia con l'ambiente, con il sistema operativo e con la versione del sistema operativo. Anche il fatto che [A-Z]corrisponda ad Á ma non a Ź non è ottimale.

Per zsh/ yash, utilizziamo un diverso ordinamento. Invece di fare affidamento sulla nozione dell'utente di ordine dei caratteri, utilizziamo i valori del codice punto carattere. Ciò ha il vantaggio di essere facile da capire, ma dal punto di vista pratico di pochi, al di fuori di ASCII, non è molto utile. [A-Z]corrisponde alle 26 lettere maiuscole inglese-americano, [0-9]corrisponde alle cifre decimali. Ci sono punti di codice in Unicode che seguono l'ordine di alcuni alfabeti, ma questo non è generalizzato e non può essere generalizzato poiché comunque persone diverse che usano uno stesso script non concordano necessariamente sull'ordine delle lettere.

Per shell tradizionali e mksh, dash, è rotto (ora che la maggior parte delle persone usa caratteri multi-byte), ma principalmente perché non hanno ancora il supporto multi-byte. L'aggiunta del supporto multi-byte a shell come bashed zshè stata un grande sforzo ed è ancora in corso. yash(una shell giapponese) è stata inizialmente progettata con supporto multi-byte sin dall'inizio.

L'approccio di ksh93 ha il vantaggio di essere coerente con le espressioni regolari o fnmatch del sistema () (o almeno sembra almeno sui sistemi GNU). Lì, non infrange le aspettative di alcune persone in quanto [A-Z]non include lettere minuscole, [A-Z]include É(e Á, ma non Ź). Non è coerente con sorto in generale strcoll()ordine.


1
Se avessi ragione, questo potrebbe essere controllato tramite variabili LC_ *. Sembra esserci una ragione diversa.
schily,

1
@cuonglm, più simile mksh(entrambi derivati ​​da pdksh). posh -c $'case Ó in [É-Ź]) echo yes; esac'non restituisce nulla.
Stéphane Chazelas,

2
@schily, menziono sortperché i bashglobs si basano sull'ordinamento dei personaggi. Al momento non ho accesso a una versione così vecchia di bash, ma posso controllare in seguito. Allora era diverso?
Stéphane Chazelas,

1
Lasciatemi menzionare di nuovo: zsh, POSIX-ksh88, ksh93t + Bourne Shell, si comportano tutti come mi aspetto. Bash è l'unica shell che si comporta in modo diverso e in questo caso bash non è controllabile tramite la localizzazione.
schily,

2
@schily, nota che \xFFesiste il byte 0xFF, non il carattere U + 00FF ( ÿcodificato in sé come 0xC3 0xBF). \xFFda solo non costituisce un personaggio valido, quindi non riesco a capire perché debba essere abbinato [É-Ź].
Stéphane Chazelas,

9

È previsto e documentato nella bashdocumentazione, sezione di corrispondenza dei motivi . L'espressione di intervallo [X-Y]includerà tutti i caratteri tra Xe Yusando la sequenza di fascicolazione e il set di caratteri della locale corrente:

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

Puoi vedere, bordinati tra Ae Zin en_US.utf8locale.

Hai alcune scelte per prevenire questo comportamento:

# 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 abilita globasciiranges(con bash 4.3 e versioni successive):

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

6

Ho osservato questo comportamento su una nuova istanza di Amazon EC2. Poiché l'OP non offriva un MCVE , ne posterò 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

Quindi, non avere il mio LC_*set porta su BASH 4.1.2 (1)-release su Linux per produrre un comportamento apparentemente strano. Posso abilitare / disabilitare il comportamento strano impostando e disinserendo le rispettive variabili locali. Non sorprende che questo comportamento appaia coerente attraverso l'esportazione:

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

Mentre vedo bash comportarsi come Stéphane "Shellshock" Chazelas ha risposto , penso che la documentazione di bash sul pattern matching sia buggy:

Ad esempio, nella locale C predefinita , '[a-dx-z]' è equivalente a '[abcdxyz]'

Ho letto quella frase (enfasi sulla mia) come "se le variabili locali rilevanti non sono impostate, allora bash tornerà alla C locale". Bash non sembra farlo. Invece sembra essere l'impostazione predefinita di una locale in cui i caratteri sono ordinati in ordine di dizionario con piegatura diacritica:

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

Penso che sarebbe utile per Bash documentare come si comporterà quando LC_*(in particolare LC_CTYPEe LC_COLLATE) non sono definiti. Ma nel frattempo, condividerò un po 'di saggezza :

... devi stare molto attento con [intervalli di caratteri] perché non produrranno i risultati attesi se non configurati correttamente. Per ora, dovresti evitare di usarli e utilizzare invece le classi di caratteri.

e

Se sei davvero corretto e / o stai eseguendo script per un ambiente multi-locale, probabilmente è meglio accertarti di sapere quali sono le variabili della tua locale quando stai abbinando i file, o per essere sicuro che stai codificando in un modo completamente generico.


Aggiornamento Basato sul commento di @ G-Man, esaminiamo più in dettaglio ciò che sta accadendo:

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

Ah ah! Questo spiega la raccolta vista in precedenza. Rimuoviamo tutte le variabili locali:

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

Eccoci. Ora bash funziona in modo coerente rispetto alla documentazione su questo sistema Linux. Se una qualsiasi delle variabili di localizzazione sono impostati ( LANGUAGE, LANG, LC_COLLATE, LC_CTYPE, LC_ALL, ecc), allora Bash utilizza quelle secondo il relativo manuale. Altrimenti, bash ritorna a C.

Le FAQ di Wooledge bash hanno questo da dire:

Sui recenti sistemi GNU, le variabili sono utilizzate in questo ordine. Se LANGUAGE è impostato, usalo, a meno che LANG sia impostato su C, nel qual caso LANGUAGE viene ignorato. Inoltre, alcuni programmi semplicemente non usano affatto LANGUAGE. Altrimenti, se è impostato LC_ALL, usalo. Altrimenti, se è impostata la variabile LC_ * specifica che copre questo utilizzo, usala. (Ad esempio, LC_MESSAGES copre i messaggi di errore.) In caso contrario, utilizzare LANG.

Quindi il problema apparente, sia in funzionamento che in documentazione, può essere spiegato osservando la somma totale di tutte le variabili guida locali.


Se non è presente alcuna variabile LC e bash non si comporta come documentato per la Clocale, si tratta di un bug.
schily

1
@bishop: (1) Errore di battitura: MVCE dovrebbe essere MCVE. (2) Se vuoi che il tuo esempio sia completo, dovresti aggiungere env | grep LANGo echo "$LANG".
G-Man dice "Reinstate Monica" il

@schily Ulteriori indagini mi hanno convinto che non ci sono bug nella documentazione o nelle operazioni su questo sistema Linux.
vescovo,

@ G-Man Grazie! Me ne sono dimenticato LANG. Con quel suggerimento, tutto è spiegato.
vescovo,

LANG fu introdotto intorno al 1988 da Sun per i primi tentativi di localizzazione, prima che scoprissero che una singola variabile non era sufficiente. Oggi è stato utilizzato come fallback e LC_ALL è utilizzato come sovrascrittura forzata.
schily,

3

Le impostazioni internazionali possono modificare i caratteri corrispondenti [A-Z]. Uso

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

per eliminare l'influenza. (Ho usato una subshell per localizzare la modifica).


Non funziona, corrisponde ancora a tutte le lettere
schily,

7
Questo non funzionerà perché glob è stato eseguito prima dell'esecuzione di rm. Prova export LC_ALL=Cprima.
cuonglm,

Siamo spiacenti, hai frainteso la domanda relativa a bash e non a rm.
schily

@schily: Sì, ho sbagliato, devi separare le dichiarazioni. Controlla l'aggiornamento.
Choroba,

2

Come è già stato detto, si tratta di un problema di "ordine di fascicolazione".

L'intervallo az può contenere lettere maiuscole in alcuni locali:

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

La soluzione corretta da bash 4.3 è impostare l'opzione globasciiranges:

shopt -s globasciiranges

per fare in modo che bash si comporti come se LC_COLLATE=Cfosse stato impostato in intervalli globali .


-6

Sembra che ho trovato la risposta giusta alla mia domanda:

Bash è difettoso in quanto non gestisce le proprie impostazioni locali. Quindi impostare LC_ * in un processo bash non ha alcun effetto in quel processo di shell.

Se si imposta LC_COLLATE = C e quindi si avvia un altro bash, il globbing funziona come previsto nel nuovo processo bash.


2
Non in nessuno dei miei colpi.
caos,

2
Non lo ripeto in nessuna versione di bash sulla mia macchina, sembra che tu non l'abbia exportfatto correttamente.
Chris Down,

Quindi credi che qualcosa che sia correttamente esportato, in modo che influisca su un nuovo processo bash non sia correttamente esportato?
schily,

4
La gestione dell'ambiente da parte di Solaris è notoriamente carente, quindi non sarei sorpreso se il "bug" in bash fosse la mancanza di una soluzione specifica per Solaris.
Hobbs,

1
@schily: hai una citazione per cui è necessario cambiare le variabili LC_ * all'interno di una shell per far sì che aggiorni il suo stato locale? Penserei esattamente il contrario. In particolare per una shell che esegue uno script, la modifica della locale a metà strada attraverso l'analisi / esecuzione dello script non avrebbe nemmeno un comportamento ben definito, poiché lo script è un file di testo e "file di testo" è significativo solo nel contesto di un codifica a carattere singolo.
R ..
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.