为什么[AZ]匹配bash中的小写字母?


42

在我所知道的所有shell中,都rm [A-Z]*删除了以大写字母开头的所有文件,但是使用bash删除了以字母开头的所有文件。

由于在使用bash-3和bash-4的Linux和Solaris上存在此问题,因此它不能是由libc中的错误模式匹配器或配置错误的语言环境定义引起的错误。

是这种奇怪而危险的行为,还是仅仅是多年来未修复的错误?


3
什么locale输出?我无法重现此内容(touch foo; echo [A-Z]*在原本为空的目录中输出文字模式,而不是“ foo”)。
chepner

4
考虑到有多少人说它对他们有用,或者显示了LC_COLLATE如何影响此事的示例,也许您可​​以编辑问题以添加示例bash会话,以准确说明您所询问的场景。请包括您正在使用的bash版本。
Kenster 2015年

如果您确实在这里阅读了所有文本,那么您将知道我使用的bash版本以及自从我已经发布了问题的解决方案以来所做的事情。让我重复一下解决方案:bash不会管理它自己的语言环境,因此设置LC_COLLATE不会更改任何内容,直到您使用新环境启动另一个bash进程为止。
schily

1
另请参见LC_COLLATE是否(应)影响字符范围?(但问题不是专门针对bash的问题)
吉尔斯(Gilles)'所以

“设置LC_COLLATE不会更改任何内容,除非您使用新环境启动另一个bash进程。” 这与我在Solaris上使用bash-4看到的行为不符。它正在改变正在运行的shell中的行为。 # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Answers:


67

