tech.guitarrapc.cóm

Technical updates

PowerShell で テキストファイルとファイル名をマッチングして変更してみよう

ファイル名がただの連番になってしまった時に、まとめて名称を変えたいと思う機会がありました。

PowerShell で気軽にさくっと書けますよアピールということで。

目次

どんな状態なのか

元のファイル名

このような適当な名前とします。

Get-ChildItem -Path D:\hoge
    Directory: D:\hoge


Mode         LastWriteTime   Length Name          
----         -------------   ------ ----          
-a--- 2013/09/18      4:50  6087020 01-test 01.txt
-a--- 2013/09/18      4:50 18947756 02-test 02.txt
-a--- 2013/09/18      4:51 14925836 03-test 03.txt
-a--- 2013/09/18      4:51 20730572 04-test 04.txt
-a--- 2013/09/18      4:51 15803132 05-test 05.txt
-a--- 2013/09/18      4:52 23336588 06-test 06.txt
-a--- 2013/09/18      4:52 18079868 07-test 07.txt
-a--- 2013/09/18      4:52 17882300 08-test 08.txt
-a--- 2013/09/18      4:53 21953612 09-test 09.txt
-a--- 2013/09/18      4:53 24620780 10-test 10.txt
-a--- 2013/09/18      4:53 18493820 11-test 11.txt
-a--- 2013/09/18      4:54 21231548 12-test 12.txt
-a--- 2013/09/18      4:54 25775612 13-test 13.txt
-a--- 2013/09/18      4:54 16760396 14-test 14.txt
-a--- 2013/09/18      4:54 23233100 15-test 15.txt
-a--- 2013/09/18      4:55 17924636 16-test 16.txt
-a--- 2013/09/18      4:55 20438924 17-test 17.txt
-a--- 2013/09/18      4:55 20890508 18-test 18.txt
-a--- 2013/09/18      4:55 21925388 19-test 19.txt
-a--- 2013/09/18      4:56 20972828 20-test 20.txt
-a--- 2013/09/18      4:56 22779164 21-test 21.txt
-a--- 2013/09/18      4:56 27219740 22-test 22.txt
-a--- 2013/09/18      4:57 22856780 23-test 23.txt
-a--- 2013/09/18      4:57 22247612 24-test 24.txt
-a--- 2013/09/18      4:57 23854028 25-test 25.txt
-a--- 2013/09/18      4:57 18881900 26-test 26.txt
-a--- 2013/09/18      4:57 16640444 27-test 27.txt

変更後の名前

一覧を適当にファイルに持っていたとしましょう。 ファイルには空白行があり、そこは省きたいモノとします。

Get-Content D:\newnames.txt
01-swordland to be continued

02-friendly feelings

03-sorrowfully

04-critical phase

05-victory

06-a new world of fairies

07-fly higher and faster

08-in time of peace

09-fly, if you can
10-fight with a devil

11-she is still sleeping

12-past sadness

13-is this love

14-town in the morning

15-climbing up the world tree

16-I wonder

17-Yui

18-is this love piano only ver.

19-Oberon

20-in the cage

21-got to win
22-aerial fight
23-false king
24-last flight
25-reconciliation
26-dance with me
27-aincrad

マッチング

  • 元のファイル名の番号と、newnames.txt の番号で一致させてファイル名を変更します
  • 拡張子は残します
  • ファイル名からスペースは外しましょう

変更スクリプト

PowerShell で さっくり書けて楽です。

$names = Select-String -Path D:\newnames.txt -Pattern "\W" | select -ExpandProperty Line
$targetpath = 'D:\hoge'

