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

时间:2009-07-03 03:16:58

标签: powershell grep

我真的习惯在Unix shell上做grep -iIr但是我还没有能够获得PowerShell等价物。

基本上,上面的命令以递归方式搜索目标文件夹,并因“-I”选项而忽略二进制文件。此选项也等同于--binary-files=without-match选项,其中“将二进制文件视为与搜索字符串不匹配”

到目前为止,我一直在使用Get-ChildItems -r | Select-String作为我的PowerShell grep替换,偶尔会添加Where-Object。但我还没有找到一种方法来忽略所有二进制文件,如grep -I命令。

如何使用Powershell过滤或忽略二进制文件?

因此,对于给定的路径,我只希望Select-String搜索文本文件。

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

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

编辑:似乎grep正在做的是检查ASCII NUL Byte 或UTF-8 Overlong 。如果存在,则认为文件是二进制文件。这是一个 memchr()调用。

2 个答案:

答案 0 :(得分:31)

在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在文件系统中没有任何特殊的二进制vs文本指示符。 (好吧,VMS确实如此,但我怀疑这是你的grep习惯的来源。)我看了一下Grep -I的实现,显然它只是一个基于文件第一块的快速n-dirty启发式。事实证明这是我a bit of experience的策略。所以我的建议是选择适合Windows文本文件的启发式函数:

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

例如,这是快速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风险较大的情况。与大多数二进制文件不同,它们的结构化“标题”(页脚?)块在最后,而不是开头。假设理想的熵压缩,前1KB中没有\ 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特有的东西。

答案 1 :(得分:8)

好的,经过几个小时的研究后,我相信我找到了解决方案。我不会将此标记为答案。

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

将此脚本另存为 isBinary.ps1

这个脚本得到了我试过的每个文本或二进制文件。