2009-07-03 17 views
22

Estoy realmente acostumbrado a hacer grep -iIr en el shell de Unix, pero aún no he podido obtener un equivalente de PowerShell.Script de búsqueda de PowerShell que ignora archivos binarios

Básicamente, el comando anterior busca las carpetas de destino recursivamente e ignora los archivos binarios debido a la opción "-I". Esta opción también es equivalente a la opción --binary-files=without-match, que dice "tratar a los archivos binarios que no coincida con la cadena de búsqueda"

Hasta ahora he estado utilizando como mi Get-ChildItems -r | Select-String PowerShell reemplazo grep con el ocasional Where-Object añadió. Pero no he encontrado una manera de ignorar todos los archivos binarios como el comando grep -I.

¿Cómo se pueden filtrar o ignorar los archivos binarios con Powershell?

Por lo tanto, para una ruta determinada, solo quiero Select-String para buscar archivos de texto.

EDIT: Un par de horas más en Google producen esta pregunta How to identify the contents of a file is ASCII or Binary. La pregunta dice "ASCII", pero creo que el autor quiso decir "Texto codificado", como yo.

EDITAR: Parece que se debe escribir un isBinary() para resolver este problema. Probablemente una utilidad de línea de comandos C# para hacerlo más útil.

EDIT: Parece que lo que está haciendo es grep comprobación de ASCII NUL Byte o UTF-8 demasiado largo. Si eso existe, considera que el archivo es binario. Esta es una sola llamada a memchr().

+0

No es un script PS, pero el equivalente 'findstr' es' findstr/p' que utilizo en la consola powershell de esta manera: 'doskey fs = findstr/spin/a: 4A $ *' luego usar como 'fs ' – orad

Respuesta

28

En Windows, las extensiones de archivo suelen ser lo suficientemente bueno:

# 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 

Pero, por supuesto, las extensiones de archivo no son perfectos. A nadie le gusta escribir largas listas, y muchos archivos son mal nombrados de todos modos.

No creo que Unix tenga ningún indicador binario especial frente a texto en el sistema de archivos. (Bueno, VMS sí, pero dudo que esa sea la fuente de tus hábitos de grep.) Miré la implementación de Grep -I, y aparentemente es solo una heurística rápida y sucia basada en la primera parte del archivo. Resulta que es una estrategia que tengo a bit of experience con. Así que este es mi consejo sobre cómo elegir una función heurística que sea apropiada para archivos de texto de Windows:

  • Examine al menos 1 KB del archivo. Muchos formatos de archivo comienzan con un encabezado que se parece al texto, pero reventará el analizador poco después. La forma en que funciona el hardware moderno, leer 50 bytes tiene aproximadamente la misma sobrecarga de E/S que leer 4 KB.
  • Si solo le preocupa el ASCII directo, salga tan pronto como vea algo fuera del rango de caracteres [31-127 más CR y LF]. Es posible que accidentalmente excluyas algún arte ASCII inteligente, pero tratar de separar esos casos de la basura binaria no es trivial.
  • Si desea manejar el texto Unicode, permita que las bibliotecas de MS manejen el trabajo sucio. Es más difícil de lo que piensas Desde Powershell puede acceder fácilmente al método estático IMultiLang2 interface (COM) o Encoding.GetEncoding (.NET). Por supuesto, todavía están adivinando.Los comentarios de Raymond sobre Notepad detection algorithm (y el enlace dentro de Michael Kaplan) valen la pena revisar antes de decidir exactamente cómo desea mezclar & para que coincidan con las bibliotecas proporcionadas por la plataforma.
  • Si el resultado es importante, es decir, una falla hará algo peor que saturar su consola grep, entonces no tenga miedo de codificar algunas extensiones de archivos por razones de precisión. Por ejemplo, los archivos * .PDF ocasionalmente tienen varios KB de texto en el frente a pesar de ser un formato binario, lo que lleva a los errores notorios vinculados anteriormente. Del mismo modo, si tiene una extensión de archivo que probablemente contenga datos XML o de tipo XML, puede probar un esquema de detección similar al Visual Studio's HTML editor. (SourceSafe 2005 realmente toma prestado este algoritmo para algunos casos)
  • Pase lo que pase, tenga un plan de copia de seguridad razonable.

Como ejemplo, aquí está el detector ASCII rápida:

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

El patrón de uso que estoy focalización es una cláusula where-objeto insertado en la tubería entre "dir" y "ss". Hay otras formas, dependiendo de su estilo de scripting.

La mejora del algoritmo de detección a lo largo de una de las rutas sugeridas se deja al lector.

edición: Empecé a responder a su comentario en un comentario de la mía, pero tengo demasiado tiempo ...

