grepping 二进制文件和 UTF16

Posted

技术标签:

【中文标题】grepping 二进制文件和 UTF16【英文标题】:grepping binary files and UTF16 【发布时间】:2011-04-14 18:04:24 【问题描述】:

标准grep/pcregrep 等可以方便地与二进制文件一起用于 ASCII 或 UTF8 数据 - 有没有一种简单的方法可以让它们也尝试 UTF16(最好同时尝试,但可以这样做)?

无论如何,我试图获取的数据都是 ASCII(库中的引用等),只是找不到,因为有时任何两个字符之间都有 00,有时没有。

我看不出有任何方法可以在语义上完成它,但这些 00 应该可以解决问题,除非我不能在命令行上轻松使用它们。

【问题讨论】:

...如果字符长两个字节,则不是 ASCII。 我指的是 ASCII 字符范围(U+0000 到 U+007F),而不是 ASCII 编码。 【参考方案1】:

最简单的方法是将文本文件转换为 utf-8 并将其通过管道传输到 grep:

iconv -f utf-16 -t utf-8 file.txt | grep query

我试图做相反的事情(将我的查询转换为 utf-16),但似乎 grep 不喜欢那样。我认为这可能与字节顺序有关,但我不确定。

似乎 grep 会将 utf-16 的查询转换为 utf-8/ascii。这是我尝试过的:

grep `echo -n query | iconv -f utf-8 -t utf-16 | sed 's/..//'` test.txt

如果 test.txt 是一个 utf-16 文件,这将不起作用,但如果 test.txt 是 ascii 则它确实有效。我只能得出结论,grep 正在将我的查询转换为 ascii。

编辑:这是一个非常疯狂的工作,但没有给你非常有用的信息:

hexdump -e '/1 "%02x"' test.txt | grep -P `echo -n Test | iconv -f utf-8 -t utf-16 | sed 's/..//' | hexdump -e '/1 "%02x"'`

它是如何工作的?好吧,它将您的文件转换为十六进制(没有 hexdump 通常适用的任何额外格式)。它通过管道将其输入 grep。 Grep 使用的查询是通过将您的查询(不带换行符)回显到 iconv 中构建的,iconv 将其转换为 utf-16。然后将其通过管道传输到 sed 以删除 BOM(用于确定字节顺序的 utf-16 文件的前两个字节)。然后将其通过管道传输到 hexdump,以便查询和输入相同。

不幸的是,如果只有一个匹配项,我认为这最终会打印出整个文件。如果二进制文件中的 utf-16 存储在与您的机器不同的字节序中,这也将不起作用。

EDIT2:知道了!!!!

grep -P `echo -n "Test" | iconv -f utf-8 -t utf-16 | sed 's/..//' | hexdump -e '/1 "x%02x"' | sed 's/x/\\\\x/g'` test.txt

这会在文件 test.txt 中搜索字符串 Test(在 utf-16 中)的十六进制版本

【讨论】:

iconv 不起作用,因为它是一个包含大量非 utf-16 数据的二进制文件,iconv 在第一个错误时退出。 哎呀...出于好奇,我仍在考虑给 grep 一个 utf-16 查询(我不认为它正在转换,因为它并不真正知道编码,它必须这样做其他奇怪的东西),如果我想出什么东西,我会告诉你的。 经过轻微修改后似乎可以正常工作:pcregrep `echo -n "test" | iconv -f utf-8 -t utf-16le | hexdump -e '/1 "x%02x"' | sed 's/x/\\\\x/g'` <binary.file。最重要的是,它不需要 utf-16 字符位于 2 字节边界上——所有以前的方法都存在很大问题。甚至可以使用-i 太棒了!我发现我遇到的问题是反引号。出于某种原因,它们返回 utf-8 字符串,并转义反斜杠。这就是 sed 有四个 '\' 的原因。 一些 sed 是 unicode 感知的,那么它将去除 unicode 标记之后的前两个字符,而不是标记字符。将 sed 's/..//' 替换为 tail -c +3【参考方案2】:

您可以在搜索字符串中显式包含空值 (00),尽管您会得到带有空值的结果,因此您可能希望将输出重定向到一个文件,以便您可以使用合理的编辑器查看它,或者通过管道传输它通过 sed 替换空值。在 *.utf16.txt 中搜索“bar”:

grep -Pa "b\x00a\x00r" *.utf16.txt | sed 's/\x00//g'

“-P”告诉 grep 接受 Perl 正则表达式语法,它允许 \x00 扩展为 null,而 -a 告诉它忽略 Unicode 看起来像二进制的事实。

【讨论】:

