如何使用grep命令匹配所有非ASCII字符?

426

我有几个非常大的XML文件,我正在尝试找到包含非ASCII字符的行。我尝试了以下方法:

grep -e "[\x{00FF}-\x{FFFF}]" file.xml

但是这会返回文件中的每一行,不管该行是否包含指定范围内的字符。

我是语法出错了还是做错了其他什么事情?我还尝试过:

egrep "[\x{00FF}-\x{FFFF}]" file.xml 

(模式周围同时使用单引号和双引号)。


ASCII字符只有一个字节长,因此除非文件是Unicode,否则不应该有超过0xFF的字符。 - zdav
我们如何超越 \xFF?Grep 给出了一个“grep: range out of order in character class”的错误。 - Mudit Jain
1
有时候,对于文件中设置了高位的字符,我们需要第二个意见。在这种情况下,我喜欢使用 tr <file.txt -d '\000-\177' >foo.out && ls -al foo.out 来获取计数。然后可以使用 od -x foo.out 查看实际值。 - Ron Burk
awk 解决方案C locale + grep 在 BSD 上可行。 - Clint Pachl
17个回答

583
你可以使用以下命令:
grep --color='auto' -P -n "[\x80-\xFF]" file.xml

这将为您提供行号,并以红色突出显示非ASCII字符。
在某些系统中,根据您的设置,上述方法可能无法正常工作,因此您可以通过反向grep来查找。
grep --color='auto' -P -n "[^\x00-\x7F]" file.xml

请注意,重要的部分是-P标志,它等同于--perl-regexp:因此它会将您的模式解释为Perl正则表达式。它还指出:

这是非常实验性的,grep -P可能会警告未实现的功能。


55
在 BSD grep(在 OS X 10.8 Mountain Lion 上)中,这不起作用,因为它不支持 P 选项。 - Bastiaan M. van de Weerd
22
更新我的最后一条评论,GNU 版本的 grep 可以在 Homebrew 的 dupes 库中使用(启用方式为 brew tap homebrew/dupes):brew install grep - Bastiaan M. van de Weerd
56
@BastiaanVanDeWeerd是正确的,OSX 10.8上的grep不再支持PCRE("Perl-compatible regular expressions"),因为Darwin现在使用的是BSD grep而非GNU grep。安装“dupes”库的替代方法是安装pcre:brew install pcre...作为安装的一部分,您将获得pcregrep实用程序,您可以按以下方式使用它:pcregrep --color='auto' -n "[\x80-\xFF]" file.xml - pvandenberk
19
对于Mac上的brew用户,可以使用brew install coreutils安装GNU的核心工具集。这将为您提供以“g”为前缀的许多GNU工具-在这种情况下使用ggrep。这样可以避免由于替换系统实用程序而引起的问题,因为特定于系统的Mac脚本现在依赖于BSD grep。 - Joel Purra
26
在 Mac 上,这个命令可以正常工作:ag "[\x80-\xFF]" file,你只需要安装 the_silver_searcher 即可。 - slf
显示剩余20条评论

154

与上述大部分解决方案一样,不要对非ASCII字符的字节范围进行假设,我认为更好的做法是明确ASCII字符实际的字节范围。

因此,例如第一个解决方案将变为:

Instead of making assumptions about the byte range of non-ASCII characters, as most of the above solutions do, it's slightly better IMO to be explicit about the actual byte range of ASCII characters instead.

So the first solution for instance would become:

grep --color='auto' -P -n '[^\x00-\x7F]' file.xml

(这基本上是在十六进制ASCII范围之外查找任何字符的grep:从\x00到\x7F)

在Mountain Lion中,由于BSD grep缺乏PCRE支持,这不起作用,但是通过Homebrew安装pcre后,以下内容也能正常工作:


pcregrep --color='auto' -n '[^\x00-\x7F]' file.xml

有人能想到的任何优点或缺点吗?


10
这对我来说很有效,而上面的解决方案却失败了。找到微软 Word 中的撇号从未如此容易! - AlbertEngelB
5
如果你有一个兼容bash的shell但没有pcre-grep,那么LC_COLLATE=C grep $'[^\1-\177]'可以工作(对于没有空字节的文件)。 - idupree
2
这个解决方案似乎比上面的更加一致地工作。 - 0xcaff
1
我必须使用这个来提取我的UTF8文件中的汉字、西里尔字母和繁体中文,使用“[\x80-\xFF]”会漏掉所有这些内容。 - buckaroo1177125
1
优点是这个工具表现得非常出色,而其他选项也不错但没有这么好。到目前为止尚未发现任何缺点。 - jwpfox
显示剩余3条评论

