2009-07-03 50 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

這個腳本得到了每一個文本文件或二進制文件我想正確的。