我想编写一个PowerShell脚本,它将递归搜索目录,但排除指定的文件(例如,*.log
和myFile.txt
),并且还排除指定的目录及其内容(对于例如,myDir
以及myDir
下面的所有文件和文件夹。
我一直在使用Get-ChildItem
CmdLet和Where-Object
CmdLet,但我似乎无法得到这种确切的行为。
答案 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
##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 }
}