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

Ron*_*ald 6 grep utf-8

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

\n\n

我创建了一个目录,其中包含一个名为“\'\xc3\xb6\”(德语元音变音)的文件

\n\n
ronald@lala:~/tempX/test$ ls\n\xc3\xb6\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
ronald@lala:~/tempX/test$ ls | grep "^\\W\\W$"\n\xc3\xb6\nronald@lala:~/tempX/test$ ls | egrep "^\\W{2,}$"\n\xc3\xb6\nronald@lala:~/tempX/test$ ls | grep -P "^\\W{2,}$"\n\xc3\xb6\nronald@lala:~/tempX/test$ ls | pcregrep "^\\W{2,}$"\n\xc3\xb6\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

最好的问候,\n罗纳德

\n

Mar*_* G. 5

简短回答:

\n\n

除了设置正确的环境变量之外,还需要存在正确的区域设置文件,然后 grep 才能正确解释非 ASCII 文本。locale-gen en_US.UTF-8跟着跑export LANG="en_US.UTF-8",你应该就可以走了。如果这不起作用(或者如果您没有locale-gen安装),请尝试export LANG=C.UTF-8.

\n\n
\n\n

长答案:

\n\n

问题示例:

\n\n
$ O_WITH_UMLAUT="\xc3\xb6"\n\n$ printf "%s" "$O_WITH_UMLAUT" | grep -E \'^[^\\w]$\'\n\n$ printf "%s" "$O_WITH_UMLAUT" | grep -E \'^[^\\w]{2}$\'\n\xc3\xb6\n
Run Code Online (Sandbox Code Playgroud)\n\n

第一次尝试不会产生任何输出,但是当您要求 grep 连续搜索两个非单词字符时,就会出现\xe2\x80\xa6

\n\n

出现此行为的原因是非 ASCII 字符使用多字节编码方案(在当今时代几乎应始终为 UTF-8,但古老/过时的系统可能使用更奇特的编码)。

\n\n
$ printf "%s" "$O_WITH_UMLAUT" | od -Ax -tx1\n000000 c3 b6\n000002\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意:如果您的终端模拟器由于相关编码问题而不允许您粘贴 \'\xc3\xb6\',那么您仍然可以将其放入环境变量中,如下所示以进行测试: O_WITH_UMLAUT=$(printf "\\xC3\\xB6")

\n\n

解决此问题的通常建议是将LANG环境变量(充当LC_*环境变量的后备)设置为类似en_US.UTF-8(或en_GB.UTF-8为你pl_PL.UTF-8有什么,等等\xe2\x80\xa6),以便grep 可以知道输入数据应该采用什么编码:ru_RU.UTF-8C.UTF-8

\n\n
$ export LANG="en_US.UTF-8"\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\xa6但是,如果 \xe2\x80\x99 不起作用怎么办?

\n\n
$ printf "%s" "$O_WITH_UMLAUT" | grep -E \'^[^\\w]$\'\n\n$ printf "%s" "$O_WITH_UMLAUT" | grep -E \'^[^\\w]{2}$\'\n\xc3\xb6\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
$ locale\nlocale: Cannot set LC_CTYPE to default locale: No such file or directory\nlocale: Cannot set LC_MESSAGES to default locale: No such file or directory\nlocale: Cannot set LC_ALL to default locale: No such file or directory\nLANG=en_US.UTF-8\nLANGUAGE=\nLC_CTYPE="en_US.UTF-8"\nLC_NUMERIC="en_US.UTF-8"\nLC_TIME="en_US.UTF-8"\nLC_COLLATE="en_US.UTF-8"\nLC_MONETARY="en_US.UTF-8"\nLC_MESSAGES="en_US.UTF-8"\nLC_PAPER="en_US.UTF-8"\nLC_NAME="en_US.UTF-8"\nLC_ADDRESS="en_US.UTF-8"\nLC_TELEPHONE="en_US.UTF-8"\nLC_MEASUREMENT="en_US.UTF-8"\nLC_IDENTIFICATION="en_US.UTF-8"\nLC_ALL=\n
Run Code Online (Sandbox Code Playgroud)\n\n

看起来有些区域设置文件丢失了。

\n\n

第一段locale-gen解释了原因:

\n\n
\n

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

\n
\n\n

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

\n\n
$ locale-gen en_US.UTF-8\nGenerating locales (this might take a while)...\n  en_US.UTF-8... done\n\n$ locale  # no more warnings!\nLANG=en_US.UTF-8\nLANGUAGE=\nLC_CTYPE="en_US.UTF-8"\nLC_NUMERIC="en_US.UTF-8"\nLC_TIME="en_US.UTF-8"\nLC_COLLATE="en_US.UTF-8"\nLC_MONETARY="en_US.UTF-8"\nLC_MESSAGES="en_US.UTF-8"\nLC_PAPER="en_US.UTF-8"\nLC_NAME="en_US.UTF-8"\nLC_ADDRESS="en_US.UTF-8"\nLC_TELEPHONE="en_US.UTF-8"\nLC_MEASUREMENT="en_US.UTF-8"\nLC_IDENTIFICATION="en_US.UTF-8"\nLC_ALL=\n\n$ printf "%s" "$O_WITH_UMLAUT" | grep -E \'^[^\\w]$\'  # works as it should!\n\xc3\xb6\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\xa6但是,如果那样呢?

