代码之家  ›  专栏  ›  技术社区  ›  kervin

忽略二进制文件的PowerShell搜索脚本

  •  22
  • kervin  · 技术社区  · 15 年前

    我真的习惯了 grep -iIr 在Unix shell上,但是我还没有得到一个PowerShell等价物。

    基本上,上面的命令递归地搜索目标文件夹,并忽略二进制文件,因为使用了“-i”选项。此选项还等效于 --binary-files=without-match 选项,即 “将二进制文件视为与搜索字符串不匹配”

    到目前为止我一直在使用 Get-ChildItems -r | Select-String 作为我的PowerShell grep的替代品 Where-Object 补充。但我还没有找到一种方法来忽略所有二进制文件,比如 grep -I 命令确实如此。

    如何使用PowerShell筛选或忽略二进制文件?

    所以对于给定的路径,我只想 Select-String 搜索文本文件。

    编辑: 在谷歌上再花几个小时就产生了这个问题。 How to identify the contents of a file is ASCII or Binary . 这个问题说的是“ASCII”,但我相信作者的意思是“文本编码”,就像我自己。

    编辑: 似乎是一个 isBinary() 需要写来解决这个问题。可能是一个C命令行实用程序,使其更有用。

    编辑: 好像是什么 grep 正在做的是检查ASCII 努尔字节 或UTF-8 超长 . 如果这些存在,它将考虑文件二进制。这是单人间 内存() 打电话。

    2 回复  |  直到 10 年前
        1
  •  29
  •   Richard Berg    15 年前

    在Windows上,文件扩展名通常足够好:

    # all C# and related files (projects, source control metadata, etc)
    dir -r -fil *.cs* | ss foo
    
    # exclude the binary types most likely to pollute your development workspace
    dir -r -exclude *exe, *dll, *pdb | ss foo
    
    # stick the first three lines in your $profile (refining them over time)
    $bins = new-list string
    $bins.AddRange( [string[]]@("exe", "dll", "pdb", "png", "mdf", "docx") )
    function IsBin([System.IO.FileInfo]$item) { !$bins.Contains($item.extension.ToLower()) }
    dir -r | ? { !IsBin($_) } | ss foo
    

    但当然,文件扩展名并不完美。没有人喜欢键入长列表,而且很多文件的名称都是错误的。

    我认为UNIX在文件系统中没有任何特殊的二进制和文本指示器。(嗯,vms确实有,但我怀疑这是你grep习惯的来源。)我看了grep-i的实现,很明显这只是一个基于文件第一块的快速n-dirty启发式方法。结果发现这是我的策略 a bit of experience 和。因此,我建议您选择适合Windows文本文件的启发式函数:

    • 检查至少1KB的文件。许多文件格式都以一个看起来像文本的头开始,但稍后会破坏解析器。现代硬件的工作方式是,读取50字节的I/O开销与读取4KB的I/O开销大致相同。
    • 如果您只关心直接的ASCII,那么一旦您看到字符范围[31-127加上CR和LF]之外的内容,就退出。您可能不小心排除了一些聪明的ASCII艺术,但是尝试将这些情况与二进制垃圾分开是非常重要的。
    • 如果要处理Unicode文本,请让MS库处理脏的工作。比你想象的要难。通过PowerShell,您可以轻松访问 IMultiLang2 interface (COM)或 Encoding.GetEncoding 静态方法(.net)。当然,他们仍然只是猜测。雷蒙德的评论 Notepad detection algorithm (以及与michael kaplan的链接)在决定如何混合和匹配平台提供的库之前,值得一看。
    • 如果结果很重要(即,一个缺陷将做一些比把grep控制台弄得乱七八糟的事情),那么不要害怕为了准确性而硬编码一些文件扩展名。例如,*.pdf文件虽然是二进制格式,但在前面偶尔会有几个kb的文本,这导致了上面链接的臭名昭著的错误。同样,如果文件扩展名可能包含XML或类似XML的数据,则可以尝试类似于 Visual Studio's HTML editor . (SourceSafe2005实际上在某些情况下借用了此算法)
    • 不管发生什么,都要有一个合理的后备计划。

    例如,这里是快速ASCII检测器:

    function IsAscii([System.IO.FileInfo]$item)
    {
        begin 
        { 
            $validList = new-list byte
            $validList.AddRange([byte[]] (10,13) )
            $validList.AddRange([byte[]] (31..127) )
        }
    
        process
        {
            try 
            {
                $reader = $item.Open([System.IO.FileMode]::Open)
                $bytes = new-object byte[] 1024
                $numRead = $reader.Read($bytes, 0, $bytes.Count)
    
                for($i=0; $i -lt $numRead; ++$i)
                {
                    if (!$validList.Contains($bytes[$i]))
                        { return $false }
                }
                $true
            }
            finally
            {
                if ($reader)
                    { $reader.Dispose() }
            }
        }
    }
    

    我的目标使用模式是在“dir”和“ss”之间的管道中插入一个where-object子句。根据您的脚本风格,还有其他方法。

    改进沿建议路径之一的检测算法留给读者。

    编辑:我开始在自己的评论中回复你的评论,但是时间太长了…

    上面,我从POV的白名单中查看了已知良好序列的问题。在我维护的应用程序中,将二进制文件错误地存储为文本的结果要比文本的结果差得多。同样的情况也适用于选择使用哪种ftp传输模式,或选择发送到电子邮件服务器的mime编码等情况。

    在其他情况下,黑名单显然是伪造的,允许所有其他的东西被称为文本也是一种同样有效的技术。虽然U+0000是一个有效的代码点,但在现实世界中几乎找不到它。同时,在结构化二进制文件中(即,每当固定字节长度的字段需要填充时),00是非常常见的,因此它是一个非常简单的黑名单。vss 6.0单独使用了这个检查,并没有问题。

    除此之外,*.zip文件是检查\0风险更大的一种情况。与大多数二进制文件不同,它们的结构化“header”(footer?)块在末尾,而不是开头。假设理想的熵压缩,前1kb中的no \0概率为(1-1/256)^1024或约2%。幸运的是,只需扫描4KB集群的其余NTFS读取就可以将风险降低到0.00001%,而无需更改算法或编写其他特殊情况。

    若要排除无效的UTF-8,请将\c0-c1和\f8-fd和\fe-ff(在您看到可能的BOM之后)添加到黑名单。非常不完整,因为您实际上并没有验证序列,而是足够接近您的目的。如果你想比这更有趣,现在是时候调用一个平台库了,比如imultilang2::detectinputcodepage。

    不知道为什么\c8(200十进制)在grep的列表中。它不是超长编码。例如,序列\c8\80表示_(U+0200)。可能是Unix特有的。

        2
  •  8
  •   Community uzul    7 年前

    好吧,经过几个小时的研究,我相信我已经找到了解决办法。不过,我不会把这当作答案。

    Pro Windows Powershell 有一个非常相似的例子。我完全忘记了我有这么好的推荐信。如果您对PowerShell感兴趣,请购买它。它详细介绍了get-content和unicode-bom。

    这个 Answer 对于类似的问题,Unicode标识也非常有用。

    这是剧本。如果你知道它可能有什么问题,请告诉我。

    # The file to be tested
    param ($currFile)
    
    # encoding variable
    $encoding = ""
    
    # Get the first 1024 bytes from the file
    $byteArray = Get-Content -Path $currFile -Encoding Byte -TotalCount 1024
    
    if( ("{0:X}{1:X}{2:X}" -f $byteArray) -eq "EFBBBF" )
    {
        # Test for UTF-8 BOM
        $encoding = "UTF-8"
    }
    elseif( ("{0:X}{1:X}" -f $byteArray) -eq "FFFE" )
    {
        # Test for the UTF-16
        $encoding = "UTF-16"
    }
    elseif( ("{0:X}{1:X}" -f $byteArray) -eq "FEFF" )
    {
        # Test for the UTF-16 Big Endian
        $encoding = "UTF-16 BE"
    }
    elseif( ("{0:X}{1:X}{2:X}{3:X}" -f $byteArray) -eq "FFFE0000" )
    {
        # Test for the UTF-32
        $encoding = "UTF-32"
    }
    elseif( ("{0:X}{1:X}{2:X}{3:X}" -f $byteArray) -eq "0000FEFF" )
    {
        # Test for the UTF-32 Big Endian
        $encoding = "UTF-32 BE"
    }
    
    if($encoding)
    {
        # File is text encoded
        return $false
    }
    
    # So now we're done with Text encodings that commonly have '0's
    # in their byte steams.  ASCII may have the NUL or '0' code in
    # their streams but that's rare apparently.
    
    # Both GNU Grep and Diff use variations of this heuristic
    
    if( $byteArray -contains 0 )
    {
        # Test for binary
        return $true
    }
    
    # This should be ASCII encoded 
    $encoding = "ASCII"
    
    return $false
    

    将此脚本另存为 ISPSARIA.PS1

    这个脚本获取了我尝试更正的所有文本或二进制文件。