请注意,在使用[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

如果这种行为可由LC_ *环境变量控制,我没有要求。我在POSIX标准委员会工作,我知道如何解决例如问题,tr所以这是我首先检查的问题。
2015年

@schily我无法用旧的bash-3或bash-4重现您的问题;两者均可控制LC_COLLATE,手册中也对此进行了记录。
混乱

抱歉,我无法复制您的信念,但会看到我自己的答案...从这次讨论中的想法中,我发现了问题的原因。
2015年

25

[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

所以cz会被匹配[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]将匹配ABCZ,但没有Ç,仍然没有

如果要匹配大写字母(在任何脚本中),则可以[[:upper:]]改用。没有内置方法bash可以只匹配拉丁脚本中的大写字母(除非单独列出它们)。

如果要在不带变音符号的情况下将其AZ 英语字母匹配,则可以在区域设置中使用[A-Z]或,[[:upper:]]但可以使用(但C不能在BIG5或GB18030这样的字符集中对数据进行编码,该字符集包含多个字符,这些字符包含这些字母的编码)或列表它们分别([ABCDEFGHIJKLMNOPQRSTUVWXYZ])。

请注意,外壳之间存在一些差异。

对于zshbash -O globasciiranges(在bash-4.3引入了名为奇怪选项),schily-sh并且yash[A-Z]在其代码点之间的字符匹配的是的A和的Z,所以就相当于的行为bash在C语言环境。

对于灰烬,mksh和古代外壳,与zsh上面相同,但仅限于单字节字符集。也就是说,例如,在UTF-8语言环境中,[É-Ź]在上不匹配Ó,但是由于是[<c3><89>-<c5><b9>],所以在字节值0x89到0xc5上将匹配!

ksh93bash除了将特殊情况范围视为两端都以小写字母或大写字母开头的方式外,其行为类似于。在那种情况下,它仅在排序在那些两端之间排序的排序元素上匹配,但是排序元素(或多字符排序元素的第一个字符)也都是小写(或分别为大写)。因此,[A-Z]将有匹配É,但不是e作为e之间的排序不AZ,但不会像大写AZ

对于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信之间CI[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()


1
如果您是对的,则可以通过LC_ *变量进行控制。似乎有不同的原因。
2015年

1
@cuonglm,更像mksh(均来自pdksh)。posh -c $'case Ó in [É-Ź]) echo yes; esac'什么也不返回。
斯特凡Chazelas

2
@schily,我提及sort是因为全局bash基于字符排序顺序。我目前无法使用的旧版本bash,但可以稍后查看。那有什么不同吗?
斯特凡Chazelas

1
让我再说一遍:zsh,POSIX-ksh88,ksh93t + Bourne Shell,它们的行为均与我期望的相同。Bash是唯一行为不同的shell,在这种情况下,bash无法通过语言环境进行控制。
schily

2
@schily,请注意\xFF存在字节 0xFF,而不是字符U + 00FF(ÿ其自身编码为0xC3 0xBF)。\xFF单独不能构成有效的字符,所以我看不到为什么要用来匹配它[É-Ź]
斯特凡Chazelas

9

它打算在bash文档的“ 模式匹配”部分中进行记录。范围表达式[X-Y]将包含当前语言环境的整理顺序和字符集之间的任何字符,XY使用它们:

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

你可以看到,b之间的排序AZen_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]*'

6

我在新的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_CTYPELC_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系统上的文档方面始终如一地运行。如果任何语言环境变量的设定(LANGUAGELANGLC_COLLATELC_CTYPELC_ALL,等等),然后击使用根据其手册那些。否则,bash会退回到C。

Wooledge bash的常见问题有这样一段话:

在最新的GNU系统上,按此顺序使用变量。如果设置了LANGUAGE,除非将LANG设置为C,否则使用该语言,在这种情况下将忽略LANGUAGE。另外,某些程序根本不使用LANGUAGE。否则,如果设置了LC_ALL,请使用它。否则,如果设置了涵盖此用法的特定LC_ *变量,请使用该变量。(例如,LC_MESSAGES包含错误消息。)否则,请使用LANG。

因此,可以通过查看所有语言环境驱动变量的总和来解释操作和文档上的明显问题。


如果不存在LC_variable,并且bash的行为与该C语言环境所记录的不一致,则这是一个错误。
schily

1
@bishop:(1)错别字:MVCE应该是MCVE。(2)如果您想完成示例,则应添加env | grep LANGecho "$LANG"
G-Man说'Resstate Monica''s

@schily进一步的调查使我确信此Linux系统上的文档或操作中没有错误。
主教

@ G-Man谢谢!我忘了LANG。有了这个提示,一切都可以解释了。
主教

Sun于1988年左右引入LANG进行首次本地化尝试,之后他们发现单个变量是不够的。如今,它已用作备用,而LC_ALL被用作强制覆盖。
schily

3

语言环境可以更改匹配的字符[A-Z]。采用

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

消除影响。(我使用了一个子外壳来本地化更改)。


这是行不通的,它仍然匹配所有字母
schily

7
这是行不通的,因为glob是在执行rm之前完成的。请先尝试export LC_ALL=C
cuonglm

对不起,您没有正确理解与bash无关的问题。
schily

@schily:是的,我错了,您必须分开声明。检查更新。
choroba

2

如前所述,这是一个“整理顺序”问题。

范围az在某些语言环境中可能包含大写字母:

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

从bash 4.3开始,正确的解决方案是设置选项globasciiranges

shopt -s globasciiranges

使bash的行为就像LC_COLLATE=C全局范围内设置的一样。


-6

看来我对自己的问题找到了正确的答案:

由于Bash无法管理自己的语言环境,因此存在很多问题。因此,在bash进程中设置LC_ *在该shell进程中无效。

如果设置LC_COLLATE = C然后开始另一个bash,则按新bash进程中的预期进行工作。


2
没有我的支持。
混乱

2
我在计算机上的任何版本的bash中都没有对此进行复制,这听起来像是您操作不export正确。
克里斯·

因此,您认为未正确导出某些东西,从而影响了新的bash进程吗?
2015年

4
Solaris对环境的处理众所周知地不足,因此,如果bash中的“错误”是缺少特定于Solaris的解决方法的,我也不会感到惊讶。
hobbs 2015年

1
@schily:您是否引用了更改外壳程序中的LC_ *变量以使其更新其自己的语言环境状态的地方?我认为恰恰相反。特别是对于执行脚本的Shell,在脚本的解析/执行过程中更改语言环境甚至不会具有明确定义的行为,因为脚本是文本文件,而“文本文件”仅在脚本上下文中有意义。单字符编码。
R..
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.