如何对所有非ascii字符进行grep ?

我有几个非常大的XML文件,我试图找到包含非ascii字符的行。我试过以下几种方法:

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

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

是我的语法错误还是我做错了什么?我也试过:

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

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

338971 次浏览

奇怪的是,我今天不得不这么做!我最终使用了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 }' '{}' \;

以下是我的工作:

grep -P "[\x80-\xFF]" file.xml

非ascii字符从0x80开始,在查看字节时转到0xFF。Grep(和家族)不做Unicode处理,将多字节字符合并为一个实体,以便进行正则表达式匹配。我的grep中的-P选项允许在字符类中使用\xdd转义来完成你想要的。

最简单的方法是定义一个非ascii字符…作为非ASCII字符的字符。

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

如有必要,在^后面添加一个制表符。

设置LC_COLLATE=C可以避免在许多locale中字符范围含义的令人讨厌的意外。设置LC_CTYPE=C是必须的,以匹配单字节字符-否则该命令将错过当前编码中的无效字节序列。设置LC_ALL=C完全避免了语言环境相关的影响。

在perl中

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

您可以使用以下命令:

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可能警告未实现 特性。< / p >

不像上面大多数解决方案那样对非ASCII字符的字节范围进行假设,在我看来,明确ASCII字符的实际字节范围会稍微好一些。

所以第一个解决方案是:

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

(基本上greps十六进制ASCII范围以外的任何字符:从\x00到\x7F)

在Mountain Lion上,(由于BSD grep缺乏对PCRE的支持)不能工作,但通过Homebrew安装pcre,下面的代码也可以工作:

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

大家能想到什么优点或缺点吗?

这是我发现的另一个变体,它产生了与grep搜索接受答案中的[\x80-\xFF]不同的完全结果。也许它会有用的人找到额外的非ascii字符:

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

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

知道如何搜索一个unicode字符可能会很有趣。该命令可以提供帮助。你只需要知道UTF8的代码

grep -v $'\u200d'

下面的代码工作:

find /tmp | perl -ne 'print if /[^[:ascii:]]/'

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

搜索不可打印字符。TLDR;执行概要

  1. 搜索控制字符和扩展unicode
  2. locale设置,例如LC_ALL=C需要让grep做你可能期望的扩展unicode

所以首选的非ascii字符查找器:

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

与top answer一样,逆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

。。更多。关于这个令人痛苦的细节:……

我同意上面隐藏在评论中的哈维,搜索不可打印字符通常更有用,或者当你真的应该考虑不可打印时,很容易想到非ascii。哈维建议“使用这个:"[^\n -~]"。为DOS文本文件添加\r。转换为“[^\x0A\x020-\x07E]”并为CR添加\x0D”

此外,在搜索不可打印字符时,在grep中添加-c(显示匹配的模式计数)也很有用,因为匹配的字符串会使终端混乱。

我发现添加范围0-8和0x0e-0x1f(到0x80-0xff范围)是一个有用的模式。这包括TAB, CR和LF以及一两个不常见的可打印字符。所以IMHO a 一个非常有用(尽管粗糙)的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 to grep当前目录下的所有文件的实际示例:

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

您可能希望不时地调整grep。例如,BS(0x08 - backspace)字符用于某些可打印文件或排除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

我最近不得不重新审视这个问题。并且,YYMV取决于终端设置/太阳天气预报,但是。我注意到Grep没有找到很多unicode或扩展字符。尽管直观上它们应该匹配0x80到0xff的范围,但3字节和4字节的unicode字符并不匹配。 ??有人能解释一下吗?是的。@frabjous问,@calandoa解释说LC_ALL=C应该用来为命令设置语言环境,以使grep匹配。

例如,我的locale LC_ALL=为空

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

grep with LC_ALL= empty匹配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

grep with LC_ALL=C似乎匹配了你想要的所有扩展字符:

$ 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”字符而不设置locale:

$ 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

与top answer一样,逆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

查找所有非ascii字符会给人留下这样的印象:要么查找unicode字符串,要么打算单独剥离这些字符。

对于前者,可以尝试其中一个(变量file用于自动化):

 file=file.txt ; LC_ALL=C grep -Piao '[\x80-\xFF\x20]{7,}' $file | iconv -f $(uchardet $file) -t utf-8


file=file.txt ; pcregrep -iao '[\x80-\xFF\x20]{7,}' $file | iconv -f $(uchardet $file) -t utf-8


file=file.txt ; pcregrep -iao '[^\x00-\x19\x21-\x7F]{7,}' $file | iconv -f $(uchardet $file) -t utf-8

如前面的答案所述,如果没有LC_ALL=C, Vanilla grep将无法正常工作。

ASCII范围是x00-x7F,空格是x20,因为字符串有空格,所以负范围省略了它。

非ascii范围是x80-xFF,因为字符串有空格,所以正范围添加它。

String假定在范围内至少有7个连续字符。{7,}

对于shell可读输出,uchardet $file返回文件编码的猜测值,该值被传递给iconv用于自动插值。

如果你试图抓取/grep utf8兼容的多字节字符,使用这个:

(                     [\302-\337][\200-\277]|
[\340][\240-\277][\200-\277]|
[\355][\200-\237][\200-\277]|
[\341-\354\356-\357][\200-\277][\200-\277]|
[\360][\220-\277][\200-\277][\200-\277]|
[\361-\363][\200-\277][\200-\277][\200-\277]|
[\364][\200-\217][\200-\277][\200-\277]  )


* please delete all newlines, spaces, or tabs in between (..)


* feel free to use bracket ranges {1,3} etc to optimize
the redundant listings of [\200-\277]. but don't change that
[\200-\277]+, as that might result in invalid encodings
due to either insufficient or too many continuation bytes


* although some historical UTF-8 references considers 5- and
6-byte encodings to be valid, as of Unicode 13 they only
consider up to 4-bytes

我甚至针对随机二进制文件测试了这个字符串,它将报告与gnu-wc相同的多字节字符计数。

如果你需要完整的UTF8匹配字符串,在前面的(后面添加另一个[\000-\177]|

这个正则表达式确实很可怕,但它也符合posix,跨语言和跨平台兼容(不依赖于任何特殊的正则表达式符号,(应该)完全符合UTF-8 (Unicode 13),并且完全独立于语言环境设置。

如果你正在运行grep,请使用grep -P

如果您只需要其他字节,那么其他人已经建议过了。

如果你需要11,172个nfc组成的韩文

(([\352][\260-\277]|[\353\354][\200-\277]|
[\355][\200-\235])[\200-\277]|[\355][\236][\200-\243])

如果你需要日本的平假名+片假名,它是

([\343]([\201-\203][\200-\277]|[\207][\260-\277]))
这个方法应该适用于任何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

$ 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 text

此方法将丢弃任何“;坏的”;它无法处理的字符,因此可能需要对输出进行消毒/验证。YMMV