如何从PowerShell中检索递归目录和文件列表,不包括某些文件和文件夹?

时间:2011-11-05 23:29:31

标签: powershell powershell-v2.0

我想编写一个PowerShell脚本,它将递归搜索目录,但排除指定的文件(例如,*.logmyFile.txt),并且还排除指定的目录及其内容(对于例如,myDir以及myDir下面的所有文件和文件夹。

我一直在使用Get-ChildItem CmdLet和Where-Object CmdLet,但我似乎无法得到这种确切的行为。

6 个答案:

答案 0 :(得分:54)

我喜欢Keith Hill的回答,除了它有一个错误,阻止它递过两个级别。这些命令显示了错误:

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的答案,但是正如他所指出的那样,效率较低。顺便说一下,它效率较低的原因是因为当ajk继续时,Hill的算法在找到修剪目标时停止遍历子树。但是ajk的答案也有一个缺陷,我称之为祖先陷阱。考虑一个这样的路径,它包括两次相同的路径组件(即subdir2):

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

将您的位置设置在两者之间,例如cd \usr\testdir\subdir2\child,然后运行ajk的算法来过滤掉较低的subdir2,你将得到 no 输出,即由于{{1}的存在而过滤掉所有内容在路径中更高。但这是一个极端情况,并且不太可能经常受到影响,因此我不排除因为这个问题而得出ajk的解决方案。

尽管如此,我在这里提供了第三个替代,其中一个具有上述两个错误中的任何一个。这是基本算法,包含修剪路径的便利性定义 - 您只需将subdir2修改为您自己的目标集即可使用它:

$excludeList

我的算法相当简洁但是,就像ajk一样,它的效率低于Hill(因为同样的原因:它不会停止遍历修剪目标的子树)。但是,我的代码比Hill更重要 - 它可以管道!因此,它适合于过滤器链来制作Get-ChildItem的自定义版本,而Hill的递归算法,通过它自己的故障,不能。 ajk的算法也可以适应管道使用,但指定要排除的项目不是很干净,嵌入在正则表达式中,而不是我使用过的简单项目列表。

我已将树修剪代码打包到Get-ChildItem的增强版本中。除了我相当缺乏想象力的名字 - Get-EnhancedChildItem - 我对此感到很兴奋并将其包含在我的open source Powershell library中。除了树修剪之外,它还包括其他几项新功能。此外,代码设计为可扩展的:如果要添加新的过滤功能,则可以直接执行。本质上,首先调用Get-ChildItem,并将其流水线化为通过命令参数激活的每个连续过滤器。这样的事情......

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

...在内部转换为:

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

每个过滤器必须符合某些规则:接受FileInfo和DirectoryInfo对象作为输入,生成与输出相同,并使用stdin和stdout,以便可以将其插入管道中。以下是针对这些规则重构的相同代码:

Get-ChildItem | FilterExcludeTree | FilterSvn | FilterFullName

这里唯一的附加部分是Coalesce-Args函数(由Keith Dahlby在this post中找到),它只是在调用没有指定任何路径的情况下将当前目录发送到管道。

因为这个答案有点冗长,而不是详细介绍这个过滤器,我将感兴趣的读者引用到我最近在Simple-Talk.com上发表的题为Practical PowerShell: Pruning File Trees and Extending Cmdlets的文章,其中我讨论了Get-EnhancedChildItem at更长的。不过,我要提到的最后一件事是我的开源库中的另一个函数New-FileTree,它允许您生成用于测试目的的虚拟文件树,以便您可以执行上述任何算法。当您尝试其中任何一个时,我建议像第一个代码片段中那样使用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 } } } } 来管道,以便检查更有用的输出。

答案 1 :(得分:25)

Get-ChildItem cmdlet有一个-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 :(得分:11)

这是另一种选择,效率较低但更简洁。这就是我通常处理这类问题的方法:

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

\\excludedir($|\\)'表达式允许您同时排除目录及其内容。

更新:请查看 msorens 的优秀答案,了解使用此方法的边缘情况漏洞,以及更加充实的解决方案。

答案 3 :(得分:0)