73

简单的方法是将非ASCII字符定义为不是ASCII字符的字符。

LC_ALL=C grep '[^ -~]' file.xml

如果必要的话,在^后添加一个制表符。

设置LC_COLLATE=C可以避免在许多语言环境中出现有关字符范围含义的令人讨厌的意外情况。设置LC_CTYPE=C是匹配单字节字符所必需的 - 否则,该命令将错过当前编码中的无效字节序列。设置LC_ALL=C可以完全避免依赖于语言环境的效果。


1
在RedHat 6.4上使用tcsh时,我不得不使用<<< env LC_COLLATE=C grep -n '[^ -~]' file.xml >>>。我添加了-n以获取行号。 - ddevienne
5
如果你设置了 LC_ALL=en_US.UTF-8,那么它会覆盖 LC_COLLATE 的设置。你的环境中不应该有这个设置!LC_ALL 只应用于强制指定某个任务使用特定的地区设置,通常是 C。如果要设置所有类别的默认地区设置,请设置 LANG - Gilles 'SO- stop being evil'
1
@gerrit 我的水晶球告诉我你在使用多字节语言环境下的GNU grep。对于某些正则表达式,它可能会非常慢。无论如何,我的答案是错误的(或者至少不完整):它会忽略环境语言中的无效字节序列。请尝试使用 LC_ALL=C 再次运行。 - Gilles 'SO- stop being evil'
1
起初,我没有添加 LC_ALL=C,在 Mac OS X 和 Ubuntu 上的行为不同。在我添加了这个设置之后,它们给出了相同的结果。 - Max Peng
3
这个在 Mac 上可行,而其他基于 grep 的解决方案则不行。 - Matthias Fripp
显示剩余8条评论

68
以下对我有效:
grep -P "[\x80-\xFF]" file.xml

非ASCII字符从0x80开始,到0xFF结束,这是以字节为单位的。Grep(及其相关工具)不会对Unicode进行处理,将多字节字符合并为一个实体以进行正则表达式匹配,这似乎是您想要的。我的grep中的-P选项允许在字符类中使用\ xdd转义来实现您想要的功能。

1
对于可能不知道如何在多个文件中调用此命令的视图,只需运行以下命令:find . -name *.xml | xargs grep -P "[\x80-\xFF]" - David Mohundro
1
这确实返回了一个匹配,但没有指示字符是什么以及在哪里。如何查看字符及其位置? - Faheem Mitha
添加 "-n" 将显示行号,另外不可见字符将显示为终端上的块:grep -n -P "[\x80-\xFF]" file.xml - fooMonster
6
我遇到了韩语的问题:echo '소녀시대' | grep -P "[\x80-\xFF]" 对我来说没有返回结果——有人能否确认一下?(使用GNU grep 2.21) - frabjous
@frabjous 我也是这样,但是使用反向grep可以解决:echo '소녀시대' | grep -P "[^\x00-\x7F]"。或者像@slf指出的那样直接使用the_silver_searcherecho '소녀시대' | ag "[\x80-\xFF]" - psmith

62
在Perl中
perl -ane '{ if(m/[[:^ascii:]]/) { print  } }' fileName > newFile

1
在OSX10.11上,我尝试了几个grep+regex的解决方案,最终找到了这个真正可行的方法。 - s g
@sg,能分享一下那个OSX的解决方案吗? - geotheory
上面的 Perl 脚本就是我所说的解决方案。 - s g
8
perl -lne 'print if /[^[:ascii:]]/' file.xml 翻译为中文是:在文件 file.xml 中,使用 Perl 编程语言读取每一行,并打印出包含非 ASCII 字符的行。 - Naveed

30

这是我找到的另一种变体,它产生了与接受答案中的 [\x80-\xFF] grep 搜索完全不同的结果。或许对于某些人来说,找到额外的非 ASCII 字符会有用:

grep --color='auto' -P -n "[^[:ascii:]]" myfile.txt

注意:我的电脑上的 grep(一台 Mac)没有 -P 选项,所以我执行了 brew install grep 并使用 ggrep 而不是 grep 开始了上述调用。


