在我知道的所有 shell 中,rm [A-Z]*
都会删除所有以大写字母开头的文件,但在 bash 中,这会删除所有以字母开头的文件。
由于此问题存在于使用 bash-3 和 bash-4 的 Linux 和 Solaris 上,因此它不可能是由 libc 中有缺陷的模式匹配器或错误配置的区域设置定义引起的错误。
这种奇怪且危险的行为是有意为之还是只是多年来存在未修复的错误?
答案1
请注意,当使用范围表达式(如 [az])时,可能会包含其他大小写的字母,具体取决于 LC_COLLATE 的设置。
LC_COLLATE
是一个变量,它确定对路径名扩展结果进行排序时使用的排序规则,并确定路径名扩展和模式匹配中范围表达式、等价类和排序序列的行为。
考虑以下:
$ 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
请注意,调用该命令时echo [a-z]
,预期输出将是所有带有小写字符的文件。此外,对于echo [A-Z]
, 文件也应该包含大写字符。
与区域设置的标准排序规则en_US
具有以下顺序:
aAbBcC...xXyYzZ
a
和z
(in )之间[a-z]
均为大写字母,除了Z
。A
和Z
(in )之间[A-Z]
都是小写字母,除了a
。
看:
aAbBcC[...]xXyYzZ
| |
from a to z
aAbBcC[...]xXyYzZ
| |
from A to Z
如果您将LC_COLLATE
变量更改为C
它,它看起来如预期的那样:
$ export LC_COLLATE=C
$ echo [a-z]
a b c x y z
$ echo [A-Z]
A B C X Y Z
所以就是不是一个错误, 它是整理问题。
您可以使用 POSIX 定义的范围表达式来代替范围表达式字符类,例如upper
或lower
。他们还与不同的LC_COLLATE
配置,甚至可以使用重音字符:
$ echo [[:lower:]]
a b c x y z à è é
$ echo [[:upper:]]
A B C X Y Z
答案2
[A-Z]
in匹配所有排序后和排序前bash
的排序元素(字符,但也称为字符序列,如匈牙利语言环境中的字符) 。在您所在的区域设置中,可能介于 B 和 C 之间。Dsz
A
Z
c
$ printf '%s\n' A a á b B c C Ç z Z Ẑ | sort
a
A
á
b
B
c
C
Ç
z
Z
Ẑ
所以c
orz
会被 匹配[A-Z]
,但Ẑ
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
在 C 语言环境中,顺序为:
$ printf '%s\n' A a á b B c C Ç z Z Ẑ | LC_COLLATE=C sort
A
B
C
Z
a
b
c
z
Ç
á
Ẑ
因此[A-Z]
会匹配A
, B
, C
, Z
, 但不匹配Ç
,仍然不匹配Ẑ
。
如果您想匹配大写字母(在任何脚本中),您可以使用[[:upper:]]
。没有内置的方法bash
可以只匹配大写字母拉丁脚本(除非单独列出它们)。
如果你想匹配A
到Z
英语没有变音符号的字母,您可以使用[A-Z]
或[[:upper:]]
但在C
(假设数据不是以 BIG5 或 GB18030 等字符集编码的,其中有几个字符的编码包含这些字母的编码)或单独列出它们([ABCDEFGHIJKLMNOPQRSTUVWXYZ]
)。
请注意,外壳之间存在一些差异。
对于zsh
, bash -O globasciiranges
(bash-4.3 中引入的奇怪命名选项)schily-sh
和yash
,[A-Z]
匹配代码点介于A
和 之间的字符Z
,因此相当于bash
C 语言环境中 的行为。
对于 ash、mksh 和古老的 shell,与zsh
上面相同,但仅限于单字节字符集。也就是说,例如,在 UTF-8 语言环境中,[É-Ź]
不会匹配Ó
,但因为那是[<c3><89>-<c5><b9>]
,所以它将匹配字节值 0x89 到 0xc5!
ksh93
表现得像bash
,只是它将视为结尾均以小写字母或大写字母开头的特殊情况范围。在这种情况下,它仅匹配在这些末端之间排序的整理元素,但它们(或多字符整理元素的第一个字符)还小写(或分别为大写)。因此,[A-Z]
会匹配 on É
,但不会匹配 on ,e
就像在 ande
之间排序一样A
,Z
但不是像A
and一样大写Z
。
对于fnmatch()
模式(如find -name '[A-Z]'
)或系统正则表达式(如grep '[A-Z]'
),它取决于系统和区域设置。例如,在此处的 GNU 系统上,在语言环境中[A-Z]
与 on 不匹配,但在x
en_GB.UTF-8
th_TH.UTF-8
。我不清楚它使用什么信息来确定这一点,但是它显然基于从 LC_COLLATE 语言环境数据派生的查找表)。
POSIX 允许所有行为,因为 POSIX 保留除 C 语言环境之外的语言环境中未指定范围的行为。现在我们可以争论每种方法的好处。
bash
的方法很有意义,因为我们想要和[C-G]
之间的字符。并使用用户的排序顺序来决定什么C
G
中间是最合乎逻辑的方法。
现在的问题是,它打破了很多人的期望,尤其是那些习惯了 Unicode 之前、甚至国际化之前的传统行为的人。虽然对于普通用户来说,[C-I]
包含可能是有意义的,h
因为h
字母位于C
和之间,I
而不[A-g]
包含Z
,但对于仅处理 ASCII 数十年的人来说这是另一回事。
该bash
行为也不同于其他 GNU 工具中的范围匹配,[A-Z]
例如 GNU 正则表达式(如grep
/ sed
...)或fnmatch()
.find -name
这也意味着[A-Z]
匹配的内容会因环境、操作系统和操作系统版本而异。匹配 Á 但不匹配 Ź的事实[A-Z]
也是次优的。
对于zsh
/ yash
,我们使用不同的排序顺序。我们不依赖用户的字符顺序概念,而是使用字符点代码值。这样做的好处是易于理解,但从很少的实际角度来看,除了 ASCII 之外,它并不是很有用。[A-Z]
匹配 26 个美国英语大写字母,[0-9]
匹配十进制数字。 Unicode 中有一些代码点遵循某些字母的顺序,但这不是通用的,也不能通用,因为使用相同脚本的不同人不一定就字母顺序达成一致。
对于传统的 shell 和 mksh、dash,它已经被破坏了(现在大多数人都使用多字节字符),但主要是因为它们还没有多字节支持。向bash
和等 shell 添加多字节支持zsh
已经付出了巨大的努力,并且仍在进行中。yash
(日语 shell)最初设计时从一开始就支持多字节。
ksh93 的方法的优点是与系统的正则表达式或 fnmatch() 保持一致(或者至少在 GNU 系统上看起来如此)。在那里,它并没有打破一些人的期望,因为[A-Z]
不包含小写字母,[A-Z]
包含É
(和 Á,但不包含 Ź)。它不符合sort
或一般strcoll()
顺序。
答案3
它的意图并记录在bash
文档中,模式匹配部分。范围表达式[X-Y]
将包含介于当前区域设置的整理序列和字符集之间X
并Y
使用当前区域设置的整理序列和字符集的任何字符:
LC_ALL=en_US.utf8 bash -c 'case b in [A-Z]) echo yes; esac'
yes
您可以看到,在区域设置之间和区域设置中b
排序。A
Z
en_US.utf8
您可以通过一些选择来防止这种行为:
# 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:]]*'
或启用globasciiranges
(使用 bash 4.3 及更高版本):
bash -O globasciiranges -c 'echo [A-Z]*'
答案4
区域设置可以更改 匹配的字符[A-Z]
。使用
(LC_ALL=C; rm [A-Z]*)
以消除影响。 (我使用子 shell 来本地化更改)。