2009-07-03 15 views
22

Sono davvero abituato a fare grep -iIr sulla shell Unix ma non sono ancora riuscito a ottenere un equivalente PowerShell.Script di ricerca PowerShell che ignora i file binari

Fondamentalmente, il comando precedente cerca le cartelle di destinazione in modo ricorsivo e ignora i file binari a causa dell'opzione "-I". Questa opzione è anche equivalente all'opzione --binary-files=without-match, che dice "trattare i file binari non corrisponde alla stringa di ricerca"

Finora ho utilizzato Get-ChildItems -r | Select-String come il mio PowerShell sostituzione grep con l'occasionale Where-Object aggiunto. Ma non ho trovato un modo per ignorare tutti i file binari come fa il comando grep -I.

In che modo i file binari possono essere filtrati o ignorati con PowerShell?

Quindi per un determinato percorso, desidero solo Select-String per cercare i file di testo.

MODIFICA: Qualche ora in più su Google ha prodotto questa domanda How to identify the contents of a file is ASCII or Binary. La domanda dice "ASCII", ma credo che lo scrittore significasse "Testo codificato", come me.

MODIFICA: Sembra che sia necessario scrivere uno isBinary() per risolvere questo problema. Probabilmente un'utilità della riga di comando C# per renderla più utile.

EDIT: Sembra che ciò che sta facendo è grep controllo per ASCII NUL Byte o UTF-8 overlong. Se esiste, considera il file binario. Questa è una singola chiamata memchr().

+0

Non uno script PS, ma 'equivalente findstr' è' findstr/p' che uso nelle console PowerShell in questo modo: 'doskey fs = findstr/spin/a: 4A $ *' quindi utilizzare come 'fs ' – orad

risposta

28

In Windows, le estensioni dei file sono di solito abbastanza buono:

# 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 

Ma, naturalmente, estensioni di file non sono perfetti. A nessuno piace scrivere lunghe liste e molti file vengono comunque chiamati in causa.

Non credo che Unix abbia degli speciali indicatori di testo binario vs nel filesystem. (Beh, il VMS ha funzionato, ma dubito che sia la fonte delle tue abitudini di grep.) Ho esaminato l'implementazione di Grep-I, e apparentemente è solo un'euristica rapida-n-sporca basata sul primo blocco del file. Risulta che è una strategia che ho a bit of experience con. Quindi ecco il mio consiglio su come scegliere una funzione euristica appropriata per i file di testo di Windows:

  • Esaminare almeno 1 KB del file. Numerosi formati di file iniziano con un'intestazione simile al testo, ma in seguito eliminerà il parser. Il modo in cui funziona l'hardware moderno, leggendo 50 byte ha approssimativamente lo stesso sovraccarico I/O della lettura di 4KB.
  • Se ti interessa direttamente ASCII, esci non appena vedi qualcosa al di fuori dell'intervallo di caratteri [31-127 più CR e LF]. Potresti accidentalmente escludere qualche arte ASCII intelligente, ma cercare di separare questi casi dalla spazzatura binaria non è banale.
  • Se si desidera gestire il testo Unicode, lasciare che le librerie MS gestiscano il lavoro sporco. È più difficile di quanto pensi. Da PowerShell è possibile accedere facilmente al metodo statico IMultiLang2 interface (COM) o Encoding.GetEncoding (.NET). Certo, stanno ancora solo indovinando.I commenti di Raymond sullo Notepad detection algorithm (e il link all'interno di Michael Kaplan) sono degni di essere esaminati prima di decidere esattamente come combinare lo & corrispondente alle librerie fornite dalla piattaforma.
  • Se il risultato è importante, ad esempio un difetto farà qualcosa di peggio che ingombrare la console grep, quindi non abbiate paura di codificare con precisione alcune estensioni di file per motivi di precisione. Ad esempio, i file * .PDF di tanto in tanto hanno diversi KB di testo nella parte anteriore nonostante sia un formato binario, che porta ai famigerati bug collegati sopra. Allo stesso modo, se si dispone di un'estensione di file che probabilmente contiene dati XML o simili a XML, è possibile provare uno schema di rilevamento simile a Visual Studio's HTML editor. (In alcuni casi SourceSafe 2005 prende in prestito questo algoritmo)
  • Qualsiasi altra cosa accada, disporre di un piano di backup ragionevole.

A titolo di esempio, ecco il rilevatore ASCII rapida:

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() } 
     } 
    } 
} 

Il modello di utilizzo ho scelto come target è una clausola in cui-oggetto inserito in cantiere tra "dir" e "ss". Ci sono altri modi, a seconda del tuo stile di scripting.

Il miglioramento dell'algoritmo di rilevamento lungo uno dei percorsi suggeriti è lasciato al lettore.

edit: ho iniziato rispondere al tuo commento in un commento di mio, ma faceva troppo lungo ...

