web-dev-qa-db-fra.com

Comment récupérer un répertoire récursif et une liste de fichiers à partir de PowerShell à l'exclusion de certains fichiers et dossiers?

Je souhaite écrire un script PowerShell qui recherchera récursivement un répertoire, mais exclura les fichiers spécifiés (par exemple, *.log, et myFile.txt) et exclut également les répertoires spécifiés et leur contenu (par exemple, myDir et tous les fichiers et dossiers sous myDir).

Je travaille avec le Get-ChildItem CmdLet, et le Where-Object CmdLet, mais je n'arrive pas à obtenir ce comportement exact.

27
Sako73

L'applet de commande Get-ChildItem a un -Exclude paramètre qui est tentant d'utiliser mais qui ne fonctionne pas pour filtrer des répertoires entiers de ce que je peux dire. Essayez quelque chose comme ceci:

 fonction GetFiles ($ path = $ pwd, [string []] $ exclude) 
 {
 foreach ($ item dans le chemin Get-ChildItem $) 
 { 
 if ($ exclude | Where {$ item -like $ _}) {continue} 
 
 if (Test-Path $ item.FullName -PathType Container) 
 {
 $ item 
 GetFiles $ item.FullName $ exclut 
} 
 sinon 
 {
 $ item 
} 
} 
} 
25
Keith Hill

J'aime la réponse de Keith Hill sauf qu'il a un bug qui l'empêche de se reproduire au-delà des deux niveaux. Ces commandes manifestent le bogue:

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

Avec le code original de Hill, vous obtenez ceci:

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

Voici une version corrigée et légèrement refactorisée:

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

Avec ce correctif de bogue en place, vous obtenez cette sortie corrigée:

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

J'aime aussi la réponse d'ajk pour la concision, cependant, comme il le fait remarquer, elle est moins efficace. La raison pour laquelle il est moins efficace, soit dit en passant, est que l'algorithme de Hill cesse de traverser un sous-arbre lorsqu'il trouve une cible Prune pendant que ajk continue. Mais la réponse d'Ajk souffre également d'un défaut, celui que j'appelle le piège des ancêtres. Considérons un chemin tel que celui-ci qui inclut deux fois le même composant de chemin (c'est-à-dire subdir2):

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

Définissez votre emplacement quelque part entre les deux, par exemple cd \usr\testdir\subdir2\child, Puis exécutez l'algorithme d'ajk pour filtrer le subdir2 Inférieur et vous obtiendrez une sortie non, c'est-à-dire qu'il filtre tout à cause de la présence de subdir2 Plus haut dans le chemin. Ceci est un cas d'angle, cependant, et il est peu probable qu'il soit souvent touché, donc je n'exclurais pas la solution d'ajk en raison de ce seul problème.

Néanmoins, je propose ici une troisième alternative , une qui ne pas a l'un des deux bugs ci-dessus. Voici l'algorithme de base, complet avec une définition pratique pour le chemin ou les chemins vers Prune - vous n'avez qu'à modifier $excludeList À votre propre ensemble de cibles pour l'utiliser:

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

Mon algorithme est raisonnablement concis mais, comme ajk, il est moins efficace que Hill (pour la même raison: il n'arrête pas de traverser les sous-arbres sur les cibles Prune). Cependant, mon code a un avantage important sur Hill's - il peut canaliser! Il est donc possible de s'insérer dans une chaîne de filtres pour créer une version personnalisée de Get-ChildItem alors que l'algorithme récursif de Hill, sans aucune faute de sa part, ne le peut pas. L'algorithme d'ajk peut également être adapté à l'utilisation du pipeline, mais la spécification de l'élément ou des éléments à exclure n'est pas aussi claire, étant incorporée dans une expression régulière plutôt que dans une simple liste d'éléments que j'ai utilisée.

J'ai empaqueté mon code d'élagage d'arbre dans une version améliorée de Get-ChildItem. Mis à part mon nom plutôt peu imaginatif -- Get-EnhancedChildItem - Je suis excité à ce sujet et l'ai inclus dans mon bibliothèque Powershell open source . Il comprend plusieurs autres nouvelles fonctionnalités en plus de l'élagage des arbres. De plus, le code est conçu pour être extensible: si vous voulez ajouter une nouvelle capacité de filtrage, c'est simple à faire. Essentiellement, Get-ChildItem est appelé en premier et acheminé par pipeline dans chaque filtre successif que vous activez via les paramètres de commande. Donc quelque chose comme ça ...

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

... est converti en interne en ceci:

Get-ChildItem | FilterExcludeTree | FilterSvn | FilterFullName

Chaque filtre doit respecter certaines règles: accepter les objets FileInfo et DirectoryInfo en tant qu'entrées, générer les mêmes que les sorties et utiliser stdin et stdout pour qu'il puisse être inséré dans un pipeline. Voici le même code refactorisé pour s'adapter à ces règles:

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

La seule pièce supplémentaire ici est la fonction Coalesce-Args (trouvée dans ce message par Keith Dahlby), qui envoie simplement le répertoire actuel dans le tuyau au cas où l'invocation ne spécifierait aucun chemin.

Parce que cette réponse devient un peu longue, plutôt que d'entrer dans les détails de ce filtre, je renvoie le lecteur intéressé à mon article récemment publié sur Simple-Talk.com intitulé PowerShell pratique: élagage des arbres de fichiers et extension des applets de commande = où je discute de Get-EnhancedChildItem encore plus longuement. Une dernière chose que je mentionnerai, cependant, est une autre fonction de ma bibliothèque open source, New-FileTree , qui vous permet de générer une arborescence de fichiers factice à des fins de test afin que vous puissiez exercer l'un des algorithmes ci-dessus. Et lorsque vous expérimentez l'un de ces éléments, je recommande de diriger vers % { $_.fullname } Comme je l'ai fait dans le tout premier fragment de code pour une sortie plus utile à examiner.

54
Michael Sorens

Voici une autre option, moins efficace mais plus concise. C'est comme ça que je gère généralement ce genre de problème:

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

L'expression \\excludedir($|\\)' vous permet d'exclure le répertoire et son contenu en même temps.

Mise à jour: Veuillez vérifier l'excellente réponse de msorens pour un défaut de cas Edge avec cette approche, et une solution beaucoup plus étoffée dans l'ensemble.

11
ajk