BASH glob/regex 范围的奇怪行为
Weird behavior of BASH glob/regex ranges
我看到 BASH 个括号范围(例如 [A-Z])以一种意外的方式表现。
是否有对这种行为的解释,或者它是一个错误?
假设我有一个变量,我想从中去除所有大写字母:
$ var='ABCDabcd0123'
$ echo "${var//[A-Z]/}"
我得到的结果是这样的:
a0123
如果我用 sed
来做,我会得到一个预期的结果:
$ echo "${var}" | sed 's/[A-Z]//g'
abcd0123
BASH 内置正则表达式匹配似乎也是如此:
$ [[ a =~ [A-Z] ]] ; echo $?
1
$ [[ b =~ [A-Z] ]] ; echo $?
0
如果我检查从'a'到'z'的所有小写字母,似乎只有'a'是一个例外:
$ for l in {a..z}; do [[ $l =~ [A-Z] ]] || echo $l; done
a
我没有启用不区分大小写的匹配,即使我启用了,它也不应该让字母 'a' 表现不同:
$ shopt -p nocasematch
shopt -u nocasematch
作为参考,我使用的是 Cygwin,我在任何其他机器上都没有看到这种行为:
$ uname
CYGWIN_NT-6.3
$ bash --version | head -1
GNU bash, version 4.3.46(7)-release (x86_64-unknown-cygwin)
$ locale
LANG=en_GB.UTF-8
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_ALL=
编辑:
我在此处发现了完全相同的问题:
https://bugs.launchpad.net/ubuntu/+source/bash/+bug/120687
所以,我想这是 "en_GB.UTF-8" 排序规则的错误(?),而不是 BASH 本身。
设置 LC_COLLATE=C
确实解决了这个问题。
肯定 与您的 locale
设置有关。摘自 GNU bash man page under Pattern Matching
[..] in the default C
locale, [a-dx-z]
is equivalent to [abcdxyz]
. Many locales sort characters in dictionary order, and in these locales [a-dx-z]
is typically not equivalent to [abcdxyz]
; it might be equivalent to [aBbCcDdxXyYz]
, for example. To obtain the traditional interpretation of ranges in bracket expressions, you can force the use of the C locale by setting the LC_COLLATE
or LC_ALL
environment variable to the value C
, or enable the globasciiranges
shell option.[..]
在这种情况下使用 POSIX
字符 - 类s,[[:upper:]]
或将 locale
设置 LC_ALL
或 LC_COLLATE
更改为C
如上所述。
LC_ALL=C var='ABCDabcd0123'
echo "${var//[A-Z]/}"
abcd0123
此外,您的 negative 测试进行大写检查将在设置此语言环境时对所有小写字母失败,因此打印字母,
LC_ALL=C; for l in {a..z}; do [[ $l =~ [A-Z] ]] || echo $l; done
还有,在上面的locale设置下
[[ a =~ [A-Z] ]] ; echo $?
1
[[ b =~ [A-Z] ]] ; echo $?
1
但对于所有 小写 范围都是如此,
[[ a =~ [a-z] ]] ; echo $?
0
[[ b =~ [a-z] ]] ; echo $?
0
这么说,所有这些都可以通过使用POSIX
指定的字符类,在一个新的shell 没有任何locale
设置,
echo "${var//[[:upper:]]/}"
abcd0123
和
for l in {a..z}; do [[ $l =~ [[:upper:]] ]] || echo $l; done
我看到 BASH 个括号范围(例如 [A-Z])以一种意外的方式表现。
是否有对这种行为的解释,或者它是一个错误?
假设我有一个变量,我想从中去除所有大写字母:
$ var='ABCDabcd0123'
$ echo "${var//[A-Z]/}"
我得到的结果是这样的:
a0123
如果我用 sed
来做,我会得到一个预期的结果:
$ echo "${var}" | sed 's/[A-Z]//g'
abcd0123
BASH 内置正则表达式匹配似乎也是如此:
$ [[ a =~ [A-Z] ]] ; echo $?
1
$ [[ b =~ [A-Z] ]] ; echo $?
0
如果我检查从'a'到'z'的所有小写字母,似乎只有'a'是一个例外:
$ for l in {a..z}; do [[ $l =~ [A-Z] ]] || echo $l; done
a
我没有启用不区分大小写的匹配,即使我启用了,它也不应该让字母 'a' 表现不同:
$ shopt -p nocasematch
shopt -u nocasematch
作为参考,我使用的是 Cygwin,我在任何其他机器上都没有看到这种行为:
$ uname
CYGWIN_NT-6.3
$ bash --version | head -1
GNU bash, version 4.3.46(7)-release (x86_64-unknown-cygwin)
$ locale
LANG=en_GB.UTF-8
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_ALL=
编辑:
我在此处发现了完全相同的问题:
https://bugs.launchpad.net/ubuntu/+source/bash/+bug/120687
所以,我想这是 "en_GB.UTF-8" 排序规则的错误(?),而不是 BASH 本身。
设置 LC_COLLATE=C
确实解决了这个问题。
肯定 与您的 locale
设置有关。摘自 GNU bash man page under Pattern Matching
[..] in the default
C
locale,[a-dx-z]
is equivalent to[abcdxyz]
. Many locales sort characters in dictionary order, and in these locales[a-dx-z]
is typically not equivalent to[abcdxyz]
; it might be equivalent to[aBbCcDdxXyYz]
, for example. To obtain the traditional interpretation of ranges in bracket expressions, you can force the use of the C locale by setting theLC_COLLATE
orLC_ALL
environment variable to the valueC
, or enable theglobasciiranges
shell option.[..]
在这种情况下使用 POSIX
字符 - 类s,[[:upper:]]
或将 locale
设置 LC_ALL
或 LC_COLLATE
更改为C
如上所述。
LC_ALL=C var='ABCDabcd0123'
echo "${var//[A-Z]/}"
abcd0123
此外,您的 negative 测试进行大写检查将在设置此语言环境时对所有小写字母失败,因此打印字母,
LC_ALL=C; for l in {a..z}; do [[ $l =~ [A-Z] ]] || echo $l; done
还有,在上面的locale设置下
[[ a =~ [A-Z] ]] ; echo $?
1
[[ b =~ [A-Z] ]] ; echo $?
1
但对于所有 小写 范围都是如此,
[[ a =~ [a-z] ]] ; echo $?
0
[[ b =~ [a-z] ]] ; echo $?
0
这么说,所有这些都可以通过使用POSIX
指定的字符类,在一个新的shell 没有任何locale
设置,
echo "${var//[[:upper:]]/}"
abcd0123
和
for l in {a..z}; do [[ $l =~ [[:upper:]] ]] || echo $l; done