2009-07-03 11 views
22

Jestem naprawdę przyzwyczajony do robienia grep -iIr na powłoce Uniksa, ale nie udało mi się jeszcze uzyskać odpowiednika PowerShell.Skrypt wyszukiwania PowerShell, który ignoruje pliki binarne

Zasadniczo powyższe polecenie przeszukuje foldery docelowe rekursywnie i ignoruje pliki binarne ze względu na opcję "-I". Ta opcja jest równoważna opcji --binary-files=without-match, który mówi „leczyć pliki binarne jako nie pasujące wyszukiwany ciąg”

tej pory używam Get-ChildItems -r | Select-String jak moja wymiana grep PowerShell z okolicznościowym Where-Object dodał. Ale nie znalazłem sposobu, aby zignorować wszystkie pliki binarne, takie jak polecenie grep -I.

W jaki sposób pliki binarne można filtrować lub ignorować za pomocą funkcji Powershell?

Tak więc dla danej ścieżki chcę tylko, aby Select-String wyszukiwał pliki tekstowe.

EDYCJA: Jeszcze kilka godzin w Google wyprodukowało to pytanie How to identify the contents of a file is ASCII or Binary. Pytanie mówi "ASCII", ale uważam, że pisarz miał na myśli "Zakodowany tekst", jak ja.

EDYCJA: Wygląda na to, że aby rozwiązać ten problem, należy napisać isBinary(). Prawdopodobnie narzędzie wiersza poleceń C#, aby było bardziej przydatne.

EDIT: Wydaje się, że to, co robi jest grep sprawdzanie ASCII NUL Byte lub UTF-8 wydłużony. Jeśli takowe istnieje, uznaje plik za binarny. Jest to pojedyncze wywołanie memchr().

+0

Nie jest to skrypt PS, ale odpowiednik 'findstr' to' findstr/p', którego używam w konsoli PowerShell w ten sposób: 'doskey fs = findstr/spin/a: 4A $ *' następnie użyj jak 'fs ' – orad

Odpowiedz

28

W Windows rozszerzenia plików są zwykle wystarczająco dobre: ​​

# 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 

Ale oczywiście, rozszerzenia plików nie są doskonałe. Nikt nie lubi pisać długich list, a wiele plików i tak jest źle nazwanych.

Nie sądzę, że Unix ma jakieś specjalne wskaźniki binarne vs tekstowe w systemie plików. (Cóż, VMS to zrobił, ale wątpię, że to jest źródło twoich nawyków grep.) Spojrzałem na implementację Grepa-I, i najwyraźniej jest to po prostu szybka i brudna heurystyka oparta na pierwszej części pliku. Okazuje się, że jest to strategia, którą mam a bit of experience z. Oto moja rada dotycząca wyboru funkcji heurystycznej odpowiedniej dla plików tekstowych Windows:

  • Sprawdź co najmniej 1 KB pliku. Wiele formatów plików zaczyna się od nagłówka, który wygląda jak tekst, ale wkrótce zostanie zniszczony przez twój parser. Sposób, w jaki działa nowoczesny sprzęt, odczyt 50 bajtów ma mniej więcej takie same narzuty we/wy, co odczyt 4KB.
  • Jeśli zależy ci jedynie na prostym ASCII, wyjdź, gdy zobaczysz coś poza zakresem znaków [31-127 plus CR i LF]. Możesz przypadkowo wykluczyć jakąś sprytną sztukę ASCII, ale próba oddzielenia tych przypadków od binarnych śmieci jest nietrywialna.
  • Jeśli chcesz obsługiwać tekst w Unicode, niech biblioteki MS obsłużą brudną pracę. To trudniejsze niż myślisz. Od Powershell można łatwo uzyskać dostęp do metody statycznej IMultiLang2 interface (COM) lub Encoding.GetEncoding (.NET). Oczywiście nadal się tylko domyślają.Uwagi Raymonda na temat Notepad detection algorithm (oraz link do Michaela Kaplana) warto przeczytać przed podjęciem decyzji, w jaki sposób chcesz mieszać & pasujące do bibliotek dostarczanych przez platformę.
  • Jeśli wynik jest ważny - np. Wada zrobi coś gorszego niż po prostu zagracenie konsoli grep - to nie obawiaj się sztywnego kodowania niektórych rozszerzeń plików ze względu na dokładność. Na przykład pliki * .PDF czasami mają kilka KB tekstu z przodu, mimo że są formatem binarnym, co prowadzi do notorycznych błędów połączonych powyżej. Podobnie, jeśli masz rozszerzenie pliku, które prawdopodobnie zawiera dane XML lub podobne do XML, możesz wypróbować schemat wykrywania podobny do Visual Studio's HTML editor. (SourceSafe 2005 w rzeczywistości pożycza ten algorytm w niektórych przypadkach)
  • Cokolwiek się stanie, należy mieć rozsądny plan tworzenia kopii zapasowych.

Jako przykład, oto szybki detektor 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() } 
     } 
    } 
} 

Wzór Wykorzystanie jestem kierowania jest klauzula gdzie-przedmiot włożony w rurociągu pomiędzy „dir” i „SS”. Istnieją inne sposoby, w zależności od stylu skryptu.

Ulepszenie algorytmu wykrywania wzdłuż jednej z sugerowanych ścieżek jest pozostawione czytnikowi.

Edit: Zacząłem odpowiedzi na Twój komentarz w komentarzu na własną rękę, ale było zbyt długo ...