Get-ChildItem -Path $targetpath `
    | %{ 
        # get original extention
        $extention = $_.Extension

        # get matching names
        [string]$matchingname = $names -like "*$($_.Name.Substring(0,2))*"

        # configure new name
        $newname = [System.IO.Path]::ChangeExtension($matchingname.Replace(" ",""),$extention)

        # rename items
        if ($matchingname)
        {
            Rename-Item -Path $_.FullName -NewName $newname
        }
    }

-Whatif で Rename-Item を実行してみると

What if: Performing the operation "Rename File" on target "Item: D:\hoge\01-test 01.txt Destination: D:\hoge\01-swordlandtobecontinued.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\02-test 02.txt Destination: D:\hoge\02-friendlyfeelings.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\03-test 03.txt Destination: D:\hoge\03-sorrowfully.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\04-test 04.txt Destination: D:\hoge\04-criticalphase.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\05-test 05.txt Destination: D:\hoge\05-victory.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\06-test 06.txt Destination: D:\hoge\06-anewworldoffairies.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\07-test 07.txt Destination: D:\hoge\07-flyhigherandfaster.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\08-test 08.txt Destination: D:\hoge\08-intimeofpeace.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\09-test 09.txt Destination: D:\hoge\09-fly,ifyoucan.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\10-test 10.txt Destination: D:\hoge\10-fightwithadevil.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\11-test 11.txt Destination: D:\hoge\11-sheisstillsleeping.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\12-test 12.txt Destination: D:\hoge\12-pastsadness.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\13-test 13.txt Destination: D:\hoge\13-isthislove.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\14-test 14.txt Destination: D:\hoge\14-towninthemorning.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\15-test 15.txt Destination: D:\hoge\15-climbinguptheworldtree.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\16-test 16.txt Destination: D:\hoge\16-Iwonder.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\17-test 17.txt Destination: D:\hoge\17-Yui.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\18-test 18.txt Destination: D:\hoge\18-isthislovepianoonlyver.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\19-test 19.txt Destination: D:\hoge\19-Oberon.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\20-test 20.txt Destination: D:\hoge\20-inthecage.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\21-test 21.txt Destination: D:\hoge\21-gottowin.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\22-test 22.txt Destination: D:\hoge\22-aerialfight.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\23-test 23.txt Destination: D:\hoge\23-falseking.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\24-test 24.txt Destination: D:\hoge\24-lastflight.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\25-test 25.txt Destination: D:\hoge\25-reconciliation.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\26-test 26.txt Destination: D:\hoge\26-dancewithme.txt".
What if: Performing the operation "Rename File" on target "Item: D:\hoge\27-test 27.txt Destination: D:\hoge\27-aincrad.txt".

では、実行してみて、結果を見てみましょう。

Get-ChildItem -Path D:\hoge
    Directory: D:\hoge


Mode         LastWriteTime   Length Name                         
----         -------------   ------ ----                         
-a--- 2013/09/18      4:50  6087020 01-swordlandtobecontinued.txt
-a--- 2013/09/18      4:50 18947756 02-friendlyfeelings.txt      
-a--- 2013/09/18      4:51 14925836 03-sorrowfully.txt           
-a--- 2013/09/18      4:51 20730572 04-criticalphase.txt         
-a--- 2013/09/18      4:51 15803132 05-victory.txt               
-a--- 2013/09/18      4:52 23336588 06-anewworldoffairies.txt    
-a--- 2013/09/18      4:52 18079868 07-flyhigherandfaster.txt    
-a--- 2013/09/18      4:52 17882300 08-intimeofpeace.txt         
-a--- 2013/09/18      4:53 21953612 09-fly,ifyoucan.txt          
-a--- 2013/09/18      4:53 24620780 10-fightwithadevil.txt       
-a--- 2013/09/18      4:53 18493820 11-sheisstillsleeping.txt    
-a--- 2013/09/18      4:54 21231548 12-pastsadness.txt           
-a--- 2013/09/18      4:54 25775612 13-isthislove.txt            
-a--- 2013/09/18      4:54 16760396 14-towninthemorning.txt      
-a--- 2013/09/18      4:54 23233100 15-climbinguptheworldtree.txt
-a--- 2013/09/18      4:55 17924636 16-Iwonder.txt               
-a--- 2013/09/18      4:55 20438924 17-Yui.txt                   
-a--- 2013/09/18      4:55 20890508 18-isthislovepianoonlyver.txt
-a--- 2013/09/18      4:55 21925388 19-Oberon.txt                
-a--- 2013/09/18      4:56 20972828 20-inthecage.txt             
-a--- 2013/09/18      4:56 22779164 21-gottowin.txt              
-a--- 2013/09/18      4:56 27219740 22-aerialfight.txt           
-a--- 2013/09/18      4:57 22856780 23-falseking.txt             
-a--- 2013/09/18      4:57 22247612 24-lastflight.txt            
-a--- 2013/09/18      4:57 23854028 25-reconciliation.txt        
-a--- 2013/09/18      4:57 18881900 26-dancewithme.txt           
-a--- 2013/09/18      4:57 16640444 27-aincrad.txt 

説明

不要な方も多いと思いますが、せっかくなので簡単に説明します。

新しいファイル名の一覧をテキストから取得

$names = Select-String -Path D:\newnames.txt -Pattern "\W" | select -ExpandProperty Line

ファイルを読むだけなら、 Get-Content Cmdlet (要は cat) もありますが、今回は空白行を省くようにする必要があります。 このようなときは、正規表現で検査しつつ読み取れる Select-String Cmdlet が便利です。 ご存知の\W を使えばいいですね。

パイプ|の先で select -ExpandProperty Line としたのは、Lineプロパティのみを取得するためです。

同様の書き方として、プロパティを指定してもいいでしょう。

$names = (Select-String -Path D:\newnames.txt -Pattern "\W").Line

対象となるファイル一覧を取得

ご存知の lsdir に相当する Get-ChildItem Cmdletを利用して、対象パス D:\hoge からファイル一覧を取得しています。

$targetpath = 'D:\hoge'
Get-ChildItem -Path $targetpath

ここで Where-Object" にパイプラインで渡せば、対象となるファイルを拡張子など任意のプロパティでフィルタ可能です。

たとえば、拡張子が .txtのファイルだけを対象にするならこうです。

$targetpath = 'D:\hoge'
Get-ChildItem -Path $targetpath | where Extension -eq ".txt"

取得した各ファイルごとに処理を行う

    | %{ 

        # get original extention
        $extention = [System.IO.Path]::GetExtension($_.FullName)

        # get matching names
        [string]$matchingname = $names -like "*$($_.Name.Substring(0,2))*"

        # configure new name
        $newname = [System.IO.Path]::ChangeExtension($matchingname.Replace(" ",""),$extention)

        # rename items
        if ($matchingname)
        {
            Rename-Item -Path $_.FullName -NewName $newname
        }
    }

ここでは、 Get-ChildItemの結果をパイプ|で受けて、 Foreach-Object (Alias は %)で処理しています。Foreach-Objectと書くのは面倒です (

# 略さないとこう
Foreach-Object {
}

# Aliasで略す
foreach {
}

# もう一つのAliasで
%{
}

なお、 Foreach-Object 内部では、パイプラインを通して渡されたファイルオブジェクトが自動変数 $_ に格納されます。

# Get-ChildItemで取得したオブジェクトが $_ に格納されている。
Get-ChildItem -Path -D:\hoge | %{
    $_
}

さらに PowerShell V4.0 からは、 -PipelineObject で、オブジェクトを操作してからパイプラインに渡せます。


続いて、現在の Extensionを取得します。 自動変数から Extension プロパティを取得します。

        $extention = $_.Extension

.NET でもいいです。

        # get original extention
        $extention = [System.IO.Path]::GetExtension($_.FullName)

Get-ItemGet-ItemProperty Cmdletでもいいでしょう。

        # get original extention
        $extention = (Get-ItemProperty -Path $_.FullName).Extension

次に、対象のファイル名と、テキストから取得したファイル名でマッチングします。 今回は、頭2文字の数字で。

        # get matching names
        [string]$matchingname = $names -like "*$($_.Name.Substring(0,2))*"

新しい名前を定めます。 取得したもともとの拡張子を忘れないように。

        # configure new name
        $newname = [System.IO.Path]::ChangeExtension($matchingname.Replace(" ",""),$extention)

マッチングが該当した場合だけ実行します。

        # rename items
        if ($matchingname)
        {
            Rename-Item -Path $_.FullName -NewName $newname
        }

以上です。 Aliasを使って、パラメータ省略もして、パイプラインで繋げば どんどん短くなりますが、そこは周りが読めるかどうか、書きやすさ などを考慮しましょう。

終わり

ファイル名変更ソフトもいいのでしょうが、PowerShell も自由に操れるのでお勧めです。 身近なところから、PowerShell を試してみませんか?

追記

敬愛する 帝国兵さま と はぐれメタルさま からしっかりやれって言われたので修正します。

こんな適当記事が見つかるとは。。。。。。。。。。。。ぐぬぬにゃお

ファイル名のバリデーション と ちょこちょこっと直しました。

# Get invalid charactors for filename
$invalidfilename = [System.IO.Path]::GetInvalidFileNameChars()

# Get names from namelist
$namelist = "D:\newnames.txt"
$names = Select-String -Path $namelist -Pattern "\W" | select -ExpandProperty Line

# get filenames from targetpath
$targetpath = 'D:\hoge'
Get-ChildItem -Path $targetpath `
    | %{ 
        # get original extention
        $extention = $_.Extension

        # get matching names
        [string]$matchingname = $names -like "*$($_.Name.Substring(0,2))*"

        if ($matchingname)
        {
            # get index of validation (valid = -1)
            $validationIndex = $matchingname.IndexOfAny($invalidfilename)

            # execute rename
            if ($validationIndex -eq "-1")
            {
                # configure new name
                $newname = [System.IO.Path]::ChangeExtension($matchingname.Replace(" ",""),$extention)

                # rename item
                Write-Warning -Message ("running rename with {0} to {1}" -f $_.FullName, $newname)
                Rename-Item -Path $_.FullName -NewName $newname
            }
            else
            {
                Write-Error -Message ("{0} contains invalid charactor. index : {1}" -f $matchingname, $validationIndex)
            }
        }
    }

