[A-Z]
in bash
matches all collating elements (characters but call also be sequence of characters like Dsz
in Hungarian locales) that sort after A
and sort before Z
. In your locale, c
probably sorts in-between B and C.
$ printf '%s\n' A a á b B c C Ç z Z Ẑ | sort
a
A
á
b
B
c
C
Ç
z
Z
Ẑ
So c
or z
would be matched by [A-Z]
, but not Ẑ
or 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 the C locale, the order would be:
$ 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]
would match A
, B
, C
, Z
, but not Ç
and still not Ẑ
.
If you want to match on upper-case letters (in any script), you can use [[:upper:]]
instead. There's no builtin way in bash
to only match uppercase letters in the latin script (except by listing them individually).
If you want to match the A
to Z
English letters without diacritics, you can either use [A-Z]
or [[:upper:]]
but in the C
locale (assuming the data is not encoded in character sets like BIG5 or GB18030 which has several characters whose encoding contains the encoding of those letters) or list them individually ([ABCDEFGHIJKLMNOPQRSTUVWXYZ]
).
Note that there is some variation between shells.
For zsh
, bash -O globasciiranges
(strangely named option introduced in bash-4.3), schily-sh
and yash
, [A-Z]
matches on the characters whose code point is between that of A
and that of Z
, so would be equivalent to the behaviour of bash
in the C locale.
For ash, mksh and ancient shells, same as zsh
above but limited to single-byte charsets. That is, in a UTF-8 locale for instance, [É-Ź]
would not match on Ó
, but since that's [<c3><89>-<c5><b9>]
, that would match on byte values 0x89 to 0xc5!
ksh93
behaves like bash
except that it treats as special cases ranges whose ends both start with lowercase letters or uppercase letters. In that case, it only matches on collating elements that sort between those ends, but that are (or their first character for multi-character collating elements) also lowercase (or uppercase respectively). So [A-Z]
there would match on É
, but not on e
as e
does sort between A
and Z
but is not uppercase like A
and Z
.
For fnmatch()
patterns (as in find -name '[A-Z]'
) or system regular expressions (as in grep '[A-Z]'
), it depends on the system and locale. For instance, on a GNU system here, [A-Z]
doesn't match on x
in the en_GB.UTF-8
locale, but it does in the th_TH.UTF-8
one. It's unclear to me what information it uses to determine that, but it is apparently based on a lookup table derived from LC_COLLATE locale data).
All behaviours are allowed by POSIX as POSIX leaves the behaviour of ranges unspecified in locales other than the C locale. Now we can argue over the benefits of each approach.
bash
's approach makes a lot of sense as with [C-G]
, we want the characters in between C
and G
. And using the user's sort order for what determines what's in-between is the most logical approach.
Now, the problem is that it breaks the expectations of a lot of people, especially those people used to the traditional behaviour of pre-Unicode, even pre-internationalisation days. While from a normal user, it makes may sense that [C-I]
includes h
as the h
letter is between C
and I
and that [A-g]
does not include Z
, it's a different matter for people having dealt with ASCII only for decades.
That bash
behaviour is also different from the [A-Z]
range matching in other GNU tools like in GNU regular expressions (as in grep
/sed
...) or fnmatch()
as in find -name
.
It also means that what [A-Z]
matches varies with the environment, with the OS and with the version of the OS. The fact that [A-Z]
matches Á but not Ź is also suboptimal.
For zsh
/yash
, we use a different sorting order. Instead of relying on the user's notion of character order, we use the character point code values. That has the benefit of being easy to understand, but from a practical point of few, outside of ASCII, it is not very useful. [A-Z]
matches the 26 US-english upper-case letters, [0-9]
matches decimal digits. There are code points in Unicode that follow the order of some alphabets but that's not generalised and can't be generalised as anyway different people using a same script do not necessarily agree on the order of letters.
For traditional shells and mksh, dash, it's broken (now that most people use multi-byte characters), but primarily because they don't have multi-byte support yet. Adding multi-byte support to shells like bash
and zsh
has been a huge effort and is still ongoing. yash
(a Japanese shell) was initially designed with multi-byte support from the start.
ksh93's approach has the benefit to be consistent with the system's regular expressions or fnmatch() (or at least appears to at least on GNU systems). There, it doesn't break some people's expectation as [A-Z]
doesn't include lower case letters, [A-Z]
includes É
(and Á, but not Ź). It's not consistent with sort
or generally strcoll()
order.
locale
output? I cannot reproduce this (touch foo; echo [A-Z]*
outputs the literal pattern, not "foo", in an otherwise empty directory).# echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*
A b B z Z A B Z