Above, spojrzałem na problem z POV-białych list znany dobre sekwencje. W aplikacji, którą utrzymywałem, niepoprawne przechowywanie pliku binarnego jako tekstu miało znacznie gorsze konsekwencje niż na odwrót. To samo dotyczy scenariuszy, w których wybierasz tryb transferu FTP do użycia lub jakiego rodzaju kodowanie MIME do wysłania na serwer e-mail itp.

W innych sytuacjach na czarnej liście jest oczywiście nieprawdziwe i pozwalające na wszystko inne tekst nazywany jest równie ważną techniką. O ile U + 0000 jest prawidłowym punktem kodowym, prawie nigdy nie można go znaleźć w tekstach rzeczywistych. Tymczasem \ 00 jest dość powszechne w strukturalnych plikach binarnych (a mianowicie, gdy pole o stałej długości bajtów wymaga dopełnienia), więc tworzy świetną prostą czarną listę. VSS 6.0 użył tego czeku sam i zrobił to dobrze.

Poza: * Pliki .zip są przypadkiem, w którym sprawdzenie \ 0 jest bardziej ryzykowne. W przeciwieństwie do większości plików binarnych ich uporządkowany blok nagłówka (stopki?) Jest na końcu, a nie na początku. Zakładając idealną kompresję entropową, szansa na \ 0 w pierwszym 1KB wynosi (1-1/256)^1024 lub około 2%. Na szczęście po prostu skanowanie pozostałej części odczytu NTFS klastra 4KB spowoduje obniżenie ryzyka do 0,00001% bez konieczności zmiany algorytmu lub napisania innego specjalnego przypadku.

Aby wykluczyć nieprawidłowy kodek UTF-8, dodaj \ C0-C1 i \ F8-FD i \ FE-FF (gdy przeszukiwali Państwo możliwą LM) do czarnej listy. Bardzo niekompletne, ponieważ w rzeczywistości nie sprawdzasz poprawności sekwencji, ale wystarczająco blisko do swoich celów. Jeśli chcesz zdobyć jakikolwiek sympatyk, pora zadzwonić do jednej z bibliotek platformy, takich jak IMultiLang2 :: DetectInputCodepage.

Nie wiem, dlaczego \ C8 (200 dziesiętnych) znajduje się na liście Grepa. To nie jest zbyt długie kodowanie. Na przykład sekwencja \ C8 \ 80 reprezentuje Ȁ (U + 0200). Może coś specyficznego dla Uniksa.

+0

Chciałbym ustąpić więcej niż jeden upominek dla prawie wyczerpującej kompletności tej odpowiedzi, gdybym mógł. – Knox

+0

Wielkie dzięki za dokładną reakcję! Już orzekłem w sprawie metody rozszerzeń plików, ponieważ jest zbyt wiele do rozważenia, jak sugerowałeś. Ale cieszę się, że zawarłeś swoją analizę, która była doskonała. Bardzo pomocna jest funkcja isAscii(). Ponieważ celem jest wykrycie binarnego i traktowanie wszystkich typów kodowania znaków tak samo, zacząłem patrzeć na metodę isBinary(). Sprawdziłem też, jak zrobił to grep. Dotarłem do pojedynczego wywołania "memchr()", wyszukując "\ 0" lub "\ 200" (utf-8 overlong?). Czy to właśnie znalazłeś? Wiesz, dlaczego to działa przez przypadek? – kervin

+0

@Richard: ''\ 200'' jest ósemkowym 200 aka 0x80 nie dziesiętnym 200. @kervin:'' \ xC0 \ x80'' będzie utf-8 overlong ... w rzeczywistości istnieje rebelianci UTF-8, który używa do kodowania U + 0000, aby rebs mógł przetrwać w okropnym zwyczaju używania '\ x00' jako terminatora znaków. Ale to nie ma nic wspólnego z grepem :-) –

8

Ok, po kilku kolejnych godzinach badań wierzę, że znalazłem moje rozwiązanie. Nie zaznaczę tego jednak jako odpowiedzi.

Pro Windows Powershell miał bardzo podobny przykład. Całkowicie zapomniałem, że mam doskonałe referencje. Kup go, jeśli jesteś zainteresowany PowerShell. Szczegółowo omówiono LM-y Get-Content i BOM-a Unicode.

Ta odpowiedź na podobne pytania była bardzo pomocna przy identyfikowaniu Unicode.

Oto skrypt. Daj mi znać, jeśli znasz jakieś problemy, które mogą mieć.

# 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 

Zapisz ten skrypt jako isBinary.ps1

Ten skrypt dostał każdy tekst lub pliku binarnego Próbowałem poprawne.

+0

Hmmm ... Powinienem był sprawdzić UTF-32 przed UTF-8 ... – kervin

+2

Jest to ten sam podstawowy pomysł co wywoływanie IMultiLang2 :: DetectInputCodepage, z tym, że obsługuje znacznie mniej kodowań i nie będzie niezawodnie wykrywać UTF-8. Zgodnie ze standardem Unicode pliki UTF-8 mają * nie * powinny być zapisywane za pomocą LM. Narzędzia Microsoft robią to i tak - doceniam, szczerze mówiąc - ale większość innych nie. –

+0

Dzięki za zgłoszenia do Richarda. Zajrzę do tego problemu w UTF-8. Zauważyłem, że grep również wyszukał "\ 200", co wydaje się być przynajmniej częścią UTF-8 "Overlong". Prawdopodobnie również będę musiał to sprawdzić. – kervin

Powiązane problemy