Sopra, ho guardato il problema dal POV di whitelist noto buone sequenze. Nell'applicazione che ho mantenuto, la memorizzazione errata di un file binario come testo ha avuto conseguenze molto peggiori rispetto al contrario. Lo stesso vale per gli scenari in cui si sceglie la modalità di trasferimento FTP da utilizzare, o quale tipo di codifica MIME da inviare a un server di posta elettronica, ecc.

In altri scenari, inserire nella lista nera i termini fittizi e consentire a tutto il resto di essere il testo chiamato è una tecnica altrettanto valida. Mentre U + 0000 è un punto di codice valido, è praticamente mai trovato nel testo del mondo reale. Nel frattempo, \ 00 è abbastanza comune nei file binari strutturati (vale a dire, ogni volta che un campo a lunghezza di byte fisso ha bisogno di riempimento), quindi crea una semplice lista nera. VSS 6.0 ha utilizzato questo controllo da solo e ha funzionato correttamente.

A parte: * i file .zip sono un caso in cui il controllo di \ 0 è più rischioso. A differenza della maggior parte dei binari, il loro blocco strutturato "header" (footer?) È alla fine, non all'inizio. Supponendo una compressione entropica ideale, la probabilità di nessun \ 0 nel primo 1 KB è (1-1/256)^1024 o circa il 2%. Fortunatamente, la semplice scansione del resto del read NTFS del cluster 4KB porterà il rischio fino allo 0,00001% senza dover modificare l'algoritmo o scrivere un altro caso speciale.

Per escludere UTF-8 non valido, aggiungere \ C0-C1 e \ F8-FD e \ FE-FF (dopo aver cercato oltre il possibile BOM) alla lista nera. Molto incompleto dal momento che non stai effettivamente convalidando le sequenze, ma abbastanza vicino per i tuoi scopi. Se vuoi essere più appassionato di questo, è il momento di chiamare una delle librerie della piattaforma come IMULTLang2 :: DetectInputCodepage.

Non sono sicuro del motivo per cui \ C8 (200 decimale) si trova nell'elenco di Grep. Non è una codifica troppo lunga. Ad esempio, la sequenza \ C8 \ 80 rappresenta Ȁ (U + 0200). Forse qualcosa di specifico per Unix.

+0

Vorrei dare più di un upvote per la completezza quasi esaustiva di questa risposta se potessi. – Knox

+0

Grazie mille per la risposta completa! Avevo già deciso sul metodo delle estensioni di file perché ci sono troppe cose da considerare, come suggerivi tu. Ma sono contento che tu abbia incluso la tua analisi, che è stata eccellente. La tua funzione isAscii() è anche molto utile. Poiché l'obiettivo è rilevare binari e trattare tutti i tipi di codifica dei caratteri uguali, ho iniziato a esaminare un metodo isBinary(). Avevo anche guardato per vedere come lo faceva grep. Arriva a una singola chiamata 'memchr()' alla ricerca di '\ 0' o '\ 200' (utf-8 overlong?). È quello che hai trovato? Sai perché funziona per caso? – kervin

+0

@Richard: ''\ 200'' è ottale 200 alias 0x80 non decimale 200. @kervin:'' \ xC0 \ x80'' sarebbe utf-8 overlong ... in effetti c'è un UTF-8 ribelle che usa per codificare U + 0000 in modo che le rebs possano persistere nell'orrida abitudine di usare '\ x00' come terminatore di stringhe. Ma questo non ha nulla a che fare con grep :-) –

8

Ok, dopo qualche ora di ricerca, credo di aver trovato la mia soluzione. Non contrassegnerò questo come la risposta comunque.

Pro Windows Powershell ha avuto un esempio molto simile. Mi ero completamente dimenticato di avere questa eccellente referenza. Si prega di acquistare se siete interessati a PowerShell. È andato in dettaglio su Get-Content e BOM Unicode.

Questo Answer a domande simili era anche molto utile con l'identificazione Unicode.

Ecco la sceneggiatura. Per favore fatemi sapere se siete a conoscenza di eventuali problemi che potrebbe avere.

# 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 

Salva questo script come isBinary.ps1

Questo script ha ogni testo o file binario ho provato corretto.

+0

Hmmm ... Avrei dovuto verificare UTF-32 prima di UTF-8 ... – kervin

+2

Questa è la stessa idea di base che chiama IMultiLang2 :: DetectInputCodepage, eccetto che supporta codifiche molto meno e non rileva in modo affidabile UTF-8. Secondo lo standard Unicode, i file UTF-8 * non * dovrebbero essere scritti con una BOM. Gli strumenti Microsoft lo fanno comunque - che apprezzo, francamente - ma la maggior parte degli altri no. –

+0

Grazie per l'heads up di Richard. Guarderò questo problema UTF-8. Ho notato che grep ha anche fatto una ricerca per "\ 200", che sembra essere almeno parte di UTF-8 'Overlong'. Probabilmente ho bisogno di cercare anche quello. – kervin

Problemi correlati