Как получить рекурсивный каталог и список файлов из PowerShell, исключая некоторые файлы и папки?

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

Я работал с Get-ChildItem CmdLet и Where-Object CmdLet, но Кажется, я не понимаю этого точного поведения.

Ответ 1

Командлет 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 
        }
    } 
}

Ответ 2

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

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

С исходным кодом Hill вы получите следующее:

...\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, и вы не получите никакого вывода вообще, т.е. он отфильтровывает все из-за наличия 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 (по той же причине: он не останавливает перемещение поддеревьев по черновикам). Тем не менее, мой код имеет важное преимущество перед Hill's - он может конвейер! Поэтому можно вписаться в цепочку фильтров, чтобы создать пользовательскую версию Get-ChildItem, в то время как рекурсивный алгоритм Hill, по своей собственной ошибке, не может. Алгоритм 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 (найденная в этот пост by Keith Dahlby), которая просто отправляет текущий каталог вниз в том случае, если в вызове не указаны какие-либо пути.

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

Ответ 3

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

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

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

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