【问题标题】:The most efficient way to delete millions of files based on modified date, in windows在 Windows 中根据修改日期删除数百万个文件的最有效方法
【发布时间】:2016-05-25 00:34:05
【问题描述】:

目标:使用脚本运行 500 万至 1000 万个 XML 文件并评估其日期,如果超过 90 天则删除该文件。该脚本将每天运行。

问题:使用 powershell Get-ChildItem -recurse,导致脚本锁定并且无法删除任何文件,我认为这是因为 Get-ChildItem 在对任何文件采取任何操作之前需要构建整个数组的方式.

解决方案?:经过大量研究,我发现 [System.IO.Directory]::EnumerateFiles 将能够在数组完全构建之前对数组中的项目采取行动,这样可以提高效率(@987654321 @)。经过更多测试,我发现foreach ($1 in $2)$1 | % {} 更有效 在我运行此新代码并可能再次使此服务器崩溃之前,是否有人可以建议任何调整以更有效地编写脚本?

为了测试,我刚刚在 15,000 个目录中创建了 15,000 x 0.02KB txt 文件,其中包含随机数据并运行以下代码,我在 $date 变量上使用了 90 秒而不是 90 天,仅用于测试,花了 6秒删除所有txt文件。

$getfiles = [System.IO.Directory]::EnumerateFiles("C:\temp", "*.txt", "AllDirectories")
$date = ([System.DateTime]::Now).AddSeconds(-90)
foreach ($2 in $getfiles) {
if ([System.IO.File]::GetLastWriteTime($2) -le $date) {
[System.IO.File]::Delete($2)
} #if
} #foreach

【问题讨论】:

  • 如果您还没有这样做,disable 8.3 filename generation 可能会有所帮助。首先阅读所有警告。做一两个备份。使用风险自负。
  • 只要您只是将 enumeratefiles 的输出保存到变量中,您就不会从 ienumerable 中获得任何好处,因为 PS 将等待该行完成后再继续(它不是异步方法) .您需要直接在循环、管道或类似的东西中使用它。

标签: powershell datetime delete-file memory-efficient


【解决方案1】:

处理 100,000 个文件 >= 90 天前的 Powershell 单行程序。

[IO.Directory]::EnumerateFiles("C:\FOLDER_WITH_FILES_TO_DELETE") |
select -first 100000 | where { [IO.File]::GetLastWriteTime($_) -lt
(Get-Date).AddDays(-90) } | foreach { rm $_ }

或显示进度:

[IO.Directory]::EnumerateFiles("C:\FOLDER_WITH_FILES_TO_DELETE") |
select -first 100000 | where { [IO.File]::GetLastWriteTime($_) -lt
(Get-Date).AddDays(-90) } | foreach { $c = 0 } { Write-Progress
-Activity "Delete Files" -CurrentOperation $_ -PercentComplete 
((++$c/100000)*100); rm $_ }

这适用于包含大量文件的文件夹。感谢我的同事 Doug!

【讨论】:

  • 我喜欢过滤数组中处理的最大文件数的想法,但可能没有必要看到,因为 [System.IO.Directory]::EnumerateFiles 可以在数组仍然存在时处理数组中的文件正在建设。还为每个文件调用 (Get-Date).AddDays(-90) 效率不高。那应该是一个静态变量。
【解决方案2】:

您可以通过在开始删除文件之前完全过滤$getfiles 数组来稍微调整一下。

在 PowerShell 3.0 和更高版本中,您可以通过使用 .Where({}) 扩展方法来完成此操作,而无需使用管道(这确实会增加一些开销):

$date  = (Get-Date).AddDays(-90)
$files = [System.IO.Directory]::EnumerateFiles("C:\temp", "*.txt", "AllDirectories").Where({[System.IO.File]::GetLastWriteTime($_) -le $date})
foreach($file in $files)
{
    [System.IO.File]::Delete($file)
}

由于您似乎并不关心它,最终的微小优化可能是完全放弃错误处理并直接调用 Windows API:

$Kernel32Util = Add-Type -MemberDefinition @'
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteFile(string filePath);
'@ -Name 'Kernel32Util' -Namespace 'NativeCode' -PassThru

然后使用新的外部函数包装器而不是 [File]::Delete() 执行与上述相同的操作:

foreach($file in $files)
{
    [void]$Kernel32Util::DeleteFile($file)
}

不过,在这一点上,我可能会退后一步,问一个问题:

“我是否使用了正确的工具来完成这项工作?”

我(个人)的回答是:“可能不会” - 是时候用编译语言(C#、F#、VB.NET)编写一个小实用程序了。

PowerShell 非常强大和有用,但以性能为代价 - 这不是一件坏事 - 在决定使用什么工具来完成特定任务时,这是值得考虑的事情:)

【讨论】:

  • 我确实让一个朋友写了一个 Windows 窗体应用程序来做同样的事情,对每个删除的文件进行额外的日志记录,而且速度很慢,不确定他用什么代码来构建数组并删除文件,但我确实提到了效率的需要。
【解决方案3】:

我最终为不同版本的 powershell 得到了几个略有不同的代码

#If powershell version is >3
$date = ([System.DateTime]::Now).AddDays(-30)
foreach ($2 in ([System.IO.Directory]::EnumerateFiles("D:\Folder to cleanup", "*.*", "AllDirectories").Where({[System.IO.File]::GetLastWriteTime($_) -le $date}))) {
[System.IO.File]::Delete($2)
} #foreach

#IF powershell version is >2.0 <3.0
$date = ([System.DateTime]::Now).AddDays(-30)
foreach ($2 in ([System.IO.Directory]::EnumerateFiles("D:\Folder to cleanup", "*.*", "AllDirectories"))) {
if ([System.IO.File]::GetLastWriteTime($2) -le $date) {
[System.IO.File]::Delete($2)
} #if
} #foreach

#IF powershell version is 2.0
$date = ([System.DateTime]::Now).AddDays(-30)
foreach ($2 in ([System.IO.Directory]::GetFiles("D:\Folder to cleanup", "*.*", "AllDirectories"))) {
if ([System.IO.File]::GetLastWriteTime($2) -le $date) {
[System.IO.File]::Delete($2)
} #if
} #foreach

【讨论】:

    猜你喜欢
    • 2023-03-22
    • 2019-02-15
    • 2015-06-02
    • 1970-01-01
    • 1970-01-01
    • 2012-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多