技术不错,没想到这个。 grep 的 -a 标志是这里的魔法。假设您没有要搜索的大文件(在这种情况下,这可能会太慢),您可以通过指定. 而不是\x00 来简化输入。 . 将匹配任何内容,而不仅仅是空值。这可能并不总是你想要的,但可能大部分时间都会好的。通常,清除空值的 sed 也不是必需的 - 它们不会在输出上打印任何内容。因此,对于您的示例,只需 grep -a b.a.r *.utf16.txt 即可。 我必须尝试记住-P 选项以允许\xnn。我在没有 perl 的情况下这样做的方式是使用“。”即任何单个字符以及@nirmal 如何在下面回答【参考方案3】:

我发现以下解决方案最适合我,来自https://www.splitbits.com/2015/11/11/tip-grep-and-unicode/

Grep 不能很好地与 Unicode 配合使用,但可以解决。例如,要查找,

Some Search Term

在 UTF-16 文件中,使用正则表达式忽略每个字符中的第一个字节,

S.o.m.e. .S.e.a.r.c.h. .T.e.r.m 

另外,告诉 grep 将文件视为文本,使用 '-a',最终命令如下所示,

grep -a 'S.o.m.e. .S.e.a.r.c.h. .T.e.r.m' utf-16-file.txt

【讨论】:

【参考方案4】:

我在转储 Windows 注册表后一直使用这个,因为它的输出是 unicode。这是在 Cygwin 下运行的。

$ regedit /e registry.data.out
$ file registry.data.out
registry.data.out: Little-endian **UTF-16 Unicode text**, with CRLF line terminators

$ sed 's/\x00//g' registry.data.out | egrep "192\.168"
"Port"="192.168.1.5"
"IPSubnetAddress"="192.168.189.0"
"IPSubnetAddress"="192.168.102.0"
[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Print\Monitors\Standard TCP/IP Port\Ports\192.168.1.5]
"HostName"="192.168.1.5"
"Port"="192.168.1.5"
"LocationInformation"="http://192.168.1.28:1215/"
"LocationInformation"="http://192.168.1.5:80/WebServices/Device"
"LocationInformation"="http://192.168.1.5:80/WebServices/Device"
"StandaloneDhcpAddress"="192.168.173.1"
"ScopeAddressBackup"="192.168.137.1"
"ScopeAddress"="192.168.137.1"
"DhcpIPAddress"="192.168.1.24"
"DhcpServer"="192.168.1.1"
"0.0.0.0,0.0.0.0,192.168.1.1,-1"=""
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\192.168.1.5]
"HostName"="192.168.1.5"
"Port"="192.168.1.5"
"LocationInformation"="http://192.168.1.28:1215/"
"LocationInformation"="http://192.168.1.5:80/WebServices/Device"
"LocationInformation"="http://192.168.1.5:80/WebServices/Device"
"StandaloneDhcpAddress"="192.168.173.1"
"ScopeAddressBackup"="192.168.137.1"
"ScopeAddress"="192.168.137.1"
"DhcpIPAddress"="192.168.1.24"
"DhcpServer"="192.168.1.1"
"0.0.0.0,0.0.0.0,192.168.1.1,-1"=""
"MRU0"="192.168.16.93"
[HKEY_USERS\S-1-5-21-2054485685-3446499333-1556621121-1001\Software\Microsoft\Terminal Server Client\Servers\192.168.16.93]
"A"="192.168.1.23"
"B"="192.168.1.28"
"C"="192.168.1.200:5800"
"192.168.254.190::5901/extra"=hex:02,00
"00"="192.168.254.190:5901"
"ImagePrinterPort"="192.168.1.5"

【讨论】:

我想这种方式误报的可能性很小,但它可能是 99.9% 的时间想要的。它在 MINGW64 Git Bash 下也适用于我。 这可以组合成一个 sed 命令:> sed -ne "s/\x00//g" -e "/192\.168/p"【参考方案5】:

ripgrep

使用ripgrep utility grep UTF-16 文件。

ripgrep 支持以 UTF-8 以外的文本编码搜索文件,例如 UTF-16、latin-1、GBK、EUC-JP、Shift_JIS 等。 (提供了一些自动检测UTF-16的支持。其他文本编码必须用-E/--encoding flag.专门指定)

示例语法:

rg sometext file

要转储所有行,请运行:rg -N . file

【讨论】:

ripgrep 太快了!谢谢【参考方案6】:

我需要递归地执行此操作,这就是我想出的:

find -type f | while read l; do iconv -s -f utf-16le -t utf-8 "$l" | nl -s "$l: " | cut -c7- | grep 'somestring'; done

这绝对可怕而且非常缓慢;我确定有更好的方法,我希望有人可以改进它——但我很着急:P

这些部件的作用:

find -type f

给出一个文件名的递归列表,其中包含相对于当前路径的文件名

while read l; do ... done

Bash 循环;对于文件路径列表的每一行,将路径放入$l 并在循环中执行操作。 (为什么我使用 shell 循环而不是 xargs,这样会快得多:我需要在输出的每一行前面加上当前文件的名称。如果我正在喂食,我想不出办法做到这一点一次将多个文件发送到 iconv,并且由于我将一次只处理一个文件,因此 shell 循环更容易语法/转义。)

