Подтвердить что ты не робот

Поиск PowerShell script, который игнорирует двоичные файлы

Я действительно привык делать grep -iIr в оболочке Unix, но пока еще не получил эквивалент PowerShell.

В принципе, указанная выше команда рекурсивно ищет целевые папки и игнорирует двоичные файлы из-за опции "-I". Эта опция также эквивалентна опции --binary-files=without-match, которая гласит: "рассматривать двоичные файлы как не соответствующие строке поиска"

До сих пор я использовал Get-ChildItems -r | Select-String в качестве замены grep PowerShell с добавлением случайных Where-Object. Но я не понял способ игнорировать все двоичные файлы, такие как команда grep -I.

Как двоичные файлы можно фильтровать или игнорировать с помощью Powershell?

Итак, для заданного пути я хочу, чтобы Select-String выполнял поиск текстовых файлов.

РЕДАКТИРОВАТЬ: Еще несколько часов в Google произвели этот вопрос Как определить содержимое файла ASCII или Binary. Вопрос гласит: "ASCII", но я считаю, что писатель имел в виду "Text Encoded", как и я.

EDIT: Кажется, что для решения этой проблемы нужно написать isBinary(). Вероятно, утилита командной строки С#, чтобы сделать ее более полезной.

EDIT: Кажется, что то, что grep делает, это проверка на ASCII NUL Byte или UTF-8 Overlong. Если они существуют, он считает файл двоичным. Это один вызов memchr().

4b9b3361

Ответ 1

В 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 имеет какие-либо специальные двоичные vs текстовые индикаторы. (Ну, VMS сделал, но я сомневаюсь, что источник ваших привычек grep.) Я посмотрел на реализацию Grep -I и, по-видимому, это просто быстро-грязная эвристика, основанная на первом фрагменте файла. Оказывается, что у меня есть немного опыта. Итак, здесь мой совет по выбору эвристической функции, подходящей для текстовых файлов Windows:

  • Изучите не менее 1 Кбайт файла. Многие форматы файлов начинаются с заголовка, который похож на текст, но вскоре он разобьет ваш синтаксический анализатор. Как работает современное оборудование, чтение 50 байтов имеет примерно одинаковые служебные данные ввода-вывода, как чтение 4 КБ.
  • Если вы только заботитесь о прямой ASCII, выходите, как только вы увидите что-то вне диапазона символов [31-127 плюс CR и LF]. Вы можете случайно исключить какое-то умное искусство ASCII, но попытка отделить эти случаи от двоичного мусора нетривиальна.
  • Если вы хотите обрабатывать текст Unicode, пусть библиотеки MS обрабатывают грязную работу. Это сложнее, чем вы думаете. Из Powershell вы можете легко получить доступ к интерфейсу IMultiLang2 (COM) или Encoding.GetEncoding static method (.NET). Конечно, они все еще догадываются. Раймонд комментирует алгоритм обнаружения блокнота (и ссылку внутри на Майкла Каплана), стоит рассмотреть, прежде чем решать, как вы хотите смешивать и сопоставлять платформу поддерживаемых библиотек.
  • Если результат важен, то есть недостаток будет делать что-то хуже, чем просто загромождать вашу консоль grep, - тогда не бойтесь жестко кодировать некоторые расширения файлов ради точности. Например, файлы *.PDF иногда имеют несколько КБ текста спереди, несмотря на то, что они являются двоичным форматом, что приводит к печально известным ошибкам, связанным выше. Аналогично, если у вас есть расширение файла, которое может содержать XML или XML-подобные данные, вы можете попробовать схему обнаружения, похожую на редактор HTML Visual Studio. (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() }
        }
    }
}

Шаблон использования, на котором я настроен таргетинг, является предложением where-object, вставленным в конвейер между "dir" и "ss". Существуют и другие способы, в зависимости от стиля написания сценариев.

Улучшение алгоритма обнаружения по одному из предложенных путей предоставляется читателю.

edit: я начал отвечать на ваш комментарий в собственном комментарии, но он слишком долго...

Выше, я посмотрел на проблему из POV в белых списках с хорошо известными последовательностями. В приложении, которое я поддерживал, неправильное сохранение двоичного кода в тексте имело гораздо худшие последствия, чем наоборот. То же самое относится к сценариям, в которых вы выбираете, какой режим передачи FTP использовать, или какой MIME-кодирование отправлять на сервер электронной почты и т.д.

В других сценариях черный список, очевидно, фиктивный и позволяющий всему, что называется текстом, является одинаково приемлемым методом. В то время как U + 0000 является допустимой точкой кода, он почти никогда не найден в тексте реального мира. Между тем, \00 довольно распространен в структурированных двоичных файлах (а именно, когда поле с фиксированной длиной байта требует заполнения), поэтому он делает большой простой черный список. VSS 6.0 использовал эту проверку самостоятельно и сделал нормально.

Кроме того: *.zip файлы - это случай, когда проверка на \0 является более рискованной. В отличие от большинства двоичных файлов, их структурированный блок "header" (нижний колонтитул?) Находится в конце, а не в начале. Предполагая идеальное сжатие энтропии, вероятность отсутствия \0 в первом 1 КБ равна (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.

Ответ 2

Хорошо, после нескольких часов исследований я считаю, что нашел свое решение. Я не буду отмечать это как ответ, хотя.

Pro Windows Powershell имел очень похожий пример. Я совершенно забыл, что у меня есть эта прекрасная ссылка. Пожалуйста, купите его, если вы заинтересованы в Powershell. Он подробно рассказал о спецификациях Get-Content и Unicode.

Этот Ответ на аналогичные вопросы также очень помог с идентификацией Unicode.

Вот script. Пожалуйста, дайте мне знать, если вы знаете какие-либо проблемы, которые могут возникнуть.

# 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 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

Сохраните этот script как isBinary.ps1

Этот script получил каждый текстовый или двоичный файл, который я пробовал правильно.