Sysinternals の du の結果を sort する

ディスク容量が不足気味で大きいデータを削除したいとき、個人 PC では WizTree で割り当て順にソートした結果を参考にしています。しかし、WizTree はサードパーティー製なので業務利用がためらわれます。

PowerShell スクリプトだけを使って容量と順位を求めることも考えられますが、NTFS 圧縮などの機能を使って容量を節約している場合、正しい結果を取得するのは困難です。

そこで、[Sysinternals の du コマンド](https://technet.microsoft.com/en-us/sysinternals/du.aspx)の CSV 出力を PowerShell でソートする方法を考えてみました。

## 忙しい人のための結論

du コマンドに PATH を通して下記コマンドを実行すれば、ほぼ正確にディスク上のサイズ トップ 10 が出ます。これは Linux でいう `du -sb * | sort -nr | head` のような感じです。-accepteula 引数によりツールの EULA に同意することになりますのでご注意ください。

```powershell
du -accepteula -nobanner -c -l 1 | ConvertFrom-Csv | Sort-Object -Descending {[decimal] $_.DirectorySizeOnDisk} | Select-Object -Skip 1 -First 10 | Format-Table
```

トップ 10 に絞らず、Out-GridView を使って全件表示してしまうのもありかもしれません。ただし、PowerShell Core では使えません。

```powershell
du -accepteula -nobanner -c -l 1 | ConvertFrom-Csv | Sort-Object -Descending {[decimal] $_.DirectorySizeOnDisk} | Select-Object -Skip 1 | Out-GridView
```

### WSL が利用できる場合

WSL が利用できる場合、WSL の du を実行してもそこそこ正確な結果を得られますが、AppData などの特殊なディレクトリでは計算を誤ることがあるようです。とはいえ、PowerShell を覚えなくて済むので便利です。

```bash
$ du -sh * | sort -hr | head
```


## 実験

ここからは、上記の結論に至る過程を説明します。Sysinternals du を使った場合にどの程度正しく結果を取得できているか確認するため、実験を行いました。PowerShell 初心者なので、説明がくどいかもしれません。

### 実験環境

dir ディレクトリに wiztree_3_20_setup.exe を保存し、適当にフォルダを作った後、粛々と mklink します。

```powershell
PS> cmd /c mklink /j dir_junction dir
dir_junction <<===>> dir のジャンクションが作成されました
PS> cmd /c mklink /d dir_symlink dir
dir_symlink <<===>> dir のシンボリック リンクが作成されました
PS> cmd /c mklink /h .\file_hardlink\wiztree_3_20_setup.exe .\dir\wiztree_3_20_setup.exe
.\file_hardlink\wiztree_3_20_setup.exe <<===>> .\dir\wiztree_3_20_setup.exe のハードリンクが作成されました
PS> cmd /c mklink .\file_symlink\wiztree_3_20_setup.exe .\dir\wiztree_3_20_setup.exe
.\file_symlink\wiztree_3_20_setup.exe <<===>> .\dir\wiztree_3_20_setup.exe のシンボリック リンクが作成されました
```

次のような環境が完成しました (一部の出力を秘密にしています)。

```powershell
PS> cmd /c dir
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は 秘密 です

 秘密 のディレクトリ

2018/04/22  12:56    <DIR>          .
2018/04/22  12:56    <DIR>          ..
2018/04/22  12:56    <DIR>          1byte
2018/04/22  12:49    <DIR>          compressed
2018/04/22  12:49    <DIR>          dir
2018/04/22  12:50    <JUNCTION>     dir_junction [秘密\dir]
2018/04/22  12:51    <SYMLINKD>     dir_symlink [dir]
2018/04/22  12:52    <DIR>          file_hardlink
2018/04/22  12:51    <DIR>          file_symlink
               0 個のファイル                   0 バイト
               9 個のディレクトリ  30,325,252,096 バイトの空き領域
PS> tree /f
フォルダー パスの一覧
ボリューム シリアル番号は 秘密 です
C:.
├─1byte
│      1byte.txt
│
├─compressed
│      get-help-get-help.txt
│
├─dir
│      wiztree_3_20_setup.exe
│
├─dir_junction
│      wiztree_3_20_setup.exe
│
├─dir_symlink
│      wiztree_3_20_setup.exe
│
├─file_hardlink
│      wiztree_3_20_setup.exe
│
└─file_symlink
        wiztree_3_20_setup.exe
```

* 1byte.txt は 1 byte のファイルです。
* get-help-get-help.txt には NTFS 圧縮がかかっています。

### CSV で出力し、ソートする

次のコマンドで du の結果を出力、ソート、整形しました。

```powershell
PS> du -nobanner -c -v | ConvertFrom-Csv | sort -d {[decimal] $_.DirectorySizeOnDisk} | select -f 10 | ft

Path               CurrentFileCount CurrentFileSize FileCount DirectoryCount DirectorySize DirectorySizeOnDisk
----               ---------------- --------------- --------- -------------- ------------- -------------------
秘密               0                0               3         6              3271949       3297496
秘密\dir           1                3259026         1         1              3259026       3260632
秘密\compressed    1                12922           1         1              12922         12288
秘密\1byte         1                1               1         1              1             4096
秘密\file_hardlink 1                0               0         1              0             0
秘密\file_symlink  1                0               0         1              0             0
```

ft は Format-Table のエイリアスです。下記コマンドで見つけました。

```powershell
PS> Get-Alias -Definition Format-Table

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           ft -> Format-Table
```

PowerShell の CSV からオブジェクトの配列 (でしたっけ？)、さらに表形式の文字列への変換など、初めて使いました。多機能ですね！

### Out-GridView を使えばいいじゃない！

その後 Out-GridView (ogv) というメチャ便利なコマンドがあることを知りました。これは Linux では味わったことのない快感……！

### 直下のディレクトリが常にトップになってしまうバグ

オプション -n を指定しない場合、DirectorySizeOnDisk は配下のファイルとディレクトリ全ての合計を表示するようです。そこで、-l 1 のランキングの1位を除いた10 件と、-n の結果を合わせた11件のデータからトップ 10 を表示するよう改めました。

```powershell
& {du -nobanner -c -l 1 | ConvertFrom-Csv | sort -d {[decimal] $_.DirectorySizeOnDisk} | select -First 10 -Skip 1; du -nobanner -c -n | ConvertFrom-Csv} | sort -d {[decimal] $_.DirectorySizeOnDisk} | select -First 10 | Format-Table
```

### du を一度も実行したことがないと動かないバグ

du コマンド自体の問題の気もしますが、初めての実行で -c オプションを使うと EULA の GUI 画面が出ずうまく動かないようです。-accepteula をつけました。

### うまくいったこと

* ファイルへのハードリンクやシンボリックリンクが 0 バイトになった。
* ディレクトリへのシンボリックリンクやジャンクションは結果から除外された。
* NTFS 圧縮が考慮され、実サイズよりも小さな値が出た。

### 課題

* 1 byte のファイルなど、ディスク上のサイズが 0 bytes のファイルを正しく扱えていない。
    * Windows Internals 6th ed. Part 2 の Resident and Nonresident Attributes に小さいファイルのデータが MFT の resident attribute になることがあると書いてある。1 byte のファイルならディスク使用量は 0 bytes が正しいだろう。
* 圧縮は考慮されているが、エクスプローラーや WizTree が報告する割り当てサイズの 8KB ではなく、12KB になってしまっている。MFT を見ている WizTree の結果が誤っているとは思えず、du の問題のような気がする。

思うようにならぬものです。

### WSL の du でもやってみる

先ほどのコマンドは、Linux でいう `du -sb * | sort -nr | head` みたいな感じです。――ということは、WSL の du を実行したらどうなるのでしょう？

```bash
$ du -sh * | sort -hr | head
3.2M    dir
8.0K    compressed
0       file_symlink
0       file_hardlink
0       dir_symlink
0       dir_junction
0       1byte
```

か、完璧です！ Windows 用の du を使う PowerShell より圧倒的に読みやすく覚えやすい。

しかし、この記事の公開後、うまく動かない例を発見してしまいました。私の 256 GB の SSD にある AppData ディレクトリが、7.3E だというのです。どう考えても 256 GB の SSD に 7.3 エクサバイトの領域なんてないのに！