2009-07-03 107 views
22

我真的很习惯在Unix shell上做grep -iIr,但我还没有能够获得与PowerShell相同的功能。忽略二进制文件的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 NUL字节或UTF-8 超长。如果存在,它会考虑文件二进制文件。这是一个单独的memchr()调用。

+0

不是PS脚本,而是`findstr`等价的是`findstr/p`,我在PowerShell控制台中使用这样的:`doskey fs = findstr/spin/a:4A $ *`然后像`fs ` – orad 2014-05-21 20:18:42

回答

28

在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字节与读取4KB大致相同的I/O开销。
  • 如果你只关心直线ASCII,只要看到字符范围外的内容[31-127加上CR和LF]就退出。您可能会意外地排除一些聪明的ASCII艺术,但试图将这些情况与二进制垃圾分开是非平凡的。
  • 如果你想处理Unicode文本,让MS库处理肮脏的工作。这比你想象的更难。从Powershell您可以轻松访问静态方法(.NET)IMultiLang2 interface(COM)或Encoding.GetEncoding。当然,他们仍然只是猜测。雷蒙德对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() } 
     } 
    } 
} 

我针对的使用模式是与“目录”,“SS”的管道插入其中,宾语从句。还有其他方法,这取决于您的脚本风格。

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

编辑:我开始回答在我自己的评论您的评论,但它得到了太久......

以上,我看着这个问题从白名单已知良好序列的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(一旦您搜索到可能的物料清单后)添加到黑名单。非常不完整,因为你实际上没有验证序列,但足够接近你的目的。如果你想获得更多的发现,现在是时候调用其中一个平台库,比如IMultiLang2 :: DetectInputCodepage。

不知道为什么\ C8(200十进制)在Grep的名单上。这不是一个超长的编码。例如,序列\ C8 \ 80代表Ȁ(U + 0200)。也许是特定于Unix的东西。

+0

如果可以的话,我会给出不止一个upvote,这个答案的几乎穷尽的完整性。 – Knox 2009-07-03 09:57:00

+0

非常感谢彻底的回应!我已经对文件扩展名方法进行了裁定,因为有太多需要考虑的事情,就像你所建议的那样。但我很高兴你把你的分析包括进去了,这很好。你的isAscii()函数也很有帮助。由于目标是检测二进制文件,并将所有类型的字符编码视为相同,我开始查看isBinary()方法。我也看过grep是如何做到的。回到搜索'\ 0'或'\ 200'(utf-8 overlong?)的单个'memchr()'调用。那是你发现的吗?你知道为什么这有可能吗? – kervin 2009-07-03 17:17:00

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

这个脚本得到了每一个文本文件或二进制文件我想正确的。