2009-07-03 2 views
22

Je suis vraiment habitué à faire grep -iIr sur le shell Unix mais je n'ai pas encore réussi à obtenir un équivalent PowerShell.Script de recherche PowerShell qui ignore les fichiers binaires

Fondamentalement, la commande ci-dessus recherche les dossiers cibles récursivement et ignore les fichiers binaires en raison de l'option "-I". Cette option est également équivalente à l'option --binary-files=without-match, qui dit « traiter les fichiers binaires ne correspond pas à la chaîne de recherche »

Jusqu'à présent, je l'ai utilisé Get-ChildItems -r | Select-String comme mon PowerShell remplacement grep avec le Where-Object occasionnel ajouté. Mais je n'ai pas trouvé un moyen d'ignorer tous les fichiers binaires comme le fait la commande grep -I.

Comment les fichiers binaires peuvent-ils être filtrés ou ignorés avec Powershell? Donc pour un chemin donné, je veux seulement Select-String pour rechercher des fichiers texte.

EDIT: Quelques heures de plus sur Google ont produit cette question How to identify the contents of a file is ASCII or Binary. La question dit "ASCII" mais je crois que l'écrivain voulait dire "Texte Encodé", comme moi-même.

EDIT: Il semble qu'un isBinary() doit être écrit pour résoudre ce problème. Probablement un utilitaire en ligne de commande C# pour le rendre plus utile.

EDIT: Il semble que ce grep est en train de faire est de vérifier pour ASCII NUL octet ou UTF-8 overlong. Si ceux-ci existent, il considère le fichier binaire. Ceci est un appel unique memchr().

+0

Pas un script PS, mais 'findstr' équivalent est' findstr/p' que j'utilise dans la console powershell comme ceci: 'doskey fs = findstr/spin/a: 4A $ *' puis utiliser comme fs ' – orad

Répondre

28

Sous Windows, les extensions de fichiers sont généralement assez bon:

# 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 

Mais bien sûr, les extensions de fichiers ne sont pas parfaits. Personne n'aime taper de longues listes, et beaucoup de fichiers sont mal nommés de toute façon.