2
这绝对是最好的答案,因为它适用于Mac和Linux。 - tommy.carstensen
1
取决于所在地区。在我设置 LC_ALL=C 像这样 LC_ALL=C grep --color='auto' -P -n "[^[:ascii:]]" myfile.txt 之前,它对我不起作用。 - Praveen Lobo

22

查找不可打印字符。简介

  1. 搜索控制字符和扩展Unicode字符。
  2. 需要设置区域设置,例如LC_ALL=C,以使grep在处理扩展Unicode字符时按预期工作。

因此,首选的非ASCII字符查找器:

$ perl -ne 'print "$. $_" if m/[\x00-\x08\x0E-\x1F\x80-\xFF]/' notes_unicode_emoji_test

就像最佳答案中所述,反向grep:

$ grep --color='auto' -P -n "[^\x00-\x7F]" notes_unicode_emoji_test

与顶部答案相同,但使用LC_ALL=C

$ LC_ALL=C grep --color='auto' -P -n "[\x80-\xFF]" notes_unicode_emoji_test

更多关于此的极其详细的内容,请查看:...

我同意评论中Harvey所说,搜索非可打印字符通常更有用,或者你应该考虑非ASCII而不是非可打印字符。Harvey建议使用“[^\n -~]”。对于DOS文本文件,请添加\r。这将转换为“[^\x0A\x020-\x07E]”,并加上\x0D表示CR。

另外,在grep命令中加入-c(显示匹配模式数)在搜索非可打印字符时非常有用,因为匹配的字符串可能会搞乱终端。

我发现将范围0-8和0x0e-0x1f(添加到0x80-0xff范围内)排除是一个有用的模式。这将排除制表符、回车和换行符以及其他一两个不常见的可打印字符。所以,在我看来,一个相当有用(尽管粗略)的grep模式是:

grep -c -P -n "[\x00-\x08\x0E-\x1F\x80-\xFF]" *

实际上,通常你需要做以下操作:
LC_ALL=C grep -c -P -n "[\x00-\x08\x0E-\x1F\x80-\xFF]" *

故障:

LC_ALL=C - set locale to C, otherwise many extended chars will not match (even though they look like they are encoded > 0x80)
\x00-\x08 - non-printable control chars 0 - 7 decimal
\x0E-\x1F - more non-printable control chars 14 - 31 decimal
\x80-1xFF - non-printable chars > 128 decimal
-c - print count of matching lines instead of lines
-P - perl style regexps

Instead of -c you may prefer to use -n (and optionally -b) or -l
-n, --line-number
-b, --byte-offset
-l, --files-with-matches

例如,在当前目录下使用find和grep查找所有文件的实际示例:

LC_ALL=C find . -type f -exec grep -c -P -n "[\x00-\x08\x0E-\x1F\x80-\xFF]" {} + 

有时候您可能希望调整grep。例如,在一些可打印文件中使用BS(0x08-退格)字符或排除VT(0x0B-垂直制表符)。在某些情况下,BEL(0x07)和ESC(0x1B)字符也可以被视为可打印字符。

Non-Printable ASCII Chars
** marks PRINTABLE but CONTROL chars that is useful to exclude sometimes
Dec   Hex Ctrl Char description           Dec Hex Ctrl Char description
0     00  ^@  NULL                        16  10  ^P  DATA LINK ESCAPE (DLE)
1     01  ^A  START OF HEADING (SOH)      17  11  ^Q  DEVICE CONTROL 1 (DC1)
2     02  ^B  START OF TEXT (STX)         18  12  ^R  DEVICE CONTROL 2 (DC2)
3     03  ^C  END OF TEXT (ETX)           19  13  ^S  DEVICE CONTROL 3 (DC3)
4     04  ^D  END OF TRANSMISSION (EOT)   20  14  ^T  DEVICE CONTROL 4 (DC4)
5     05  ^E  END OF QUERY (ENQ)          21  15  ^U  NEGATIVE ACKNOWLEDGEMENT (NAK)
6     06  ^F  ACKNOWLEDGE (ACK)           22  16  ^V  SYNCHRONIZE (SYN)
7     07  ^G  BEEP (BEL)                  23  17  ^W  END OF TRANSMISSION BLOCK (ETB)
8     08  ^H  BACKSPACE (BS)**            24  18  ^X  CANCEL (CAN)
9     09  ^I  HORIZONTAL TAB (HT)**       25  19  ^Y  END OF MEDIUM (EM)
10    0A  ^J  LINE FEED (LF)**            26  1A  ^Z  SUBSTITUTE (SUB)
11    0B  ^K  VERTICAL TAB (VT)**         27  1B  ^[  ESCAPE (ESC)
12    0C  ^L  FF (FORM FEED)**            28  1C  ^\  FILE SEPARATOR (FS) RIGHT ARROW
13    0D  ^M  CR (CARRIAGE RETURN)**      29  1D  ^]  GROUP SEPARATOR (GS) LEFT ARROW
14    0E  ^N  SO (SHIFT OUT)              30  1E  ^^  RECORD SEPARATOR (RS) UP ARROW
15    0F  ^O  SI (SHIFT IN)               31  1F  ^_  UNIT SEPARATOR (US) DOWN ARROW

