在我所知道的所有shell中,都rm [A-Z]*
删除了以大写字母开头的所有文件,但是使用bash删除了以字母开头的所有文件。
由于在使用bash-3和bash-4的Linux和Solaris上存在此问题,因此它不能是由libc中的错误模式匹配器或配置错误的语言环境定义引起的错误。
是这种奇怪而危险的行为,还是仅仅是多年来未修复的错误?
# echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*
A b B z ZABZ
在我所知道的所有shell中,都rm [A-Z]*
删除了以大写字母开头的所有文件,但是使用bash删除了以字母开头的所有文件。
由于在使用bash-3和bash-4的Linux和Solaris上存在此问题,因此它不能是由libc中的错误模式匹配器或配置错误的语言环境定义引起的错误。
是这种奇怪而危险的行为,还是仅仅是多年来未修复的错误?
# echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*
A b B z ZABZ
Answers:
请注意,在使用[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
([a-z]
)中的所有大写字母,但除外Z
。A
和之间Z
([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
tr
所以这是我首先检查的问题。
LC_COLLATE
,手册中也对此进行了记录。
[A-Z]
in bash
中的所有归类元素(Dsz
在匈牙利语言环境中,字符但称为call也是字符序列),它们在after A
之前排序Z
。在您的语言环境中,c
可能在B和C之间排序。
$ printf '%s\n' A a á b B c C Ç z Z Ẑ | sort
a
A
á
b
B
c
C
Ç
z
Z
Ẑ
所以c
或z
会被匹配[A-Z]
,但不会Ẑ
或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语言环境。
对于灰烬,mksh和古代外壳,与zsh
上面相同,但仅限于单字节字符集。也就是说,例如,在UTF-8语言环境中,[É-Ź]
在上不匹配Ó
,但是由于是[<c3><89>-<c5><b9>]
,所以在字节值0x89到0xc5上将匹配!
ksh93
bash
除了将特殊情况范围视为两端都以小写字母或大写字母开头的方式外,其行为类似于。在那种情况下,它仅在排序在那些两端之间排序的排序元素上匹配,但是排序元素(或多字符排序元素的第一个字符)也都是小写(或分别为大写)。因此,[A-Z]
将有匹配É
,但不是e
作为e
之间的排序不A
和Z
,但不会像大写A
和Z
。
对于fnmatch()
模式(如find -name '[A-Z]'
)或系统正则表达式(如grep '[A-Z]'
),它取决于系统和语言环境。例如,在这里的GNU系统[A-Z]
上x
,在en_GB.UTF-8
语言环境中不匹配,但在一个语言环境中匹配th_TH.UTF-8
。我目前还不清楚它使用什么信息来确定该信息,但显然是基于从LC_COLLATE语言环境data派生的查找表。
POSIX允许所有行为,因为POSIX会在C语言环境以外的语言环境中保留未指定范围的行为。现在我们可以争论每种方法的好处。
bash
与的搭配非常有意义[C-G]
,我们希望介于C
和之间的字符G
。使用用户的排序顺序来确定介于两者之间的内容是最合乎逻辑的方法。
现在的问题是,它打破了许多人的期望,尤其是那些习惯于Unicode之前,甚至是国际化之前的传统行为的人们。虽然从一个普通用户,这使得可以感测[C-I]
包括h
作为h
信之间C
和I
和[A-g]
不包括Z
,它是具有处理ASCII人只几十年另当别论。
这bash
行为与也不同[A-Z]
其他GNU工具范围匹配像GNU正则表达式(如grep
/ sed
...)或fnmatch()
作为find -name
。
这也意味着[A-Z]
匹配的内容随环境,操作系统和操作系统版本而变化。[A-Z]
匹配Á但不匹配also 的事实也不理想。
对于zsh
/ yash
,我们使用不同的排序顺序。代替依赖用户的字符顺序概念,我们使用字符点代码值。这样做的好处是易于理解,但实际上,除了ASCII之外,它并不是很有用。[A-Z]
匹配26个美国英语大写字母,[0-9]
匹配十进制数字。Unicode中的代码点遵循某些字母的顺序,但是并未被概括,也无法被概括,因为使用同一脚本的不同人不一定就字母顺序达成共识。
对于传统的shell和mksh(破折号),它已经坏了(现在大多数人都使用多字节字符),但是主要是因为它们还没有多字节支持。向like bash
和shell添加多字节支持zsh
是一项巨大的努力,并且仍在进行中。yash
(日语外壳)最初从一开始就设计为具有多字节支持。
ksh93的方法的好处是与系统的正则表达式或fnmatch()一致(或者至少在GNU系统上至少如此)。在那里,它不违反某些人的期望,因为[A-Z]
不包括小写字母,[A-Z]
包括É
(和Á,但不包括Ź)。它与顺序不一致sort
或通常不相符strcoll()
。
mksh
(均来自pdksh)。posh -c $'case Ó in [É-Ź]) echo yes; esac'
什么也不返回。
sort
是因为全局bash
基于字符排序顺序。我目前无法使用的旧版本bash
,但可以稍后查看。那有什么不同吗?
\xFF
存在字节 0xFF,而不是字符U + 00FF(ÿ
其自身编码为0xC3 0xBF)。\xFF
单独不能构成有效的字符,所以我看不到为什么要用来匹配它[É-Ź]
。
它打算在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]*'
我在新的Amazon EC2实例上观察到了这种行为。由于OP不提供MCVE,因此我将发布一个:
$ 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
因此,没有我的LC_*
设置会使Linux上的bash 4.1.2(1)-发行版产生明显的奇怪行为。我可以通过设置和取消设置相应的语言环境变量来可靠地切换奇数行为。毫不奇怪,通过导出,此行为看起来是一致的:
$ export LC_ALL=C
$ bash
$ echo [A-Z]*
[A-Z]*
$ exit
$ echo $SHLVL
1
$ unset LC_ALL
$ bash
$ echo [A-Z]*
foo
当我看到bash像Stéphane“ Shellshock” Chazelas 回答时一样,我认为bash关于模式匹配的文档有很多错误:
例如,在默认的C语言环境中,“ [a-dx-z]”等效于“ [abcdxyz]”
我将该句子(强调我的意思)读为“如果未设置相关的语言环境变量,则bash将默认为C语言环境”。Bash似乎没有这样做。相反,它似乎默认为语言环境,在该语言环境中,字符以字典顺序和变音符号折叠进行排序:
$ echo [A-E]*
[A-E]*
$ echo [A-F]*
foo
$ touch "évocateur"
$ echo [A-F]*
foo évocateur
我认为bash最好记录未定义LC_*
(特别是LC_CTYPE
和LC_COLLATE
)时它将如何表现。但是与此同时,我将分享一些智慧:
...您必须非常小心[字符范围],因为除非正确配置,否则它们不会产生预期的结果。现在,您应该避免使用它们,而应使用字符类。
和
如果您确实很合适,并且/或者正在为多语言环境编写脚本,那么最好是确保在匹配文件时知道您的语言环境变量,或者确保您在完全通用的方式。
基于@ G-Man的更新,让我们更深入地了解正在发生的事情:
$ env | grep LANG
LANG=en_US.UTF-8
啊,哈!这就解释了前面看到的整理。让我们删除所有语言环境变量:
$ unset LANG LANGUAGE LC_ALL
$ env | grep 'LC_|LANG'
$ echo [A-Z]*
[A-Z]*
好了 现在,bash在此Linux系统上的文档方面始终如一地运行。如果任何语言环境变量的设定(LANGUAGE
,LANG
,LC_COLLATE
,LC_CTYPE
,LC_ALL
,等等),然后击使用根据其手册那些。否则,bash会退回到C。
该Wooledge bash的常见问题有这样一段话:
在最新的GNU系统上,按此顺序使用变量。如果设置了LANGUAGE,除非将LANG设置为C,否则使用该语言,在这种情况下将忽略LANGUAGE。另外,某些程序根本不使用LANGUAGE。否则,如果设置了LC_ALL,请使用它。否则,如果设置了涵盖此用法的特定LC_ *变量,请使用该变量。(例如,LC_MESSAGES包含错误消息。)否则,请使用LANG。
因此,可以通过查看所有语言环境驱动变量的总和来解释操作和文档上的明显问题。
C
语言环境所记录的不一致,则这是一个错误。
env | grep LANG
或echo "$LANG"
。
LANG
。有了这个提示,一切都可以解释了。
如前所述,这是一个“整理顺序”问题。
范围az在某些语言环境中可能包含大写字母:
aAbBcC[...]xXyYzZ
| |
from a to z
从bash 4.3开始,正确的解决方案是设置选项globasciiranges
:
shopt -s globasciiranges
使bash的行为就像LC_COLLATE=C
在全局范围内设置的一样。
看来我对自己的问题找到了正确的答案:
由于Bash无法管理自己的语言环境,因此存在很多问题。因此,在bash进程中设置LC_ *在该shell进程中无效。
如果设置LC_COLLATE = C然后开始另一个bash,则按新bash进程中的预期进行工作。
export
正确。
locale
输出?我无法重现此内容(touch foo; echo [A-Z]*
在原本为空的目录中输出文字模式,而不是“ foo”)。