エクスプローラなどで選択した複数のファイルの名前をテキストエディタで変更するPowerShellスクリプト

## [2020/04/10]に編集しました

### 変更点１
テキストファイルに保存する際のエンコードをUTF-8(BOM)に変更しました。
ASCII文字だけを使用したテキストファイルをエディターで開いて、日本語などを記述して上書き保存してしまうと、多くのエディターではShift-JISがデフォルトになってしまうので、これを防止するために意図的にBOM付きのUnicode形式にしました。

### 変更点２
テキストエディターで変更後に各行を読みだして比較する時に「-eq」演算子を使用していたのですが、これだと単独の濁点記号（ハ゜ラタ゛イス）と合成した濁点記号（パラダイス）を同じ文字列だと判定してしまうことが分かりました。
これに対応するために「String.Equals()」メソッドを使用するように変更しました。

## はじめに

最近、Webスクレイピングやその他の方法でネット上から大量のファイルを取得することが多くなったのですが、その際にファイル名をまとめて変更したいことがよくあります。
秀丸ファイラーClassicなどを使えば「固定名称」＋「連番」などの定型パターンは簡単に変換できるのですが、もっと不規則な変換や事前に用意してあるファイル名のリストにまるっと置き換えたい場合などはとても苦労します。

そこで、現在勉強中のPowerShellを使用してファイル名をテキストエディターで変更するスクリプトを書いてみました。最大のメリットはエディターが持っているテキスト編集機能（変換機能）をそのまま活用できることです。
さらに、私が開発した[ペースター](https://www.autumn-soft.com/)というソフトを使用すれば、単独の濁点記号を直前の文字と合成するなどの機能（ハ゜ラタ゛イス.png --> パラダイス.png）も使用できるようになります。単独の濁点記号を使用したファイル名は最近よく見かけます。

使い方としては、ファイラーで対象となるファイルを選択してから、コンテキストメニューを表示して「送る」－「エディターで名前を変更」を選択してエディターを起動します。
エディター上で好きなようにファイル名を変更してから上書き保存して終了します。

すると、「Rename.ps1」がファイル名を読み込んでリネームしてくれます。

## 開発環境
動作環境はWindows10です。
MacでもPowerShell6.xはインストールできますが動作確認はしていません。

PowerShellはVer6.xを使用します。
Windows10に標準添付のVer5.xを使用する場合には、下記で紹介する「Rename.ps1」のファイルのエンコードをUTF-8(BOM付き)で保存してください。
Ver6.xの場合にはUTF-8(BOMなし)で保存してください。

```PowerShell
PS C:\Users\nsnhr> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      6.2.3
PSEdition                      Core
GitCommitId                    6.2.3
OS                             Microsoft Windows 10.0.18362
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
```

テキストエディターは秀丸を使っていますが、別に何でも構いません。
VSCodeなどを使用すれば、マルチカーソル機能なども使用できます。
今回のスクリプトでは「*.txt」に関連付けされているエディターを起動します。

## コード

PowerShellのコードは以下の通りです。
ファイルのエンコードは上記で説明したものを使用してください。
なお、**最終的にリネームを行う`Rename-Item`には安全のために` -WhatIf`オプションを付けています。**これは実際にリネームを行うのではなく何が実行されるのかを確認するためのものです。
テストファイルなどでスクリプトを実行してみて問題がなければ、` -WhatIf`オプションを削除して実際にリネームを行ってください。

```PowerShell:Rename.ps1
#送られたファイルパスを受け取る
param(
    [parameter(ValueFromRemainingArguments = $true)]
    [String[]]$FileList # = @("D:\WorkShop\test.csv", "D:\WorkShop\test.txt")
)
function MyPause() {
    Write-Host "続行するには何かキーを押してください..." -NoNewLine
    [Console]::ReadKey() > $null
}

$srcList = New-Object System.Collections.ArrayList

# 選択されているファイルの一覧を「rename_file_list.txt」に書き込む（utf-8 BOM）
$filename = $PSScriptRoot + "\rename_file_list.txt"
$file = New-Object System.IO.StreamWriter($filename, $false, [System.Text.Encoding]::GetEncoding("utf-8"))

# ファイルのリストを日付の降順でソートする
$lstSort = Get-Item -LiteralPath $FileList | Sort-Object -Property LastWriteTime -Descending

foreach ($item in $lstSort) {
    $file.WriteLine($item.Name)
    $srcList.Add($item.FullName) > $null
}
$file.Close()

# 変更前の更新日を取得
$temp = Get-Item -LiteralPath $filename
$dtBefore = $temp.LastWriteTime

# エディターを起動して編集する
Start-Process -FilePath $filename -Wait

# 変更後の更新日を取得
$temp = Get-Item -LiteralPath $filename
$dtAfter = $temp.LastWriteTime

# もしファイルが編集されていなければ終了
if ($dtBefore -eq $dtAfter) {
    Write-Host "ファイル名のリストが変更されていないので終了します。"

    # ファイルを削除する場合はコメントアウトしてください
    # [System.IO.File]::Delete($filename)

    MyPause
    exit
}

# 「rename_file_list.txt」からファイルの一覧を読み込む（utf-8 BOM）
[String[]]$dstList = Get-Content -Path $filename -Encoding utf8BOM

# ファイルを削除する場合はコメントアウトしてください
# [System.IO.File]::Delete($filename)

# 空白行を削除する
for ($i = 0; $i -lt $dstList.Count; $i++){
    $dstList[$i] = $dstList[$i].Trim()
}
$dstList = $dstList -ne ""

if ($srcList.Count -ne $dstList.Count) {
    Write-Host "ファイルの個数が一致していません。"
    Write-Host $dstList
    MyPause
    exit
}

# ファイルをリネームする
$i = 0
$count = 0
foreach ($item in $srcList) {
    $temp = [System.IO.Path]::GetFileName($item)

    if (-not $temp.Equals($dstList[$i])) {
        Write-Host "（リネーム前）$temp"
        Write-Host "（リネーム後）$($dstList[$i])"
        Write-Host ""
        $count++
    }

    $i += 1
}

if ($count -eq 0) {
    Write-Host "リネーム対象のファイルはありませんでした。"
    MyPause
    exit
}

$input = Read-Host "リネームしてもよろしいですか？ [y]/n"
if ($input -eq "" -or $input -eq "y") {
    $i = 0
    foreach ($item in $srcList) {
        $temp = [System.IO.Path]::GetFileName($item)

        if (-not $temp.Equals($dstList[$i])) {
            Rename-Item -Path $item -NewName $dstList[$i] -WhatIf
        }
        $i += 1
    }
}
```

## 解説

ファイラーから渡ってきたファイル名のフルパス名のリストはスクリプトと同じフォルダに「rename_file_list.txt」という名前で作成します。
スクリプト内部ではフルパス名のリストを保存しますが、このテキストファイルにはファイル名のみを書き込みます。あとはそのファイルをデフォルトのテキストエディターでオープンして、終了するのを待ちます。

エディターが終了したら、PowerShellで「rename_file_list.txt」を読み込んで、リネームしていきます。

## 使い方

実際の実行イメージを画像を使って説明します。

1. ファイラーの「エディターで名前を変更」メニューにスクリプトを登録しておく必要があります。
[Win]+[R]キーを押して、「ファイル名を指定して実行」ダイアログを開いて「shell:sendto」と入力します。
すると、「送る」メニューに表示されるフォルダが表示されますので、ここに新しくショートカットを作成します。
ショートカットのリンク先は「pwsh.exe "C:\Users\nsnhr\Documents\Visual Studio Code\PowerShell\Rename.ps1"」
のようにします。ショートカットの名前は「エディターで名前を変更」などにします。
Ver5.xを使用する場合には「powershell.exe "・・・"」にします。

1. ファイラーでリネーム対象のファイルを選択して、「送る」－「エディターで名前を変更」を実行する。
![step_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262544/3d287bb8-0805-2947-bc2a-71637ddd6963.png)

1. すると、デフォルトのテキストエディターが起動するので、そこで自由にファイル名を編集します。
![step_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262544/49c2c78f-47e8-2421-ef67-b324f3692ea2.png)

1. エディターを終了すると、PowerShellで読み込んでリネームするファイルを表示します。
![step_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262544/bcdcaf7c-d226-4c76-7ead-66b3a1e55c73.png)

だいたいこのような流れになります。

## 終わりに

PowerShellのコーディングはまだ慣れていないのですが、VSCodeで行単位のデバッグができるのはとても便利ですね。

## 参考記事、参考書籍

[PowerShell 実践ガイドブック／吉崎生](https://www.amazon.co.jp/PowerShell%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89%E3%83%96%E3%83%83%E3%82%AF-%E3%82%AF%E3%83%AD%E3%82%B9%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E5%AF%BE%E5%BF%9C%E3%81%AE%E6%AC%A1%E4%B8%96%E4%BB%A3%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%92%E5%BE%B9%E5%BA%95%E8%A7%A3%E8%AA%AC-%E5%90%89%E5%B4%8E-%E7%94%9F/dp/4839965986/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=powershell%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89%E3%83%96%E3%83%83%E3%82%AF&qid=1586137103&s=books&sr=1-1)