更新:最近我不得不重新访问这个问题。取决于终端设置/太阳天气预报,但是我注意到grep无法找到许多unicode或扩展字符。尽管在直觉上它们应该匹配范围0x80到0xff,但是3字节和4字节的unicode字符没有被匹配。??? 有人能解释一下吗?是的。@frabjous问了,@calandoa解释说应该使用 LC_ALL=C 来设置命令的区域设置以使grep匹配。

例如,我的区域设置为 LC_ALL= empty

$ locale
LANG=en_IE.UTF-8
LC_CTYPE="en_IE.UTF-8"
.
.
LC_ALL=

使用LC_ALL=的grep可以匹配2字节编码字符但不能匹配3或4字节编码字符:

$ grep -P -n "[\x00-\x08\x0E-\x1F\x80-\xFF]" notes_unicode_emoji_test
5:© copyright c2a9
7:call  underscore c2a0
9:CTRL
31:5 © copyright
32:7 call  underscore

使用LC_ALL=C的grep似乎可以匹配所有你想要的扩展字符:

$ LC_ALL=C grep --color='auto' -P -n "[\x80-\xFF]" notes_unicode_emoji_test  
1:���� unicode dashes e28090
3:��� Heart With Arrow Emoji - Emojipedia == UTF8? f09f9298
5:� copyright c2a9
7:call� underscore c2a0
11:LIVE��E! ���������� ���� ���������� ���� �� �� ���� ����  YEOW, mix of japanese and chars from other e38182 e38184 . . e0a487
29:1 ���� unicode dashes
30:3 ��� Heart With Arrow Emoji - Emojipedia == UTF8 e28090
31:5 � copyright
32:7 call� underscore
33:11 LIVE��E! ���������� ���� ���������� ���� �� �� ���� ����  YEOW, mix of japanese and chars from other
34:52 LIVE��E! ���������� ���� ���������� ���� �� �� ���� ����  YEOW, mix of japanese and chars from other
81:LIVE��E! ���������� ���� ���������� ���� �� �� ���� ����  YEOW, mix of japanese and chars from other

我觉得这个 Perl 匹配(部分在 StackOverflow 上找到)或者顶部答案中的反向 grep,似乎都能找到所有“奇怪”的和“美妙的”“非 ASCII”字符,而不需要设置本地环境。

$ grep --color='auto' -P -n "[^\x00-\x7F]" notes_unicode_emoji_test

$ perl -ne 'print "$. $_" if m/[\x00-\x08\x0E-\x1F\x80-\xFF]/' notes_unicode_emoji_test  

1 ‐‐ unicode dashes e28090
3  Heart With Arrow Emoji - Emojipedia == UTF8? f09f9298
5 © copyright c2a9
7 call  underscore c2a0
9 CTRL-H CHARS URK URK URK 
11 LIVE‐E! あいうえお かが アイウエオ カガ ᚊ ᚋ ซฌ आइ  YEOW, mix of japanese and chars from other e38182 e38184 . . e0a487
29 1 ‐‐ unicode dashes
30 3  Heart With Arrow Emoji - Emojipedia == UTF8 e28090
31 5 © copyright
32 7 call  underscore
33 11 LIVE‐E! あいうえお かが アイウエオ カガ ᚊ ᚋ ซฌ आइ  YEOW, mix of japanese and chars from other
34 52 LIVE‐E! あいうえお かが アイウエオ カガ ᚊ ᚋ ซฌ आइ  YEOW, mix of japanese and chars from other
73 LIVE‐E! あいうえお かが アイウエオ カガ ᚊ ᚋ ซฌ आइ  YEOW, mix of japanese and chars from other

以下是首选的非ASCII字符查找器:

$ perl -ne 'print "$. $_" if m/[\x00-\x08\x0E-\x1F\x80-\xFF]/' notes_unicode_emoji_test