最近,我探索了参数化扫描文件夹的可能性以及递归扫描结果的存储位置。最后,我还总结了扫描的文件夹数量以及其中的文件数量。与社区共享以防它可能对其他开发人员有所帮助。

    ##Script Starts
    #read folder to scan and file location to be placed

    $whichFolder = Read-Host -Prompt 'Which folder to Scan?'  
    $whereToPlaceReport = Read-Host -Prompt 'Where to place Report'
    $totalFolders = 1
    $totalFiles = 0

    Write-Host "Process started..."

    #IMP separator ? : used as a file in window cannot contain this special character in the file name

    #Get Foldernames into Variable for ForEach Loop
    $DFSFolders = get-childitem -path $whichFolder | where-object {$_.Psiscontainer -eq "True"} |select-object name ,fullName

    #Below Logic for Main Folder
    $mainFiles = get-childitem -path "C:\Users\User\Desktop" -file
    ("Folder Path" + "?" + "Folder Name" + "?" + "File Name " + "?"+ "File Length" )| out-file "$whereToPlaceReport\Report.csv" -Append

    #Loop through folders in main Directory
    foreach($file in $mainFiles)
    {

    $totalFiles = $totalFiles + 1
    ("C:\Users\User\Desktop" + "?" + "Main Folder" + "?"+ $file.name + "?" + $file.length ) | out-file "$whereToPlaceReport\Report.csv" -Append
    }


    foreach ($DFSfolder in $DFSfolders)
    {
    #write the folder name in begining
    $totalFolders = $totalFolders + 1

    write-host " Reading folder C:\Users\User\Desktop\$($DFSfolder.name)"
    #$DFSfolder.fullName | out-file "C:\Users\User\Desktop\PoC powershell\ok2.csv" -Append
    #For Each Folder obtain objects in a specified directory, recurse then filter for .sft file type, obtain the filename, then group, sort and eventually show the file name and total incidences of it.

    $files = get-childitem -path "$whichFolder\$($DFSfolder.name)" -recurse

    foreach($file in $files)
    {
    $totalFiles = $totalFiles + 1
    ($DFSfolder.fullName + "?" + $DFSfolder.name + "?"+ $file.name + "?" + $file.length ) | out-file "$whereToPlaceReport\Report.csv" -Append
    }

    }


    # If running in the console, wait for input before closing.
    if ($Host.Name -eq "ConsoleHost")
    {

    Write-Host "" 
    Write-Host ""
    Write-Host ""

    Write-Host  "                            **Summary**"  -ForegroundColor Red
    Write-Host  "                            ------------" -ForegroundColor Red

    Write-Host  "                           Total Folders Scanned = $totalFolders "  -ForegroundColor Green
    Write-Host  "                           Total Files   Scanned = $totalFiles "     -ForegroundColor Green

    Write-Host "" 
    Write-Host "" 
        Write-Host "I have done my Job,Press any key to exit" -ForegroundColor white
        $Host.UI.RawUI.FlushInputBuffer()   # Make sure buffered input doesn't "press a key" and skip the ReadKey().
        $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
    }

##Output

enter image description here

##Bat Code to run above powershell command

@ECHO OFF
SET ThisScriptsDirectory=%~dp0
SET PowerShellScriptPath=%ThisScriptsDirectory%MyPowerShellScript.ps1
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""%PowerShellScriptPath%""' -Verb RunAs}";

答案 4 :(得分:0)

有点晚了,但是尝试这个。

function Set-Files($Path) {
    if(Test-Path $Path -PathType Leaf) {
        # Do any logic on file
        Write-Host $Path
        return
    }

    if(Test-Path $path -PathType Container) {
        # Do any logic on folder use exclude on get-childitem
        # cycle again
        Get-ChildItem -Path $path | foreach { Set-Files -Path $_.FullName }
    }
}

# call
Set-Files -Path 'D:\myFolder'

答案 5 :(得分:0)

在这里评论,因为这似乎是搜索文件的主题中最流行的答案,而排除了powershell中的某些目录。

为避免对结果进行后期过滤(例如,避免权限问题等),我只需要过滤掉顶层目录,而这正是此示例所基于的,因此尽管此示例不过滤子目录名称,如果您愿意,可以很容易地将其递归以支持此操作。

代码段的工作原理快速细分

$ folders <<使用Get-Childitem查询文件系统并执行文件夹排除

$ file <<我正在寻找的文件模式

foreach <<使用Get-Childitem命令迭代$ folders变量以执行递归搜索

$folders = Get-ChildItem -Path C:\ -Directory -Name -Exclude Folder1,"Folder 2"
$file = "*filenametosearchfor*.extension"

foreach ($folder in $folders) {
   Get-Childitem -Path "C:/$folder" -Recurse -Filter $file | ForEach-Object { Write-Output $_.FullName }
}