arriba, yo miraba el problema desde el POV de listas blancas conocida buenas secuencias. En la aplicación que mantuve, almacenar incorrectamente un archivo binario como texto tenía peores consecuencias que viceversa. Lo mismo es cierto para escenarios en los que se elige qué modo de transferencia FTP usar o qué tipo de codificación MIME enviar a un servidor de correo electrónico, etc.

En otras situaciones, la lista negra es obviamente falsa y permite que todo lo demás sea el texto llamado es una técnica igualmente válida. Mientras que U + 0000 es un punto de código válido, casi nunca se encuentra en el texto del mundo real. Mientras tanto, \ 00 es bastante común en archivos binarios estructurados (es decir, cuando un campo de longitud de byte fijo necesita relleno), por lo que es una gran lista negra simple. VSS 6.0 utilizó este control solo y lo hizo bien.

Además: los archivos * .zip son un caso en el que la comprobación de \ 0 es más arriesgada. A diferencia de la mayoría de los binarios, su bloque estructurado de "encabezado" (pie de página?) Está al final, no al principio. Suponiendo la compresión de entropía ideal, la posibilidad de no \ 0 en el primer 1KB es (1-1/256)^1024 o aproximadamente 2%. Afortunadamente, simplemente escaneando el resto de la lectura NTFS del clúster de 4KB disminuirá el riesgo hasta 0.00001% sin tener que cambiar el algoritmo o escribir otro caso especial.

Para excluir el UTF-8 no válido, agregue \ C0-C1 y \ F8-FD y \ FE-FF (una vez que haya buscado más allá de la posible lista de materiales) a la lista negra. Muy incompleto ya que en realidad no está validando las secuencias, pero lo suficientemente cerca para sus propósitos. Si desea obtener algo más elegante que este, es hora de llamar a una de las bibliotecas de plataforma como IMultiLang2 :: DetectInputCodepage.

No estoy seguro de por qué \ C8 (200 decimal) está en la lista de Grep. No es una codificación demasiado larga. Por ejemplo, la secuencia \ C8 \ 80 representa Ȁ (U + 0200). Tal vez algo específico de Unix.

+0

Daría más de un voto positivo por la exhaustividad casi exhaustiva de esta respuesta si pudiera. – Knox

+0

¡Muchas gracias por la respuesta completa! Ya me había pronunciado sobre el método de extensiones de archivo porque hay demasiados para considerar, como sugirió. Pero me alegra que hayas incluido tu análisis, que fue excelente. Su función isAscii() también es muy útil. Dado que el objetivo es detectar binarios y tratar todos los tipos de codificación de caracteres de la misma manera, he comenzado a ver un método isBinary(). También había mirado para ver cómo lo hizo grep. Se reduce a una sola llamada a 'memchr()' buscando '\ 0' o '\ 200' (utf-8 ¿demasiado larga?). ¿Es eso lo que encontraste? ¿Sabes por qué eso funciona por casualidad? – kervin

+0

@Richard: ''\ 200'' es octal 200 aka 0x80 no decimal 200. @kervin:'' \ xC0 \ x80'' sería utf-8 demasiado ... de hecho hay un UTF-8 rebelde que usa eso para codificar U + 0000 para que los rebs puedan persistir en el horrendo hábito de usar '\ x00' como un terminador de cadena. Pero eso no tiene nada que ver con grep :-) –

8

Ok, después de unas horas más de investigación, creo que he encontrado mi solución. Sin embargo, no lo marcaré como la respuesta.

Pro Windows Powershell tenía un ejemplo muy similar. Me había olvidado por completo que tenía esta excelente referencia. Por favor cómprelo si está interesado en Powershell. Se entró en detalle en Get-Content y Unicode BOMs.

Esta pregunta Answer también fue muy útil con la identificación Unicode.

Aquí está el guión. Por favor, avíseme si conoce algún problema que pueda tener.

# 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 

Guardar este script como isBinary.ps1

Este script tiene cada texto o archivo binario que intentaron correcta.

+0

Hmmm ... Debería haber verificado UTF-32 antes de UTF-8 ... – kervin

+2

Esta es la misma idea básica que llamar a IMultiLang2 :: DetectInputCodepage, excepto que admite muchas menos codificaciones y no detectará UTF-8 de manera confiable. Según el estándar Unicode, los archivos UTF-8 * no * se supone que deben escribirse con una lista de materiales. Las herramientas de Microsoft lo hacen de todos modos, lo cual agradezco, francamente, pero la mayoría de las demás no. –

+0

Gracias por el aviso Richard. Analizaré este problema UTF-8. Me di cuenta de que grep también hizo una búsqueda de '\ 200', que parece ser al menos parte de UTF-8 'Overlong'. Probablemente necesito buscar eso también entonces. – kervin