就像最佳答案中所说的,反向grep:

$ grep --color='auto' -P -n "[^\x00-\x7F]" notes_unicode_emoji_test

就像最佳答案一样,但加上LC_ALL=C

$ LC_ALL=C grep --color='auto' -P -n "[\x80-\xFF]" notes_unicode_emoji_test

1
在问题上方的评论中,感谢@calandoa和frabjous的回答,解释了为什么grep无法匹配编码超过2个字节的字符。在grep命令之前使用LC_ALL=C。 - gaoithe
1
非常感谢您在800个赞之下发布答案!我的问题是0x02字符。您可能希望将“使用实际示例”放在顶部,因为您真的不需要阅读整篇文章来确定是否存在该问题。 - Noumenon
1
我知道,这是一个非常老的答案,而且详细得令人痛苦,但对我和其他人来说确实很有用。你是对的,我在顶部添加了TLDR;。 - gaoithe

10
以下代码有效:
find /tmp | perl -ne 'print if /[^[:ascii:]]/'

/tmp替换为您想要搜索的目录名称。

4
在Mac上,这个功能有效,而大多数基于grep的功能则无法实现。 - Matthias Fripp
对于Mac电脑:find ./folder -print -exec perl -ne 'print if /[^[:ascii:]]/' {} \; - undefined

2
这种方法应该适用于任何符合POSIX标准的awkiconv版本。 我们还可以利用filetr
当然,curl不是POSIX的。
上述解决方案在某些情况下可能更好,但它们似乎依赖于GNU/Linux实现或额外的工具。 只需以某种方式获取一个示例文件:

$ curl -LOs http://gutenberg.org/files/84/84-0.txt

$ file 84-0.txt

84-0.txt: UTF-8 Unicode (with BOM) text, with CRLF line terminators

搜索UTF-8字符:

$ awk '/[\x80-\xFF]/ { print }' 84-0.txt

或非ASCII字符(实际上不是POSIX,参见下面的可能解决方案)

$ awk '/[^[:ascii:]]/ { print }' 84-0.txt

将UTF-8转换为ASCII,删除有问题的字符(包括BOM,它本来就不应该出现在UTF-8中):

$ iconv -c -t ASCII 84-0.txt > 84-ascii.txt

检查一下:

$ file 84-ascii.txt

84-ascii.txt: ASCII text, with CRLF line terminators

调整它以去除DOS行尾/ ^M("CRLF行终止符"):

$ tr -d '\015' < 84-ascii.txt > 84-tweaked.txt && file 84-tweaked.txt

84-tweaked.txt:ASCII文本

这种方法会丢弃无法处理的任何“坏”字符,因此您可能需要对输出进行清理/验证。 YMMV

>> 更新 << 我最近使用了类似于这个的东西:

$ LC_ALL=C tr -d '[:print:]' < 84-0.txt | fold -w 1 | sort -u | sed -n l

但我不确定它有多可移植,但它给我提供了自动替换字符或字符串的选项。

我现在没有快速访问真正的UNIX,但我认为这些都是符合POSIX标准的选项和开关。我知道它运行得相当快。 YMMV。


awk 解决方案适用于 BSD。 - Clint Pachl
/[^[:ascii:]]/ 在任何符合 POSIX 标准的 awk 中都不应该是有效的。 - RARE Kpop Manifesto
你可能是对的。我可能在某个地方误读了什么。 - Kajukenbo
是的,我找到了我误读的地方。 "POSIX标准定义了12个字符类。下表列出了所有12个字符类,以及一些正则表达式引擎还支持的[:ascii:]和[:word:]类别。" - Kajukenbo

1
奇怪的是,我今天不得不做这个!最后我使用了Perl,因为我无法让grep/egrep工作(即使在-P模式下)。大致如下:
cat blah | perl -en '/\xCA\xFE\xBA\xBE/ && print "found"'

对于Unicode字符(例如下面示例中的\u2212),请使用此方法:

find . ... -exec perl -CA -e '$ARGV = @ARGV[0]; open IN, $ARGV; binmode(IN, ":utf8"); binmode(STDOUT, ":utf8"); while (<IN>) { next unless /\N{U+2212}/; print "$ARGV: $&: $_"; exit }' '{}' \;

在这种情况下,您可能需要检查 https://dev59.com/43A75IYBdhLWcg3w-OVA#3208902 中提到的语言环境。 - user8162

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接