grep和utf-8编码的变音符号

时间:2014-07-25 18:20:38

标签: utf-8 grep

我正在运行Ubuntu并且echo $ LANG告诉我我使用的是UTF-8:“en_US.UTF-8”。

我创建了一个目录,其中包含一个名为'ö'的文件(德语变音符号)

ronald@lala:~/tempX/test$ ls
ö

我的理解是,由于utf-8编码,文件名由两个字节组成,代表一个字符。因此,我很惊讶这匹配:

ronald@lala:~/tempX/test$ ls | grep "^\W\W$"
ö
ronald@lala:~/tempX/test$ ls | egrep "^\W{2,}$"
ö
ronald@lala:~/tempX/test$ ls | grep -P "^\W{2,}$"
ö
ronald@lala:~/tempX/test$ ls | pcregrep "^\W{2,}$"
ö

为什么grep将'ö'视为两个非单词字符而不只是一个?

祝你好运, 罗纳德

4 个答案:

答案 0 :(得分:1)

Grep在字符级别上工作,并考虑当前区域设置的编码和排序规则(在联机帮助页中有记录)。您可以通过切换到C语言环境强制它使用ASCII。

使用pl_PL.UTF-8:

$ echo Ź | grep -i ź
Ź
$ echo ó | grep '[a-z]'
ó
$ echo ó | grep '^..$'
(nothing)

使用C:

$ echo Ź | LC_ALL=C grep -i ź
(nothing)
$ echo ó | LC_ALL=C grep '[a-z]'
(nothing)
$ echo ó | LC_ALL=C grep '^..$'
ó

答案 1 :(得分:1)

简答:

在grep可以正确解释非ASCII文本之前,需要在 之前存在正确的语言环境文件,以便设置适当的环境变量。运行locale-gen en_US.UTF-8,然后运行export LANG="en_US.UTF-8",您应该很好。如果这不起作用(或者如果您没有安装locale-gen),请尝试export LANG=C.UTF-8

答案很长:

问题示例:

$ O_WITH_UMLAUT="ö"

$ printf "%s" "$O_WITH_UMLAUT" | grep -E '^[^\w]$'

$ printf "%s" "$O_WITH_UMLAUT" | grep -E '^[^\w]{2}$'
ö

第一次尝试没有产生输出,但是一旦你要求grep连续搜索两个非单词字符,那就是......

出现这种情况是因为非ASCII字符使用多字节编码方案(在这个时代应该几乎总是UTF-8,但古老/过时的系统可能使用更奇特的编码)。

$ printf "%s" "$O_WITH_UMLAUT" | od -Ax -tx1
000000 c3 b6
000002

注意:如果您的终端模拟器因为相关的编码问题而不允许您粘贴“ö”,那么您仍然可以将一个放入这样的环境变量中来测试: O_WITH_UMLAUT=$(printf "\xC3\xB6")

解决此问题的通常建议是将LANG环境变量(作为LC_* environment variables的后备)设置为en_US.UTF-8(或en_GB.UTF-8之类的内容,pl_PL.UTF-8ru_RU.UTF-8C.UTF-8,你有什么......等等......)以便grep可以知道它应该为输入数据预期的编码:

$ export LANG="en_US.UTF-8"

...但是,如果这不起作用怎么办?

$ printf "%s" "$O_WITH_UMLAUT" | grep -E '^[^\w]$'

$ printf "%s" "$O_WITH_UMLAUT" | grep -E '^[^\w]{2}$'
ö

在这种情况下,首先要检查的是locale的输出:

$ locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

看起来缺少某些区域设置文件。

locale-gen联机帮助页的第一段解释了原因:

  

编译的区域设置文件占用大约50MB的磁盘空间,大多数用户只需要很少的区域设置。为了节省磁盘空间,编译的语言环境文件不在locales包中分发,但是通过运行locale-gen程序安装此包时会自动生成选定的语言环境。

所以,我们所要做的就是:

$ locale-gen en_US.UTF-8
Generating locales (this might take a while)...
  en_US.UTF-8... done

$ locale  # no more warnings!
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

$ printf "%s" "$O_WITH_UMLAUT" | grep -E '^[^\w]$'  # works as it should!
ö

