web-dev-qa-db-ja.com

何百万ものファイルがあるフォルダーのファイルサイズとファイル数を見つけるPowerShellスクリプトですか?

スクリプトの目的は次のとおりです。

  1. ディレクトリ内で再帰的に見つかったファイルの数を印刷します(フォルダー自体は省略します)
  2. ディレクトリの合計ファイルサイズを出力します
  3. 大量のメモリ使用のためにコンピューターをクラッシュさせません。

これまでのところ(3)は難しい部分です。

ここに私がこれまでに書いてテストしたものを示します。これは、100個または1000個のファイルがあるフォルダーで完全に機能します。

$hostname=hostname
$directory = "foo"
$dteCurrentDate = Get-Date –f "yyyy/MM/dd"

$FolderItems = Get-ChildItem $directory -recurse
$Measurement = $FolderItems | Measure-Object -property length -sum
$colitems = $FolderItems | measure-Object -property length -sum
"$hostname;{0:N2}" -f ($colitems.sum / 1MB) + "MB;" + $Measurement.count + " files;" + "$dteCurrentDate"

ただし、数百万のファイルがあるフォルダーでは、$colitems変数は、数百万のファイルの情報の収集から非常に大きくなり、システムを不安定にします。この情報を描画して保存するより効率的な方法はありますか?

21
Stephen Wood

ストリーミングとパイプラインを使用する場合は、(3)の問題を大幅に減らす必要があります。これは、ストリーミングすると、各オブジェクトが使用可能なときにパイプラインに沿って渡され、多くのメモリを消費せず、数百万のファイルを処理します(ただし時間がかかります)。

_Get-ChildItem $directory -recurse | Measure-Object -property length -sum
_

@Stejのステートメント_Get-ChildItem probably reads all entries in the directory and then begins pushing them to the pipeline._が正しいとは思わない。パイプライン処理は、PowerShellの基本的な概念です(コマンドレット、スクリプトなどがそれをサポートすることを提供します)。両方とも、処理済みオブジェクトがパイプラインに沿って、利用可能なときに1つずつ渡され、必要なときはonlyも渡されます。 _Get-ChildItem_の動作は変わりません。

このすばらしい例は、Windows PowerShellパイプラインについてにあります。

それから引用:

Out-Host -Pagingコマンドは、出力を長く表示し、ゆっくり表示したい場合に便利なパイプライン要素です。操作が非常にCPUを集中的に使用する場合に特に役立ちます。表示の準備が完了した完全なページがある場合、処理はOut-Hostコマンドレットに転送されるため、パイプラインで先行するコマンドレットは、出力の次のページが使用可能になるまで操作を停止します。これは、Windowsタスクマネージャーを使用して、Windows PowerShellによるCPUとメモリの使用を監視する場合に確認できます。

次のコマンドを実行します:_Get-ChildItem C:\Windows -Recurse_。 CPUとメモリの使用量をこのコマンドと比較してください:_Get-ChildItem C:\Windows -Recurse | Out-Host -Paging_。

_Get-ChildItem_で_c:\_を使用する場合のベンチマーク(約179516個のファイル、数百万個ではなく、十分な数):

_$a = gci c:\ -recurse_を実行した後(および_$a.count_を実行した後)のメモリ使用量は_527,332K_でした。

_gci c:\ -recurse | measure-object_を実行した後のメモリ使用量は_59,452K_であり、_80,000K_を超えることはありませんでした。

(メモリ-プライベートワーキングセット-TaskManagerから、_powershell.exe_プロセスのメモリを参照。最初は、_22,000K_についてでした。)

また、200万個のファイルを試してみました(作成に時間がかかりました!)

同様の実験:

_$a = gci c:\ -recurse_を実行した後(および_$a.count_を実行した後)のメモリ使用量は_2,808,508K_でした。

_gci c:\ -recurse | measure-object_の実行中のメモリ使用量は_308,060K_であり、_400,000K_を超えることはありませんでした。終了後、[GC]::Collect()を実行して_22,000K_レベルに戻る必要がありました。

_Get-ChildItem_とパイプライン処理により、数百万のファイルに対してもメモリを大幅に改善できると確信しています。

34
manojlds

Get-ChildItemは、おそらくディレクトリ内のすべてのエントリを読み取り、パイプラインへのプッシュを開始します。その場合は Get-ChildItemはうまく動作しません。NET4.0に切り替えてEnumerateFilesEnumeratedDirectoriesを使用してみてください:

function Get-HugeDirStats($directory) {
    function go($dir, $stats)
    {
        foreach ($f in [system.io.Directory]::EnumerateFiles($dir))
        {
            $stats.Count++
            $stats.Size += (New-Object io.FileInfo $f).Length
        }
        foreach ($d in [system.io.directory]::EnumerateDirectories($dir))
        {
            go $d $stats
        }
    }
    $statistics = New-Object PsObject -Property @{Count = 0; Size = [long]0 }
    go $directory $statistics

    $statistics
}

#example
$stats = Get-HugeDirStats c:\windows

ここで最も高価な部分は、New-Object io.FileInfo $f。これは、EnumerateFilesがファイル名のみを返すためです。したがって、ファイルの数だけで十分な場合は、行をコメント化できます。

.NET 4.0の使用方法については、スタックオーバーフローの質問。NET 4ランタイムでPowerShellを実行するにはどうすればよいですか?を参照してください。


また、高速ですが、ディレクトリ内のすべてのファイルを読み取るプレーンな古いメソッドを使用することもできます。それはあなたのニーズに依存しますので、試してみてください。後ですべての方法の比較があります。

function Get-HugeDirStats2($directory) {
    function go($dir, $stats)
    {
        foreach ($f in $dir.GetFiles())
        {
            $stats.Count++
            $stats.Size += $f.Length
        }
        foreach ($d in $dir.GetDirectories())
        {
            go $d $stats
        }
    }
    $statistics = New-Object PsObject -Property @{Count = 0; Size = [long]0 }
    go (new-object IO.DirectoryInfo $directory) $statistics

    $statistics
}

比較

Measure-Command { $stats = Get-HugeDirStats c:\windows }
Measure-Command { $stats = Get-HugeDirStats2 c:\windows }
Measure-Command { Get-ChildItem c:\windows -recurse | Measure-Object -property length -sum }
TotalSeconds      : 64,2217378
...

TotalSeconds      : 12,5851008
...

TotalSeconds      : 20,4329362
...

@manojlds:パイプラインは基本的な概念です。しかし、概念としては、プロバイダーとは関係ありません。ファイルシステムプロバイダーは、遅延評価機能(〜列挙子)を持たない.NET実装(.NET 2.0)に依存しています。自分で確認してください。

9
stej

次の関数は非常にクールで、フォルダーのサイズをすばやく計算できますが、常に機能するとは限りません(特に、アクセス許可の問題またはフォルダーパスが長すぎる場合)。

Function sizeFolder($path) # Return the size in MB.
{
    $objFSO = New-Object -com  Scripting.FileSystemObject
    ("{0:N2}" -f (($objFSO.GetFolder($path).Size) / 1MB))
}
0
fretman92