如何使用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个回答

1
这个命令可以帮助你找到一个Unicode字符,了解如何搜索一个Unicode字符可能会很有趣。你只需要知道UTF8编码中的代码即可。
grep -v $'\u200d'

我并不是专家,但我知道这不是UTF8表示,它可能是UTF16或者UTF32,或者UCS16。对于一个2字节的码点,这三个可能都是相同的。 - Baxissimo
@Baxissimo:那显然你不够了解。这确实生成了一个3字节的UTF-8兼容序列\342\200\215。这也不能同时是UTF-16UTF-32,因为UTF-32需要对所有代码点进行NUL字节填充,但可以在little endian中将UTF-16字节序列定位于UTF-32中作为前缀子字符串或在big endian中作为后缀子字符串。 - RARE Kpop Manifesto

1
寻找所有非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,普通的grep命令将无法正确工作。

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

非ASCII字符范围为x80-xFF,因为字符串包含空格,所以正范围中加入了它。

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

为了获得可读的Shell输出,uchardet $file会猜测文件编码,并将其传递给iconv进行自动插值。


这非常有用,因为提到了uchardet命令。感谢您的提示! - bballdave025

0
这对我来说有效。
备注:
- 在Ubuntu 22.04中,如果不在前面使用`LC_ALL=C`,它将无法工作。 - 因为在我的情况下行很长,所以我只想看到非ASCII字符以及每个匹配发生的行和绝对偏移量。 - `-o`:仅匹配,`-b`:匹配的字节偏移量,`-l`:匹配的行号,`-P`:Perl正则表达式。

grep非ASCII字符:

命令:

LC_ALL=C grep --color='auto' -obnP  "[\x80-\xFF]" file.xml

输出:

868:31879:�
868:106287:�
868:106934:�
868:107349:�
868:254456:�
868:254678:�
868:286403:�
870:315585:�
870:389741:�
870:390388:�
870:390803:�
870:537910:�
870:538132:�
870:569811:�
870:598916:�
870:673324:�
870:673971:�
870:674386:�
870:821493:�
870:821715:�
870:853440:�
871:882578:�
871:956734:�
871:957381:�
871:957796:�
871:1104903:�
871:1105125:�
871:1136804:�

在十六进制中查找非ASCII字符:

命令:

# Splitting the output of grep to ':'. Then printing the first 2 tokens and passing the 3rd one from xxd to convert to byte hex
LC_ALL=C grep --color='auto' -obnP  "[\x80-\xFF]" file.xml |\
xargs -I{} bash -c "echo {}|awk 'BEGIN { FS = \":\" };{printf \"%s:%s:\",\$1, \$2; print \$3 | \"xxd -p -l1\" }'"

输出:

868:31879:96
868:106287:92
868:106934:92
868:107349:92
868:254456:92
868:254678:92
868:286403:92
870:315585:96
870:389741:92
870:390388:92
870:390803:92
870:537910:92
870:538132:92
870:569811:92
870:598916:96
870:673324:92
870:673971:92
870:674386:92
870:821493:92
870:821715:92
870:853440:92
871:882578:96
871:956734:92
871:957381:92
871:957796:92
871:1104903:92
871:1105125:92
871:1136804:92

0

更新 1:将主要的 awk 代码从 9 更改为 NF,以处理前导和尾随的 ASCII


使用 awk 保持简单 - 利用 RS 实现无需区域调整的自动驾驶:

__=$'123=pqr:\303\606?\414#45&6\360\641\266\666]>^{(\13xyz'

printf '%s' "$__" | od
0000000        1026765361       980578672       205489859       641020963
           1   2   3   =   p   q   r   :   Æ  **   ?  \f   #   4   5   &
          061 062 063 075 160 161 162 072 303 206 077 014 043 064 065 046
           1   2   3   =   p   q   r   :   ?  86   ?  ff   #   4   5   &
           49  50  51  61 112 113 114  58 195 134  63  12  35  52  53  38
           31  32  33  3d  70  71  72  3a  c3  86  3f  0c  23  34  35  26

0000020        3064066102      1581145526      2013997179           31353
           6    **  **  **   ]   >   ^   {   (  \v   x   y   z        
          066 360 241 266 266 135 076 136 173 050 013 170 171 172        
           6   ?   ?   ?   ?   ]   >   ^   {   (  vt   x   y   z        
           54 240 161 182 182  93  62  94 123  40  11 120 121 122        
           36  f0  a1  b6  b6  5d  3e  5e  7b  28  0b  78  79  7a        

0000036
printf '%s' "$__"
123=pqr:Æ?
          #45&6]>^{(
                      xyz
mawk NF RS='[\0-\577]+' | gcat -b
 1  Æ
 2  

为单行输出设置自定义ORS

gawk NF RS='[\0-\577]+' ORS='|' | gcat -b
Æ||

如果您坚持使用 nawk,则需要修改 RS 以...
nawk NF RS='(\\0|[\1-\177]+)+'

由于 nawk 在处理字符类中的 \0\\0 时存在问题,因此必须将其从 [...] 中取出,并用令人不安的冗长替代方案进行替换


0

如果你想要抓取/搜索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]))

0

ripgrep (rg)


这是一个关于编程的内容,其中提到了“ripgrep (rg)”。
LC_ALL=C rg -v '[[:ascii:]]'  # --invert-match

brew install ripgrep,也可以在Linux上安装。

也许我错过了什么,但我发现这是最简单和快速的替代方法。


0
nawk    '/[\200-\377]/'
mawk    '/[\200-\377]/'

gawk -b '/[\200-\377]/'
gawk -e '!/^[\0-\177]*$/'

gawk的Unicode模式下,仅仅使用`/[^\0-\177]/`是不够的,因为它会忽略所有格式错误的序列和/或任意字节,比如\371
否则,你必须以交替形式列出所有128个字节,这样做非常丑陋。

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