使用 PowerShell 计算文件夹深度
Counting Folder Depth with PowerShell
1.代码说明 别名它是如何工作的
用户在 PowerShell 中输入目录路径。代码检查声明目录中的任何文件夹是否根本不包含任何数据。如果是这样,任何空文件夹的路径都会在提示中显示给用户,并最终从系统中删除。
2。问题 别名是我正在努力解决的问题
我刚刚编写的代码没有像我预期的那样计算文件夹层次结构的深度(输出 table 中的列为空白)。除此之外,该程序运行良好——我仍然需要解决我的代码先删除空父目录然后再删除子目录的问题,这当然会导致 PowerShell 出错;例如,取
C:\Users\JohnMiller\Desktop\Homework
其中 Homework
由 Homework\Math\School Project
和 Homework\Computer Science\PowerShell Code
组成。请注意,所有目录都应该是空的,但 PowerShell Code
除外,该文件夹包含此脚本。 (旁注:当文件夹中没有文件时,该文件夹被认为是空的。至少我的代码目前是基于此的。)
3。代码
# Delete all empty (sub)folders in [$path]
[Console]::WriteLine("`n>> Start script for deleting all empty (sub)folders.")
$path = Read-Host -prompt ">> Specify a path"
if (test-path $path)
{
$allFolders = Get-ChildItem $path -recurse | Where {$_.PSisContainer -eq $True}
$allEmptyFolders = $allFolders | Where-Object {$_.GetFiles().Count -eq 0}
$allEmptyFolders | Select-Object FullName,@{Name = "FolderDepth"; Expression = {$_.DirectoryName.Split('\').Count}} | Sort-Object -descending FolderDepth,FullName
[Console]::WriteLine("`n>> Do you want do remove all these directories? Validate with [True] or [False].") #'#
$answer = Read-Host -prompt ">> Answer"
if ([System.Convert]::ToBoolean($answer) -eq $True)
{
$allEmptyFolders | Remove-Item -force -recurse
}
else
{
[Console]::WriteLine(">> Termination confirmed.`n")
exit
}
}
else
{
[Console]::WriteLine(">> ERROR: [$($path)] is an invalid directory. Program terminates.`n")
exit
}
深度计数问题:
您的代码在传递给 Select-Object
的计算 属性 中引用了 .DirectoryName
属性,但是 Get-ChildItem
输出的 [System.IO.DirectoryInfo]
个实例没有这样的属性。使用 .FullName
属性 代替:
$allEmptyFolders |
Select-Object FullName,@{Name='FolderDepth'; Expression={$_.FullName.Split('\').Count}} |
Sort-Object -descending FolderDepth,FullName
消除嵌套的空子文件夹:
用一个简单的例子来概括你的问题:
如果 c:\foo
为空(无文件)但子目录为空。 c:\foo\bar
,你的代码输出它们 both,如果你先删除 c:\foo
,接下来删除 c:\foo\bar
会失败(因为删除 c:\foo
也删除了 c:\foo\bar
).
如果消除所有嵌套的空子目录。在前面,您不仅可以整理呈现给用户的内容,还可以安全地迭代输出并一一删除。
使用您的方法,您需要第二步来消除嵌套的空目录。但这里有一个深度优先递归函数,它忽略了嵌套的空文件夹。要使其在隐藏文件方面的行为与您的代码相同,请传递 -Force
.
function Get-RecursivelyEmptyDirectories {
[cmdletbinding()]
param(
[string] $LiteralPath = '.',
[switch] $Force,
[switch] $DoNotValidatePath
)
$ErrorActionPreference = 'Stop'
if (-not $DoNotValidatePath) {
$dir = Get-Item -LiteralPath $LiteralPath
if (-not $dir.PSIsContainer) { Throw "Not a directory path: $LiteralPath" }
$LiteralPath = $dir.FullName
}
$haveFiles = [bool] (Get-ChildItem -LiteralPath $LiteralPath -File -Force:$Force | Select-Object -First 1)
$emptyChildDirCount = 0
$emptySubdirs = $null
if ($childDirs = Get-ChildItem -LiteralPath $LiteralPath -Directory -Force:$Force) {
$emptySubDirs = New-Object System.Collections.ArrayList
foreach($childDir in $childDirs) {
if ($childDir.LinkType -eq 'SymbolicLink') {
Write-Verbose "Ignoring symlink: $LiteralPath"
} else {
Write-Verbose "About to recurse on $($childDir.FullName)..."
try { # If .AddRange() fails due to exceeding the array list's capacity, we must fail too.
$emptySubDirs.AddRange(@(Get-RecursivelyEmptyDirectories -DoNotValidatePath -LiteralPath $childDir.FullName -Force:$Force))
} catch {
Throw
}
# If the last entry added is the child dir. at hand, that child dir.
# is by definition itself empty.
if ($emptySubDirs[-1] -eq $childDir.FullName) { ++$emptyChildDirCount }
}
} # foreach ($childDir ...
} # if ($childDirs = ...)
if (-not $haveFiles -and $emptyChildDirCount -eq $childDirs.Count) {
# There are no child files and all child dirs., if any, are themselves
# empty, so we only output the input path at hand, as the highest
# directory in this subtree that is empty (save for empty descendants).
$LiteralPath
} else {
# This directory is not itself empty, so output the (highest-level)
# descendants that are empty.
$emptySubDirs
}
}
关于您的代码的提示:
Get-ChildItem -Directory
在PSv3+中可用,比Get-ChildItem | .. Where { $_.PSisContainer -eq $True }
.
不仅更短而且效率更高
使用Write-Host
代替[Console]::WriteLine
[System.Convert]::ToBoolean($answer)
仅适用于文化不变的字符串文字 'True'
和 'False'
([bool]::TrueString
和 [bool]::FalseString
,尽管大小写变体和前导和尾随空格是允许的)。
1.代码说明 别名它是如何工作的
用户在 PowerShell 中输入目录路径。代码检查声明目录中的任何文件夹是否根本不包含任何数据。如果是这样,任何空文件夹的路径都会在提示中显示给用户,并最终从系统中删除。
2。问题 别名是我正在努力解决的问题
我刚刚编写的代码没有像我预期的那样计算文件夹层次结构的深度(输出 table 中的列为空白)。除此之外,该程序运行良好——我仍然需要解决我的代码先删除空父目录然后再删除子目录的问题,这当然会导致 PowerShell 出错;例如,取
C:\Users\JohnMiller\Desktop\Homework
其中 Homework
由 Homework\Math\School Project
和 Homework\Computer Science\PowerShell Code
组成。请注意,所有目录都应该是空的,但 PowerShell Code
除外,该文件夹包含此脚本。 (旁注:当文件夹中没有文件时,该文件夹被认为是空的。至少我的代码目前是基于此的。)
3。代码
# Delete all empty (sub)folders in [$path]
[Console]::WriteLine("`n>> Start script for deleting all empty (sub)folders.")
$path = Read-Host -prompt ">> Specify a path"
if (test-path $path)
{
$allFolders = Get-ChildItem $path -recurse | Where {$_.PSisContainer -eq $True}
$allEmptyFolders = $allFolders | Where-Object {$_.GetFiles().Count -eq 0}
$allEmptyFolders | Select-Object FullName,@{Name = "FolderDepth"; Expression = {$_.DirectoryName.Split('\').Count}} | Sort-Object -descending FolderDepth,FullName
[Console]::WriteLine("`n>> Do you want do remove all these directories? Validate with [True] or [False].") #'#
$answer = Read-Host -prompt ">> Answer"
if ([System.Convert]::ToBoolean($answer) -eq $True)
{
$allEmptyFolders | Remove-Item -force -recurse
}
else
{
[Console]::WriteLine(">> Termination confirmed.`n")
exit
}
}
else
{
[Console]::WriteLine(">> ERROR: [$($path)] is an invalid directory. Program terminates.`n")
exit
}
深度计数问题:
您的代码在传递给 Select-Object
的计算 属性 中引用了 .DirectoryName
属性,但是 Get-ChildItem
输出的 [System.IO.DirectoryInfo]
个实例没有这样的属性。使用 .FullName
属性 代替:
$allEmptyFolders |
Select-Object FullName,@{Name='FolderDepth'; Expression={$_.FullName.Split('\').Count}} |
Sort-Object -descending FolderDepth,FullName
消除嵌套的空子文件夹:
用一个简单的例子来概括你的问题:
如果 c:\foo
为空(无文件)但子目录为空。 c:\foo\bar
,你的代码输出它们 both,如果你先删除 c:\foo
,接下来删除 c:\foo\bar
会失败(因为删除 c:\foo
也删除了 c:\foo\bar
).
如果消除所有嵌套的空子目录。在前面,您不仅可以整理呈现给用户的内容,还可以安全地迭代输出并一一删除。
使用您的方法,您需要第二步来消除嵌套的空目录。但这里有一个深度优先递归函数,它忽略了嵌套的空文件夹。要使其在隐藏文件方面的行为与您的代码相同,请传递 -Force
.
function Get-RecursivelyEmptyDirectories {
[cmdletbinding()]
param(
[string] $LiteralPath = '.',
[switch] $Force,
[switch] $DoNotValidatePath
)
$ErrorActionPreference = 'Stop'
if (-not $DoNotValidatePath) {
$dir = Get-Item -LiteralPath $LiteralPath
if (-not $dir.PSIsContainer) { Throw "Not a directory path: $LiteralPath" }
$LiteralPath = $dir.FullName
}
$haveFiles = [bool] (Get-ChildItem -LiteralPath $LiteralPath -File -Force:$Force | Select-Object -First 1)
$emptyChildDirCount = 0
$emptySubdirs = $null
if ($childDirs = Get-ChildItem -LiteralPath $LiteralPath -Directory -Force:$Force) {
$emptySubDirs = New-Object System.Collections.ArrayList
foreach($childDir in $childDirs) {
if ($childDir.LinkType -eq 'SymbolicLink') {
Write-Verbose "Ignoring symlink: $LiteralPath"
} else {
Write-Verbose "About to recurse on $($childDir.FullName)..."
try { # If .AddRange() fails due to exceeding the array list's capacity, we must fail too.
$emptySubDirs.AddRange(@(Get-RecursivelyEmptyDirectories -DoNotValidatePath -LiteralPath $childDir.FullName -Force:$Force))
} catch {
Throw
}
# If the last entry added is the child dir. at hand, that child dir.
# is by definition itself empty.
if ($emptySubDirs[-1] -eq $childDir.FullName) { ++$emptyChildDirCount }
}
} # foreach ($childDir ...
} # if ($childDirs = ...)
if (-not $haveFiles -and $emptyChildDirCount -eq $childDirs.Count) {
# There are no child files and all child dirs., if any, are themselves
# empty, so we only output the input path at hand, as the highest
# directory in this subtree that is empty (save for empty descendants).
$LiteralPath
} else {
# This directory is not itself empty, so output the (highest-level)
# descendants that are empty.
$emptySubDirs
}
}
关于您的代码的提示:
Get-ChildItem -Directory
在PSv3+中可用,比Get-ChildItem | .. Where { $_.PSisContainer -eq $True }
. 不仅更短而且效率更高
使用
Write-Host
代替[Console]::WriteLine
[System.Convert]::ToBoolean($answer)
仅适用于文化不变的字符串文字'True'
和'False'
([bool]::TrueString
和[bool]::FalseString
,尽管大小写变体和前导和尾随空格是允许的)。