iconv -s -f utf-16le -t utf-8 "$l"

转换$l中命名的文件:假设输入文件为utf-16 little-endian,并将其转换为utf-8。 -s 使 iconv 关闭任何转换错误(会有很多,因为此目录结构中的某些文件不是 utf-16)。此转换的输出到标准输出。

nl -s "$l: " | cut -c7-

这是一个 hack:nl 插入行号,但它恰好有一个“使用此任意字符串将数字与行分开”参数,因此我将文件名(后跟冒号和空格)放入其中.然后我使用cut 去掉行号,只留下文件名前缀。 (为什么我不使用sed:这样转义要容易得多。如果我使用 sed 表达式,我不得不担心文件名中有正则表达式字符,在我的情况下有很多。@987654332 @ 比 sed 笨得多,并且会完全按字面意思接受参数 -s,而 shell 会为我处理转义。)

所以,在这个管道结束时,我已经将一堆文件转换为 utf-8 行,以文件名为前缀,然后我 grep。如果有匹配项,我可以从前缀中知道它们在哪个文件中。

注意事项

这比grep -R 慢得多,因为我正在为每个文件生成iconvnlcutgrep 的新副本。太可怕了。 所有不是 utf-16le 输入的东西都会作为完全垃圾输出,所以如果有一个包含“somestring”的普通 ASCII 文件,这个命令不会报告它——你需要做一个普通的grep -R以及这个命令(如果你有多种 unicode 编码类型,比如一些 big-endian 和一些 little-endian 文件,你需要调整这个命令并针对每个不同的编码再次运行它)。 名称恰好包含“somestring”的文件将显示在输出中,即使它们的内容没有匹配项。

【讨论】:

我必须在 OS X 上执行find . -type f【参考方案7】:

ugrep(通用 grep)完全支持 Unicode、UTF-8/16/32 输入文件,检测无效的 Unicode 以确保正确的结果,显示文本和二进制文件,并且快速且免费:

ugrep 搜索 UTF-8/16/32 输入和其他格式。选项 --encoding 允许搜索许多其他文件格式,例如 ISO-8859-1 到 16、EBCDIC、代码页 437、850、858、1250 到 1258、MacRoman 和 KOI8。

详情请见ugrep on GitHub。

【讨论】:

【参考方案8】:

sed 语句超出了我的理解范围。我有一个简单的、远非完美的 TCL 脚本,我认为它在我的测试点上做得很好:

#!/usr/bin/tclsh

set insearch [lindex $argv 0]

set search ""

for set i 0 $i<[string length $insearch]-1 incr i 
    set search "$search[string range $insearch $i $i]."

set search "$search[string range $insearch $i $i]"

for set i 1 $i<$argc incr i 
    set file [lindex $argv $i]
    set status 0
    if ! [catch exec grep -a $search $file results options] 
        puts "$file: $results"
    

【讨论】:

【参考方案9】:

我将此作为评论添加到上面已接受的答案中,但为了使其更易于阅读。这允许您在一堆文件中搜索文本,同时还显示它正在查找文本的文件名。所有这些文件都有一个 .reg 扩展名,因为我正在搜索导出的 Windows 注册表文件。只需将 .reg 替换为任何文件扩展名即可。

// Define grepreg in bash by pasting at bash command prompt
grepreg ()

    find -name '*.reg' -exec echo  \; -exec iconv -f utf-16 -t utf-8  \; | grep "$1\|\.reg"


// Sample usage
grepreg SampleTextToSearch

【讨论】:

【参考方案10】:

您可以使用以下 Ruby 的单线:

ruby -e "puts File.open('file.txt', mode:'rb:BOM|UTF-16LE').readlines.grep(Regexp.new 'PATTERN'.encode(Encoding::UTF_16LE))"

为简单起见,这可以定义为 shell 函数,如:

grep-utf16()  ruby -e "puts File.open('$2', mode:'rb:BOM|UTF-16LE').readlines.grep(Regexp.new '$1'.encode(Encoding::UTF_16LE))"; 

然后以类似 grep 的方式使用它:

grep-utf16 PATTERN file.txt

来源:How to use Ruby's readlines.grep for UTF-16 files?

【讨论】:

虽然这可行,但对我来说,在 450,000 行的 UTF16LE 文本文件上比 ugrep 慢很多。

以上是关于grepping 二进制文件和 UTF16的主要内容,如果未能解决你的问题,请参考以下文章

如何读取 UTF-16 文件并将其内容与使用十六进制值定义的 wchar_t* 字符串文字进行比较

ASCII,GBK,和Unicode的UTF-8,UTF-16,UTF-32阐述

我可以让 git 将 UTF-16 文件识别为文本吗?

C# byte和10进制16进制相互转换

C# 解析16进制字符串。将16进制字符串转换成明文字符串

一句话理解字符编码(Unicode ,UTF8,UTF16)