Как получить рекурсивный каталог и список файлов из PowerShell, исключая некоторые файлы и папки? - PullRequest
27 голосов
/ 06 ноября 2011

Я хочу написать сценарий PowerShell, который будет рекурсивно искать каталог, но исключать указанные файлы (например, *.log и myFile.txt), а также исключать указанные каталоги и их содержимое (например, * 1003).* и все файлы и папки ниже myDir).

Я работал с Get-ChildItem CmdLet и Where-Object CmdLet, но яне могу получить такое точное поведение.

Ответы [ 3 ]

54 голосов
/ 14 мая 2012

Мне нравится ответ Кейта Хилла , за исключением того, что в нем есть ошибка, которая не позволяет ему повторяться после двух уровней. Эти команды показывают ошибку:

New-Item level1/level2/level3/level4/foobar.txt -Force -ItemType file
cd level1
GetFiles . xyz | % { $_.fullname }

С оригинальным кодом Хилла вы получите:

...\level1\level2
...\level1\level2\level3

Вот исправленная и слегка переработанная версия:

function GetFiles($path = $pwd, [string[]]$exclude)
{
    foreach ($item in Get-ChildItem $path)
    {
        if ($exclude | Where {$item -like $_}) { continue }

        $item
        if (Test-Path $item.FullName -PathType Container)
        {
            GetFiles $item.FullName $exclude
        }
    }
} 

После исправления этой ошибки вы получите исправленный вывод:

...\level1\level2
...\level1\level2\level3
...\level1\level2\level3\level4
...\level1\level2\level3\level4\foobar.txt

Мне также нравится ответ Айка для краткости, хотя, как он указывает, он менее эффективен. Кстати, причина, по которой он менее эффективен, заключается в том, что алгоритм Хилла перестает проходить через поддерево, когда находит цель обрезки, а ajk продолжает. Но ответ Айка также страдает недостатком, который я называю ловушкой предков . Рассмотрим такой путь, который включает в себя один и тот же компонент пути (то есть subdir2) дважды:

\usr\testdir\subdir2\child\grandchild\subdir2\doc

Установите ваше местоположение где-то посередине, например, cd \usr\testdir\subdir2\child, затем запустите алгоритм ajk, чтобы отфильтровать нижний subdir2, и вы получите no output вообще, то есть он отфильтрует все из-за присутствия subdir2 выше в пути. Однако это ключевой случай, и его вряд ли удастся часто атаковать, поэтому я бы не стал исключать решение ajk из-за этой проблемы.

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

$excludeList = @("stuff","bin","obj*")
Get-ChildItem -Recurse | % {
    $pathParts = $_.FullName.substring($pwd.path.Length + 1).split("\");
    if ( ! ($excludeList | where { $pathParts -like $_ } ) ) { $_ }
}

Мой алгоритм достаточно лаконичен, но, как и ajk, он менее эффективен, чем алгоритм Хилла (по той же причине: он не останавливает обход поддеревьев у целей обрезки). Тем не менее, мой код имеет важное преимущество перед Hill - он может работать! Поэтому можно вписаться в цепочку фильтров, чтобы создать собственную версию Get-ChildItem, в то время как рекурсивный алгоритм Хилла, не по своей вине, не может. Алгоритм ajk также может быть адаптирован к конвейерному использованию, но указание исключаемого элемента или элементов не так чисто, поскольку оно встроено в регулярное выражение, а не в простой список элементов, которые я использовал.

Я упаковал код удаления дерева в расширенную версию Get-ChildItem. Помимо моего довольно невообразимого имени - Get-EnhancedChildItem - я очень рад этому и включил его в мою открытую библиотеку Powershell с открытым исходным кодом. Он включает в себя несколько других новых возможностей помимо обрезки деревьев. Кроме того, код предназначен для расширения: если вы хотите добавить новую возможность фильтрации, это сделать несложно. По сути, сначала вызывается Get-ChildItem, который передается по конвейеру в каждый последующий фильтр, который вы активируете с помощью параметров команды. Таким образом, что-то вроде этого ...

Get-EnhancedChildItem –Recurse –Force –Svn
    –Exclude *.txt –ExcludeTree doc*,man -FullName -Verbose 

... внутренне преобразуется в это:

Get-ChildItem | FilterExcludeTree | FilterSvn | FilterFullName

Каждый фильтр должен соответствовать определенным правилам: принимать объекты FileInfo и DirectoryInfo в качестве входных данных, генерировать их так же, как выходные данные, и использовать stdin и stdout, чтобы его можно было вставить в конвейер. Вот тот же код, переработанный для соответствия этим правилам:

filter FilterExcludeTree()
{
  $target = $_
  Coalesce-Args $Path "." | % {
    $canonicalPath = (Get-Item $_).FullName
    if ($target.FullName.StartsWith($canonicalPath)) {
      $pathParts = $target.FullName.substring($canonicalPath.Length + 1).split("\");
      if ( ! ($excludeList | where { $pathParts -like $_ } ) ) { $target }
    }
  }
} 

Единственным дополнительным компонентом здесь является функция Coalesce-Args (найденная в этой записи Китом Далби), которая просто отправляет текущий каталог по конвейеру в случае, если в вызове не указаны пути ,

Поскольку этот ответ становится несколько длиннее, а не углубляюсь в подробности об этом фильтре, я отсылаю заинтересованного читателя к моей недавно опубликованной статье на Simple-Talk.com под названием Практическая PowerShell: удаление файловых деревьев и расширение командлетов где я обсуждаю Get-EnhancedChildItem еще более подробно. Последнее, что я упомяну, это еще одна функция в моей библиотеке с открытым исходным кодом, New-FileTree , которая позволяет вам генерировать фиктивное дерево файлов для целей тестирования, чтобы вы могли использовать любой из вышеперечисленных алгоритмов. И когда вы экспериментируете с любым из них, я рекомендую подключиться к % { $_.fullname }, как я делал в самом первом фрагменте кода, для более полезного вывода.

25 голосов
/ 06 ноября 2011

Командлет Get-ChildItem имеет параметр -Exclude, который заманчиво использовать, но он не работает для фильтрации целых каталогов из того, что я могу сказать. Попробуйте что-то вроде этого:

function GetFiles($path = $pwd, [string[]]$exclude) 
{ 
    foreach ($item in Get-ChildItem $path)
    {
        if ($exclude | Where {$item -like $_}) { continue }

        if (Test-Path $item.FullName -PathType Container) 
        {
            $item 
            GetFiles $item.FullName $exclude
        } 
        else 
        { 
            $item 
        }
    } 
}
11 голосов
/ 07 ноября 2011

Вот еще один вариант, который менее эффективен, но более лаконичен.Вот как я обычно решаю проблему такого рода:

Get-ChildItem -Recurse .\targetdir -Exclude *.log |
  Where-Object { $_.FullName -notmatch '\\excludedir($|\\)' }

Выражение \\excludedir($|\\)' позволяет исключить каталог и его содержимое одновременно.

Обновление: Пожалуйста, проверьте отличный ответ от msorens , чтобы узнать о недостатке в крайнем случае с этим подходом и о гораздо более конкретном решении в целом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...