ねじまきまきまき -Random Note-

やったこと忘れないための雑記

PowerShellでduコマンドっぽいものを作ってみる

やりたいこと

Linux/Unix系でよくあるduコマンドに近いものがWindowsで欲しかった。
クローズ環境で使うので、フリーソフトウェアとかでも気軽に入れられないので、代替として使えそうなものを組んでみる。
たぶん同じようなことをやっている人はいると思う。

duコマンドの出力イメージ

duコマンドは指定されたディレクトリ配下のサイズをはじき出しつつ最後に引数ディレクトリのサイズを出力してくれる。
ということで、イメージとしてはこういうやつを作るっていうことになる。

$ du -h etc
8.0K    etc/kernel/postinst.d
12K     etc/kernel
4.0K    etc/subversion
36K     etc/logrotate.d
4.0K    etc/smartmontools/smartd_warning.d
24K     etc/smartmontools
4.0K    etc/security/namespace.d
4.0K    etc/security/console.perms.d
8.0K    etc/security/limits.d
12K     etc/security/console.apps
~~~中略~~~
52K     etc/dconf/db
68K     etc/dconf
22M     etc

実装のため

PowerShellを使う

PowerShellスクリプト実行を行う時には実行ポリシーの変更が必要だったりするが、それはめんどくさいので、powershell -command -の仕組みを利用して、直接スクリプトを流し込んで実装するようにした。
これを利用すれば設定変更の必要もなくなるので、ぐっと使いやすくなる気がする。

duコマンドとの違い

duとの違いとして、duは標準出力には引数に渡した対象ディレクトリとサブディレクトリのみ出力される。
今回作るものは対象フォルダと直下のサブフォルダ・ファイルはすべて列挙するようにする。

対象の渡し方

ソースにべた書きとか、引数で対象を渡すというオーソドックスな方法もあったのだが、確認したいフォルダは複数あったのと、対象に変動があった時にわざわざソースを修正することや実行用のbatを編集するのは馬鹿らしいと思ったので、入力用のテキストファイルを用いることとした。

結果の出し方

アウトプットは標準出力に出すだけだと後から(どこがどれだけ増えたとか)比較することもできないので、テキストファイルに吐き出すようにした。
その際にファイル名は日付時刻を使用してなるべくユニークになるようにした。

作ったもの

以下に作ったモノたちのコードを並べる。
これらを使用することで、やりたいことができていることは確認できている。

起動用のbatファイル

PowerShellスクリプトファイルを実行するための起動用batファイル
やることはPowerShellスクリプトファイルの呼び出しだけなので、シンプル。
ファイル名は何でもよい。

@(type dirlist.ps1) | powershell -command -

PowerShellスクリプト

実際に仕事をするPowerShellスクリプトファイル。
ファイル名は何でもよいが、batでは「dirlist.ps1」としているので、デフォルトはこれ。
ファイル名を変える場合はbat内の呼び出しファイル名も変えること。

$ErrorActionPreference = "SilentlyContinue"
$RDate = Get-Date -Format "yyyyMMddHHmmss"
$File_Name = $RDate + ".txt"
$DummyPath ="dummypath/"

### list.txtから対象フォルダと直下のフォルダ・ファイルのサイズを取得してファイル出力
### 直下のフォルダ・ファイル処理が終わったら指定されたフォルダサイズも出力(ソートのためにダミー文字を付ける)

### 出力ファイルにヘッダー行を出力
[String]::Format("{0}`t{1}", "Path", "Size") | Out-File $File_Name -Encoding default