Je ne pense pas que Unix ait des indicateurs binaires ou textuels spéciaux dans le système de fichiers. (Eh bien, VMS l'a fait, mais je doute que ce soit la source de vos habitudes de grep.) J'ai regardé l'implémentation de Grep -I, et apparemment c'est juste une heuristique quick-n-dirty basée sur le premier morceau du fichier. Il s'avère que c'est une stratégie que j'ai avec a bit of experience. Voici donc mon conseil sur le choix d'une fonction heuristique appropriée pour les fichiers texte Windows:

  • Examinez au moins 1 Ko du fichier. Beaucoup de formats de fichiers commencent par un en-tête ressemblant à du texte, mais qui va bientôt éclater votre analyseur. La façon dont le matériel moderne fonctionne, en lisant 50 octets a à peu près le même overhead d'E/S que la lecture de 4 Ko.
  • Si vous ne vous souciez que de l'ASCII rectiligne, quittez dès que vous voyez quelque chose en dehors de la plage de caractères [31-127 plus CR et LF]. Vous pourriez accidentellement exclure un art ASCII intelligent, mais essayer de séparer ces cas d'ordure binaire est non trivial.
  • Si vous souhaitez gérer le texte Unicode, laissez les bibliothèques MS gérer le travail sale. C'est plus difficile que tu ne le penses. De Powershell vous pouvez facilement accéder à la méthode statique IMultiLang2 interface (COM) ou Encoding.GetEncoding (.NET). Bien sûr, ils ne font que deviner.Les commentaires de Raymond sur le Notepad detection algorithm (et le lien vers Michael Kaplan) valent la peine d'être revus avant de décider exactement comment vous voulez mélanger & avec les bibliothèques fournies par la plateforme.
  • Si le résultat est important - c'est à dire qu'une faille fera pire que de simplement encombrer votre console grep - alors n'ayez pas peur de coder en dur certaines extensions de fichiers par souci de précision. Par exemple, les fichiers * .PDF ont parfois plusieurs Ko de texte à l'avant, bien qu'ils soient en format binaire, ce qui conduit aux bogues notoires ci-dessus. De même, si vous avez une extension de fichier susceptible de contenir des données XML ou de type XML, vous pouvez essayer un schéma de détection similaire à Visual Studio's HTML editor. (SourceSafe 2005 emprunte cet algorithme pour certains cas)
  • Quoi qu'il arrive, ayez un plan de sauvegarde raisonnable.

À titre d'exemple, voici le détecteur ASCII rapide:

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

Le modèle d'utilisation je cible est une clause où-objet inséré dans le pipeline entre « dir » et « ss ». Il y a d'autres façons, en fonction de votre style de script.

L'amélioration de l'algorithme de détection le long d'un des chemins suggérés est laissée au lecteur.

edit: j'ai commencé à répondre à votre commentaire dans un commentaire de ma propre, mais il faisait trop longtemps ...

Au-dessus, je regarde le problème du POV de liste blanche en bon état des séquences. Dans l'application j'ai maintenu, en stockant incorrectement un binaire comme le texte avait des conséquences bien pires que vice versa. Il en va de même pour les scénarios dans lesquels vous choisissez le mode de transfert FTP à utiliser ou le type d'encodage MIME à envoyer à un serveur de messagerie, etc.

Dans d'autres scénarios, la mise en liste noire de l'évidence et de tout le reste Le texte appelé est une technique tout aussi valide. Alors que U + 0000 est un point de code valide, on ne le trouve pratiquement jamais dans un texte réel. Pendant ce temps, \ 00 est assez commun dans les fichiers binaires structurés (à savoir, chaque fois qu'un champ de longueur d'octet fixe a besoin d'un remplissage), donc il fait une grande liste noire simple. VSS 6.0 a utilisé cette vérification seule et a bien fait.

À côté: * Les fichiers .zip sont un cas où la vérification de \ 0 est plus risquée. Contrairement à la plupart des binaires, leur bloc structuré "header" (pied de page?) Est à la fin, pas le début. En supposant une compression entropique idéale, la probabilité de non \ 0 dans le premier 1KB est de (1-1/256)^1024 ou environ 2%. Heureusement, il suffit de scanner le reste du cluster NTFS de 4 Ko pour lire le risque jusqu'à 0,00001% sans devoir modifier l'algorithme ou écrire un autre cas particulier.

Pour exclure un fichier UTF-8 invalide, ajoutez \ C0-C1 et \ F8-FD et \ FE-FF (une fois que vous avez dépassé la nomenclature possible) dans la liste noire. Très incomplet puisque vous n'êtes pas en train de valider les séquences, mais assez proche pour vos objectifs. Si vous voulez obtenir un colombophile, il est temps d'appeler l'une des bibliothèques de la plate-forme comme IMultiLang2 :: DetectInputCodepage.

Vous ne savez pas pourquoi \ C8 (200 décimal) est sur la liste de Grep. Ce n'est pas un encodage trop long. Par exemple, la séquence \ C8 \ 80 représente Ȁ (U + 0200). Peut-être quelque chose de spécifique à Unix.

+0

Je donnerais plus d'un upvote pour l'exhaustivité presque exhaustive de cette réponse si je le pouvais. – Knox

+0

Merci beaucoup pour la réponse complète! Je m'étais déjà prononcé sur la méthode des extensions de fichiers car il y en a trop à considérer, comme vous l'avez suggéré. Mais je suis content que vous ayez inclus votre analyse, ce qui était excellent. Votre fonction isAscii() est également très utile. Puisque le but est de détecter les binaires et de traiter tous les types de caractères de la même manière, j'ai commencé à regarder une méthode isBinary(). J'avais aussi regardé pour voir comment le grep l'avait fait. Arrivé à un seul appel 'memchr()' cherchant '\ 0' ou '\ 200' (utf-8 overlong?). Est-ce que c'est ce que tu as trouvé? Vous savez pourquoi cela fonctionne par hasard? – kervin

+0

@Richard: ''\ 200'' est octal 200, soit 0x80 non décimal 200. @kervin:'' \ xC0 \ x80'' serait utf-8 trop long ... en fait, il y a un rebelle UTF-8 qui utilise pour encoder U + 0000 afin que les rebs puissent persister dans l'horrible habitude d'utiliser '\ x00' comme terminateur de chaîne. Mais ça n'a rien à voir avec grep :-) –

8

Ok, après quelques heures de recherche, je crois avoir trouvé ma solution. Je ne marquerai pas cela comme la réponse si.

Pro Windows Powershell avait un exemple très similaire. J'avais complètement oublié que j'avais cette excellente référence. S'il vous plaît l'acheter si vous êtes intéressé par Powershell. Il est entré dans les détails sur les nomenclatures Get-Content et Unicode.

Cette Answer à des questions similaires a également été très utile avec l'identification Unicode.

Voici le script. S'il vous plaît laissez-moi savoir si vous connaissez des problèmes, il peut avoir.

# 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 

Enregistrer ce script comme isBinary.ps1

Ce script a chaque fichier texte ou binaire j'ai essayé correct.

+0

Hmmm ... J'aurais dû vérifier UTF-32 avant UTF-8 ... – kervin

+2

C'est la même idée de base que d'appeler IMultiLang2 :: DetectInputCodepage, sauf qu'elle supporte beaucoup moins de codages et ne détectera pas UTF-8 de façon fiable. Selon la norme Unicode, les fichiers UTF-8 ne sont pas * supposés être écrits avec une nomenclature. Les outils Microsoft le font quand même - ce que j'apprécie, franchement - mais la plupart des autres ne le font pas. –

+0

Merci pour les heads up Richard. Je vais me pencher sur ce problème UTF-8. J'ai remarqué que grep a également fait une recherche pour '\ 200', qui semble être au moins une partie de 'Overlong' de l'UTF-8. J'ai probablement besoin de chercher ça aussi. – kervin