Bash Globbing not as expected

bashpatternswildcards

This is a homework question:

Match all filenames with 2 or more characters that start with a lower case letter, but do not end with an upper case letter.

I do not understand why my solution is not working.

So I executed the below:

touch aa
touch ha
touch ah
touch hh
touch a123e
touch hX
touch Ax

ls [a-z]*[!A-Z]

Output:

aa  ha

My question: Why did it not match "ah", "hh", or "a123e"?

Best Answer

This is a locale problem. In your locale, [A-Z] expands to something like [AbBcZ...zZ] (plus probably others like accented characters), therefore [^A-Z] actually means "files that end with a" in your example (and only in your example).

If you want to avoid such a surprise, one way is to set LC_COLLATE=C since the collation is the part of your locale settings that is responsible of the sorting order. Also, empty LC_ALL if it is set, as it would take precedence.

$ ls [a-z]*[^A-Z]
aa  ha

$ ( LC_ALL=; LC_COLLATE=C; ls [a-z]*[^A-Z] )
a123e  aa  ah  ha  hh

Or, better, it's probably preferable to not change your locale settings and use the appropriate classes: [:lower:] instead of [a-z] and [:upper:] instead of [A-Z].

$ ls [[:lower:]]*[^[:upper:]]
a123e  aa  ah  ha  hh

Or use bash's globasciiranges option:

$ shopt -s globasciiranges
$ ls [a-z]*[^A-Z]
a123e  aa  ah  ha  hh

$ shopt -u globasciiranges
$ ls [a-z]*[^A-Z]
aa  ha