ファイルシステムに対するGet-ChildItem
の結果を元にファイルのリネームなどを行う場合、同じファイルに対して複数回操作してしまうことがある、という話。
よくある話だと思いますが、ちょっとハマったので備忘録としてメモ。
環境
Windows 8.1 Pro
Windows PowerShell 4.0
Name Value
---- -----
PSVersion 4.0
WSManStackVersion 3.0
SerializationVersion 1.1.0.1
CLRVersion 4.0.30319.42000
BuildVersion 6.3.9600.18968
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion 2.2
問題となる例
サンプルコード
# 事前準備
# ユーザーのドキュメントフォルダに適当な名前のフォルダを作成
# カレントディレクトリへ変更
[IO.Path]::Combine( #
[System.Environment]::GetFolderPath('MyDocuments'),
[guid]::NewGuid()
) | New-Item -Path {$_} -ItemType Directory |
Push-Location -LiteralPath {$_.FullName}
# 1.log .. 10.log というファイルを作る
1..10 |
New-Item -Name {'{0}{1}' -f $_ , '.log'} -ItemType File > $null
# 問題の箇所
# .logのファイルに今日の日付(yyyyMMdd)をつけてリネーム
Get-ChildItem -Filter *.log |
Rename-Item -NewName {
'{0}-{1}' -f $_.Name ,
(Get-Date -Format yyyyMMdd)
} -PassThru
サンプルの実行結果
この時のコンソール出力(=Rename-Item
が変更したファイル)は以下のようになる。
ディレクトリ: C:\Users\username\Documents\7a9baef0-bb8b-44e0-a3d4-7cc871f875b2
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 18/08/16 20:01 1 1.log-20180816
-a--- 18/08/16 20:01 2 10.log-20180816
-a--- 18/08/16 20:01 1 1.log-20180816-20180816
-a--- 18/08/16 20:01 1 2.log-20180816
-a--- 18/08/16 20:01 1 3.log-20180816
-a--- 18/08/16 20:01 1 4.log-20180816
-a--- 18/08/16 20:01 1 5.log-20180816
-a--- 18/08/16 20:01 1 6.log-20180816
-a--- 18/08/16 20:01 1 7.log-20180816
-a--- 18/08/16 20:01 1 8.log-20180816
-a--- 18/08/16 20:01 1 9.log-20180816
出力を見ると1.log
に対して2回処理が行われているのが確認できる。
原因と対策
原因
Rename-Item
は逐次実行のため、Get-ChildItem
のパイプライン出力1個毎に処理をする。
つまり、ファイルが1個見つかるたびそのファイルの名前が変更される。
ファイルの名前が変わることで内部的な並び順が変わり、名前によってはGet-ChildItem
によって同じファイルが再度パイプラインに出力される、
そのため複数回同じ処理をしてしまう、
ということだと思われる。
対策
Get-ChildItem
の出力結果を固定すれば良いので、以下のような方法が考えられます(もっと良い方法があれば教えて下さい)。
-
Get-ChildItem
の結果を変数に入れる -
Get-ChildItem
を括弧でくくる -
Sort-Object
など全結果を一度キャッシュするコマンドを挟む