Powershell 二进制 grep

Powershell binary grep

有没有办法在powershell中判断指定的文件是否包含指定的字节数组(任意位置)?

类似于:

fgrep --binary-files=binary "$data" "$filepath"

当然,我可以写一个简单的实现:

function posOfArrayWithinArray {
    param ([byte[]] $arrayA, [byte[]]$arrayB)
    if ($arrayB.Length -ge $arrayA.Length) {
        foreach ($pos in 0..($arrayB.Length - $arrayA.Length)) {
            if ([System.Linq.Enumerable]::SequenceEqual(
                $arrayA,
                [System.Linq.Enumerable]::Skip($arrayB, $pos).Take($arrayA.Length)
            )) {return $pos}
        }
    }
    -1
}

function posOfArrayWithinFile {
    param ([byte[]] $array, [string]$filepath)
    posOfArrayWithinArray $array (Get-Content $filepath -Raw -AsByteStream)
}

// They return position or -1, but simple $false/$true are also enough for me.

— 但它 非常 慢。

下面的代码可能会更快,但您必须在二进制文件上进行测试:

function Get-BinaryText {
    # converts the bytes of a file to a string that has a
    # 1-to-1 mapping back to the file's original bytes. 
    # Useful for performing binary regular expressions.
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateScript( { Test-Path $_ -PathType Leaf } )]
        [Alias('FullName','FilePath')]
        [string]$Path
    )

    $Stream = New-Object System.IO.FileStream -ArgumentList $Path, 'Open', 'Read'

    # Note: Codepage 28591 returns a 1-to-1 char to byte mapping
    $Encoding     = [Text.Encoding]::GetEncoding(28591)
    $StreamReader = New-Object System.IO.StreamReader -ArgumentList $Stream, $Encoding
    $BinaryText   = $StreamReader.ReadToEnd()

    $Stream.Dispose()
    $StreamReader.Dispose()

    return $BinaryText
}

# enter the byte array to search for here
# for demo, I'll use 'SearchMe' in bytes
[byte[]]$searchArray = 83,101,97,114,99,104,77,101

# create a regex from the $searchArray bytes
# 'SearchMe' --> '\x53\x65\x61\x72\x63\x68\x4D\x65'
$searchString = ($searchArray | ForEach-Object { '\x{0:X2}' -f $_ }) -join ''
$regex = [regex]$searchString

# read the file as binary string
$binString = Get-BinaryText -Path 'D:\test.bin'

# use regex to return the 0-based starting position of the search string
# return -1 if not found
$found = $regex.Match($binString)
if ($found.Success) { $found.Index } else { -1}

我确定以下方法可以作为解决方法:

(Get-Content $filepath -Raw -Encoding 28591).IndexOf($fragment)

——即当我们指定二进制安全编码[=26=时,PowerShell strings(实际上,.NET System.Strings)可以成功匹配任何字节].当然,我们需要对文件和片段使用相同的编码,并且编码必须是真正的二进制安全(例如 1250、1000 和 28591 适合,但各种 Unicode 种类(包括默认的无 BOM 的 UTF-8)不这样做,因为它们会将任何格式不正确的代码单元转换为相同的 replacement character (U+FFFD)). Thanks to Theo 以进行澄清。

在较旧的 PowerShell 上,您可以使用:

[System.Text.Encoding]::GetEncoding(28591).
    GetString([System.IO.File]::ReadAllBytes($filepath)).
    IndexOf($fragment)

遗憾的是,我还没有找到一种普遍匹配序列的方法(即匹配序列与任何项目类型的通用方法:整数、对象等)。我相信它 必须 存在于 .NET 中(特别是存在字符序列的特定实现)。希望有人会建议它。

只是将我的意见正式化并同意您的意见:

I dislike the idea of converting byte sequences to character sequences at all (I'd better have functionality to match byte (or other) sequences as they are), among the conversion-to-character-strings-implying solutions this seems to be one of the quickest

性能

字符串操作通常很昂贵,但重新初始化一个 LINQ call is apparently pretty expensive as well. I guess, that you might presume that the native algorithms for the PowerShell string representation and methods (operators) like -Like 同时完全压缩。

内存

除了一些已确定的性能缺点外,将每个字节转换为十进制字符串表示形式也存在内存缺点。在有目的的解决方案中,每个字节将平均占用 2.57 个字节(取决于每个字节的小数位数:(1 * 10 / 256) + (2 * 90 /256) + (3 * 156 / 256))。此外,您将 use/need 用于分隔数字表示的额外字节。总的来说,这会将序列增加大约 3.57 倍!
您可能会考虑通过例如节省字节将其转换为 hexadecimal and/or 组合分隔符,但这可能会再次导致昂贵的转换。

简单

不管怎样,简单的方法可能还是最有效的。
这归结为以下简化语法:

" $Sequence " -Like "* $SubSequence *" # $True if $Sequence contains $SubSequence

(其中 $Sequence$SubSequence 是二进制数组类型:[Byte[]]

注 1:变量周围的空格很重要。如果 1(或 2)位字节表示与 2(或 3)位字节表示重叠,这将防止误报。例如:123 59 74 在字符串表示中包含 23 59 7 但在实际字节中不包含。

注2:这个语法只会告诉你是否 $arrayA 包含 $arrayB$True$False)。没有线索 $arrayB 实际上位于 $arrayA 的什么地方。如果你需要知道这个,或者例如想把$arrayB换成别的,参考这个答案: Methods to hex edit binary files via PowerShell .

抱歉,补充回答。这样做并不常见,但这个普遍问题引起了我的兴趣,我最初的“”答案的方法和信息完全不同。顺便说一句,如果您正在寻找对“我相信它必须存在于.NET”这个问题的肯定回答来接受答案,它可能不会发生,同样的问题存在于Whosebug 结合 C#.NetLinq.
进行搜索 无论如何,到目前为止,没有人能够找到 single assumed .Net 命令这一事实,可以理解的是,有几个 semi-.Net 解决方案是有目的的但我相信这会对通用函数造成一些不希望的开销。
假设您 ByteArray (正在搜索的字节数组)SearchArray ( byte array to be searched) 是完全随机的。 ByteArray 中的每个字节与 SearchArray 的第一个字节匹配的可能性只有 1/256。在这种情况下,您不必进一步查看,如果它 确实 匹配,则第二个字节也匹配的机会是 1/2562, 等等。这意味着内部循环只会 运行 大约 1.004 倍于外部循环。换句话说,内循环之外(但在外循环中)的所有性能几乎与内循环中的一样重要!
请注意,这也意味着 500Kb 随机序列存在于 100Mb 随机序列中的几率几乎为零。 (那么,你给定的二进制序列实际上有多随机?如果它们远非随机,我认为你需要在你的问题中添加更多细节)。根据我的假设,更糟糕的情况是 ByteArray 存在相同的字节(例如 0, 0, 0, ..., 0, 0, 0)和相同的 SearchArray以不同字节结尾的字节(例如 0, 0, 0, ..., 0, 0, 1)。

基于此,它再次显示(我也在其他一些答案中证明了这一点)本机 PowerShell 命令并没有那么糟糕,甚至可能胜过 .Net/Linq 命令在某些情况下。在我的测试中,下面的 Find-Bytes 函数比你问题中的函数快 20% 到两倍:

查找字节

Returns -Search 字节序列在 -Bytes 字节序列中的索引。如果未找到搜索序列 $Null ([System.Management.Automation.Internal.AutomationNull]::Value) 是 returned.

参数

-Bytes
要搜索的字节数组

-Search
要搜索的字节数组

-Start
定义在 Bytes 序列中从哪里开始搜索(默认值:0

-All
默认情况下,只有找到的第一个索引才会被 returned。使用 -All 切换到 return 找到的任何其他搜索序列的剩余索引。

Function Find-Bytes([byte[]]$Bytes, [byte[]]$Search, [int]$Start, [Switch]$All) {
    For ($Index = $Start; $Index -le $Bytes.Length - $Search.Length ; $Index++) {
        For ($i = 0; $i -lt $Search.Length -and $Bytes[$Index + $i] -eq $Search[$i]; $i++) {}
        If ($i -ge $Search.Length) { 
            $Index
            If (!$All) { Return }
        } 
    }
}

用法示例:

$a = [byte[]]("the quick brown fox jumps over the lazy dog".ToCharArray())
$b = [byte[]]("the".ToCharArray())

Find-Bytes -all $a $b
0
31

基准
请注意,您应该打开一个新的 PowerShell 会话以正确地对其进行基准测试,因为 Linq 使用的大型缓存不适用于您的用例。

$a = [byte[]](&{ foreach ($i in (0..500Kb)) { Get-Random -Maximum 256 } })
$b = [byte[]](&{ foreach ($i in (0..500))   { Get-Random -Maximum 256 } })

Measure-Command {
    $y = Find-Bytes $a $b
}

Measure-Command {
    $x = posOfArrayWithinArray $b $a
}