### list.txtの行数分繰り返す
foreach ($list in Get-Content "list.txt" -Encoding Default) {
    Write-Output $list
    $j = 0
    $k = 0

    ### サブフォルダの情報を拾う
    foreach ($i in (Get-ChildItem "$list" | ForEach-Object {$_.FullName})) {

        ### パスの最後が\の場合は削除
        ### 削除後にソートするためのダミー文字列を文字列最後にセット
        $ppath = (($i -replace "\\+$", "") -replace "$", "$DummyPath")
        $k = (Get-ChildItem -LiteralPath "$i" -Recurse -Force | Measure-Object Length -Sum).Sum

        ### フォルダ・ファイルサイズが0の場合は出力時に文字列変換する
        if($null -eq $k) {
            ### フォルダ・ファイルサイズが0の場合
            $k = 0
            [string]::Format("{0}`t{1:#,#}", $ppath, [string]$k) | Out-File $File_Name -Encoding default -Append
        }
        else {
            ### フォルダサイズが0じゃない場合
            [string]::Format("{0}`t{1:#,#}", $ppath, $k) | Out-File $File_Name -Encoding default -Append
        }

        ### 編集した出力用パスをクリア
        $ppath = $null

        ### 合計値計算
        $j = $j + $k
    }

    ### パスの最後が\だったら削除
    ### 削除後にソート用ダミー文字列を付与
    $plist = (($list -replace "\\+$", "") -replace "$", "$DummyPath")

    ### list.txtに記載されたフォルダ自体の書き出し
    [string]::Format("{0}`t{1:#,#}", $plist, $j) | Out-File $File_Name -Encoding default -Append
}

### CSV読み取り機能を使って編集
### Path順でソートして出力
$CSVData = Import-Csv -Path $File_Name -Encoding Default -Delimiter "`t"
$CSVData | Sort-Object -Property Path -unique | Export-Csv -Path $File_Name -Encoding Default -NoTypeInformation -Delimiter "`t"


### ファイルを読み込んで、ダミー文字列を削除して出力
$OutData = Get-Content -Encoding Default $File_Name | ForEach-Object {$_ -replace "$DummyPath", ""}
$OutData | Out-File $File_Name -Encoding default

$ErrorActionPreference = "Continue"

使ってみる

テスト用のフォルダ構成

適当な構成としている以下のものでテストを行っている。
マルチバイト文字を含むパス有無とファイルが無いフォルダという環境。

C:.
├─dummy
│      test
│
└─ダミー
    ├─1
    │      test.txt
    │      テスト.txt
    │
    ├─2
    └─3
            test

入力ファイル

ソースを見てもらえればわかる通り、「list.txt」が決め打ちで読み込むファイルとなっている。
そのため、上記のテスト用環境に合わせてこんな感じの入力ファイルを用意した。

C:\tmp\test\dummy
C:\tmp\test\ダミー

batファイルを動かして動作を確認

batファイルのコマンドプロンプトウインドウに実行中の入力ファイル行が表示される。
これは大量データの処理時に、どこを実行しているのかがわからなくなったためにあえて出力するようにしているもので、なくてもいい。

出力結果サンプル

出力結果のテキストファイルは起動用のbatとPowershellスクリプトファイルと同じフォルダに「日付時刻.txt」といったファイル名で出力されるので、それを確認する。
今回はこんな感じのものが出来上がっている。
C:\tmp\test\dummyにはファイルが1ファイルしか存在していないので、それのファイルサイズと合計が出力されている。。
C:\tmp\test\ダミーにはサブフォルダが3つあるので、それぞれのファイルサイズと合計が出力されている。

"Path"	"Size"
"C:\tmp\test\dummy\test"	"3,000,000"
"C:\tmp\test\dummy"	"3,000,000"
"C:\tmp\test\ダミー\1"	"30,000"
"C:\tmp\test\ダミー\2"	"0"
"C:\tmp\test\ダミー\3"	"10,000"
"C:\tmp\test\ダミー"	"40,000"

この辺もっとやりようがある気がするってところ

PowerShellのデフォルトエンコードUTF-8Shift_JISかがバージョンによって変わるので、もしかしたらバグる可能性はある。
duの出力順みたいな感じでソートさせているので、ダミー文字列を使った処理をやっているが、ダミー文字が記号だとソート時に最上段に行ってしまうのでアルファベット文字列としている。
エラーは無視しつつ、階層内のすべてのファイルサイズを合計しているので、実値と違いが出ることはあるはず。