...但是,如果 不起作用怎么办?

$ locale-gen en_US.UTF-8
bash: locale-gen: command not found

绝望之下,您可以尝试C.UTF-8,这几乎应该随处可用:

$ export LANG="C.UTF-8"

$ printf "%s" "$O_WITH_UMLAUT" | grep -E '^[^\w]$'
ö

如果不起作用,您可以尝试设置LC_ALL(充当重手覆盖 )而不是LANG(如前所述,它仅仅是后备)。

最终附录:

在您的情况下,您的非ASCII数据不是来自环境变量,而是文件系统上的目录(或者,更具体地,ls选择该目录名称的文本表示...)所以最好注意一些文件系统(或它们的API,或ls等工具...)将以不同于您预期的方式存储/生成信息,这可能导致类似(但不相关)问题。

例如,请考虑在Linux系统上执行以下操作:

$ mkdir -p /tmp/dirs
$ cd /tmp/dirs
$ python -i

>>> import os
>>> os.getcwd()
'/tmp/dirs'
>>> os.listdir('.')
[]
>>> # Create a directory with this name:
>>> # U+00F6: LATIN SMALL LETTER O WITH DIAERESIS
>>> # (total Unicode code-points: 1)
>>> os.makedirs('\xc3\xb6')
>>> os.listdir('.')
['\xc3\xb6']
>>> # Now create a directory with *this* name:
>>> # U+006F: LATIN SMALL LETTER O (ASCII)
>>> # followed by U+00A8: DIAERESIS (non-ASCII modifier)
>>> # (total Unicode code-points: 2)
>>> os.makedirs('o\xcc\x88')
>>> os.listdir('.')
['\xc3\xb6', 'o\xcc\x88']
>>> exit()

$ ls | grep -E '^[^\w]$'
ö

$ ls | grep -E '^[^\w]{2}$'
ö

$ ls -Fl
total 8
drwxr-xr-x 2 docker docker 4096 May 15 20:52 ö/
drwxr-xr-x 2 docker docker 4096 May 15 20:51 ö/

(混淆是怎么回事?!)

现在,同样的事情,在Mac OS X(HFS +)系统上,幸运的是 - 不允许这样的恶作剧,但是以你的文件/目录为代价,或许在< em>完全你想象的方式:

>>> import os
>>> os.getcwd()
'/private/tmp/dirs'
>>> os.listdir('.')
[]
>>> os.makedirs('\xc3\xb6')
>>> os.listdir('.')
['o\xcc\x88']  # ...that's not what we asked it to create...
>>> os.makedirs('o\xcc\x88')
OSError: [Errno 17] File exists: 'o\xcc\x88'
>>> os.makedirs('\xc3\xb6')
OSError: [Errno 17] File exists: '\xc3\xb6'
>>> exit()

$ ls | grep -E '^[^\w]$'  # nothing...

$ ls | grep -E '^[^\w]{2}$'  # there it is.
ö

因此,一旦您确定您的语言环境已设置并正常运行,如果您的正则表达式不起作用,则接下来要检查的是确保您的文件系统(或您的文件系统)构建ls,或者您在grep管道中使用的任何其他实用程序)都不会在幕后转码。 (我可以编织一条关于MinGW / MSYS实用工具和NTFS / exFAT的纱线,当我在那个特殊的封锁期间拉出我自己的头发时,头发的头发就会多了......但是,我离题了。)

希望有所帮助!

进一步阅读:

答案 2 :(得分:0)

非传统的“回答”,但我的回答是你的Ubuntu坏了,或者你需要和我一样使用相同的语言环境!我正在使用OSX Mavericks。

ls ??
<nothing>

ls ?
¨

ls ?| xxd
0000000: c2a8 0a                                  ...

ls | grep "^\W\W$"
<nothing>

ls | grep "^\W$"
¨

echo $LANG
en_GB.UTF-8

答案 3 :(得分:0)

是的,你对@Ronald,grep和Unicode有问题。 根据{{​​1}}:

  

man grep

但这个同义词不起作用。

The symbol \w is a synonym for [_[:alnum:]] and \W is a synonym for [^_[:alnum:]].