元の newnames.txt に ファイル名に使えない文字が含まれている場合、エラーを明示します。 newnames.txt がこうだとします。

01-swordland to be continuedfghfghfh>>>

02-friendly feelings

03-sorrowfully

04-critical phase

05-victory

06-a new world of fairies

07-fly higher and faster

08-in time of peace

09-fly, if you can
10-fight with a devil

11-she is still sleeping

12-past sadness

13-is this love

14-town in the morning

15-climbing up the world tree

16-I wonder

17-Yui

18-is this love piano only ver.

19-Oberon

20-in the cage

21-got to win
22-aerial fight
23-false king
24-last flight
25-reconciliation
26-dance with me
27-aincrad

結果、01-swordland to be continuedfghfghfh>>>>は使えないので、エラー検出します。

D:\GitHub\PowerShellUtil\Rename-FileNames\Rename-FileNames.ps1 : 01-swordland to be continuedfghfghfh>>> contains invalid charactor. index : 36
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Rename-FileNames.ps1
 
WARNING: running rename with D:\hoge\02-friendlyfeelings.txt to 02-friendlyfeelings.txt
WARNING: running rename with D:\hoge\03-sorrowfully.txt to 03-sorrowfully.txt
WARNING: running rename with D:\hoge\04-criticalphase.txt to 04-criticalphase.txt
WARNING: running rename with D:\hoge\05-victory.txt to 05-victory.txt
WARNING: running rename with D:\hoge\06-anewworldoffairies.txt to 06-anewworldoffairies.txt
WARNING: running rename with D:\hoge\07-flyhigherandfaster.txt to 07-flyhigherandfaster.txt
WARNING: running rename with D:\hoge\08-intimeofpeace.txt to 08-intimeofpeace.txt
WARNING: running rename with D:\hoge\09-fly,ifyoucan.txt to 09-fly,ifyoucan.txt
WARNING: running rename with D:\hoge\10-fightwithadevil.txt to 10-fightwithadevil.txt
WARNING: running rename with D:\hoge\11-sheisstillsleeping.txt to 11-sheisstillsleeping.txt
WARNING: running rename with D:\hoge\12-pastsadness.txt to 12-pastsadness.txt
WARNING: running rename with D:\hoge\13-isthislove.txt to 13-isthislove.txt
WARNING: running rename with D:\hoge\14-towninthemorning.txt to 14-towninthemorning.txt
WARNING: running rename with D:\hoge\15-climbinguptheworldtree.txt to 15-climbinguptheworldtree.txt
WARNING: running rename with D:\hoge\16-Iwonder.txt to 16-Iwonder.txt
WARNING: running rename with D:\hoge\17-Yui.txt to 17-Yui.txt
WARNING: running rename with D:\hoge\18-isthislovepianoonlyver.txt to 18-isthislovepianoonlyver.txt
WARNING: running rename with D:\hoge\19-Oberon.txt to 19-Oberon.txt
WARNING: running rename with D:\hoge\20-inthecage.txt to 20-inthecage.txt
WARNING: running rename with D:\hoge\21-gottowin.txt to 21-gottowin.txt
WARNING: running rename with D:\hoge\22-aerialfight.txt to 22-aerialfight.txt
WARNING: running rename with D:\hoge\23-falseking.txt to 23-falseking.txt
WARNING: running rename with D:\hoge\24-lastflight.txt to 24-lastflight.txt
WARNING: running rename with D:\hoge\25-reconciliation.txt to 25-reconciliation.txt
WARNING: running rename with D:\hoge\26-dancewithme.txt to 26-dancewithme.txt
WARNING: running rename with D:\hoge\27-aincrad.txt to 27-aincrad.txt

なんだか、すいませんでした。