\n\n
$ locale-gen en_US.UTF-8\nbash: locale-gen: command not found\n
Run Code Online (Sandbox Code Playgroud)\n\n

无奈之下,你可以尝试一下C.UTF-8,它几乎在任何地方都可以轻松获得:

\n\n
$ export LANG="C.UTF-8"\n\n$ printf "%s" "$O_WITH_UMLAUT" | grep -E \'^[^\\w]$\'\n\xc3\xb6\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果这仍然不起作用,您可以尝试设置LC_ALL(充当严厉的覆盖)而不是LANG(如前所述,这仅充当后备)。

\n\n
\n\n

最后附录:

\n\n

在您的情况下,您的非 ASCII 数据不是来自环境变量,而是来自文件系统上的目录(或者更具体地说,ls\xe2\x80\x99s 选择该目录的文本表示形式\ xe2\x80\x99s name\xe2\x80\xa6),因此最好了解某些文件系统(或其 API,或类似工具)ls\xe2\x80\xa6 这样的工具)会以与您预期不同的方式存储/生成信息,这可能会导致类似(但不相关)的问题。

\n\n

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

\n\n
$ mkdir -p /tmp/dirs\n$ cd /tmp/dirs\n$ python -i\n\n>>> import os\n>>> os.getcwd()\n\'/tmp/dirs\'\n>>> os.listdir(\'.\')\n[]\n>>> # Create a directory with this name:\n>>> # U+00F6: LATIN SMALL LETTER O WITH DIAERESIS\n>>> # (total Unicode code-points: 1)\n>>> os.makedirs(\'\\xc3\\xb6\')\n>>> os.listdir(\'.\')\n[\'\\xc3\\xb6\']\n>>> # Now create a directory with *this* name:\n>>> # U+006F: LATIN SMALL LETTER O (ASCII)\n>>> # followed by U+00A8: DIAERESIS (non-ASCII modifier)\n>>> # (total Unicode code-points: 2)\n>>> os.makedirs(\'o\\xcc\\x88\')\n>>> os.listdir(\'.\')\n[\'\\xc3\\xb6\', \'o\\xcc\\x88\']\n>>> exit()\n\n$ ls | grep -E \'^[^\\w]$\'\n\xc3\xb6\n\n$ ls | grep -E \'^[^\\w]{2}$\'\no\xcc\x88\n\n$ ls -Fl\ntotal 8\ndrwxr-xr-x 2 docker docker 4096 May 15 20:52 o\xcc\x88/\ndrwxr-xr-x 2 docker docker 4096 May 15 20:51 \xc3\xb6/\n
Run Code Online (Sandbox Code Playgroud)\n\n

(怎么\xe2\x80\x99s 让人困惑?!)

\n\n

现在,同样的事情,在 Mac OS X (HFS+) 系统上, \xe2\x80\x94 值得庆幸的是 \xe2\x80\x94 不允许这样的恶作剧,但代价是你的文件/目录可能无法以相当方式表示您可能期望的方式:

\n\n
>>> import os\n>>> os.getcwd()\n\'/private/tmp/dirs\'\n>>> os.listdir(\'.\')\n[]\n>>> os.makedirs(\'\\xc3\\xb6\')\n>>> os.listdir(\'.\')\n[\'o\\xcc\\x88\']  # ...that\'s not what we asked it to create...\n>>> os.makedirs(\'o\\xcc\\x88\')\nOSError: [Errno 17] File exists: \'o\\xcc\\x88\'\n>>> os.makedirs(\'\\xc3\\xb6\')\nOSError: [Errno 17] File exists: \'\\xc3\\xb6\'\n>>> exit()\n\n$ ls | grep -E \'^[^\\w]$\'  # nothing...\n\n$ ls | grep -E \'^[^\\w]{2}$\'  # there it is.\no\xcc\x88\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,一旦您确定您的语言环境已设置并正常运行,如果您的正则表达式仍然无法正常工作,那么接下来要检查的就是确保您的文件系统(或您的构建ls,或您在 grep 管道中使用的任何其他实用程序\xe2\x80\x99)都\xe2\x80\x99t 在幕后对您的内容进行转码。(我可以编一篇关于 MinGW/MSYS 实用程序和 NTFS/exFAT 的故事,它会让你的头发和我在那个特定的越轨\xe2\x80\xa6 中拔出的头发一样多,但是,我离题了。)

\n\n

希望有帮助!

\n\n
\n\n

进一步阅读:

\n\n\n


Mar*_*ell 0

非常规的“答案”,但我的答案是你的Ubuntu坏了,或者你需要使用与我相同的语言环境!我正在使用 OSX Mavericks。

\n\n
ls ??\n<nothing>\n\nls ?\n\xc2\xa8\n\nls ?| xxd\n0000000: c2a8 0a                                  ...\n\nls | grep "^\\W\\W$"\n<nothing>\n\nls | grep "^\\W$"\n\xc2\xa8\n\necho $LANG\nen_GB.UTF-8\n
Run Code Online (Sandbox Code Playgroud)\n