スキャナで連続取り込みした画像や PDF ファイルでフォルダが溢れ返ってしまうので、ファイル名の接頭辞ごとにフォルダ分けします。
作業環境
- Windows 10 20H2 (OS Build 19042.1237)
- Windows PowerShell 5.1.19041.1237
- PowerShell 7.1.4
完成形
# 正規表現 # 先頭から、ハイフン以外の文字の連続部分を接頭辞として使用 $pattern = '^([^-]+)-.+\.pdf$' # 接頭辞でフォルダ分けする Get-ChildItem -File | Where-Object {$_.Name -match $pattern} | ForEach-Object {if ((Test-Path $Matches[1]) -eq $false) {New-Item -Name $Matches[1] -ItemType Directory}; $_ | Move-Item -Destination $Matches[1]}
正規表現はファイル名やフォルダ名に合わせて適宜変えてください。フォルダ名にしたい部分を ()
で囲みます。エスケープ文字はバックスラッシュ (\
) です。円記号 (¥
) ではだめです。
^([^-]+)-.+\.pdf$
で、「ハイフン以外の文字が一回以上連続した後、ハイフンが一回出現し、その後任意の文字が任意の回数出現し、'.pdf' で終わる」というファイル名を表します。拡張子が '.jpg' なら ^([^-]+)-.+\.jpg$
、指定しない場合は ^([^-]+)-.+$
あるいは ^([^-]+)
とします。
2021/10/03 追記
もっとシンプルに「ハイフンより左」と考えれば、^(.+)-
でいいですね。あるいは「ハイフン以外の文字の連続部分」なら ^[^-]+
でいいです。後者はグループ化をしていないので $Matches
だけでいいです。
2021/10/03 追記ここまで
説明
以下の 2 ステップです。
- 正規表現で接頭辞をグループ化してキャプチャ
- 接頭辞ごとにフォルダに移動
正規表現で接頭辞をグループ化してキャプチャ
正規表現のパターンの一部を ()
でくくると、その部分をグループ(ひとつのまとまり)として扱い、マッチした文字列からその部分だけをキャプチャする(抜き出す)ことができます。^([^-]+)-.+\.pdf$
とすると [^-]+
にマッチした部分を取得できます。この場合、「ファイル名の先頭から、ハイフン以外の文字が一回以上連続する部分」を取得します。
-match
でマッチした内容は $Matches
自動変数に入ります。グループ化した部分は $Matches[1]
のようにすると取得できます。
正規表現やグループについての詳しい説明はこちらの方々にお任せします。また、後の「正規表現のグループ化のサンプル」でサンプルをご覧ください。
接頭辞ごとにフォルダに移動
$Matches[1]
に入っている接頭辞を移動先のフォルダ名にします。
正規表現のグループ化のサンプル
$Matches 自動変数
グループ化してキャプチャした内容がどのように $Matches
自動変数に入るか見てみましょう。
次のファイルがあるディレクトリで以下のコードを試してください。
apple-001.pdf apple-002.pdf apple-003.pdf caramel-001.pdf caramel-002.pdf caramel-003.pdf caramel-004.pdf potato-001.pdf potato-002.pdf potato-003.pdf pumpkin-001.pdf pumpkin-002.pdf pumpkin-003.pdf pumpkin-004.pdf pumpkin-005.pdf
# 正規表現 # 先頭から、ハイフン以外の文字の連続部分を接頭辞として使用 $pattern = '^([^-]+)-.+\.pdf$' # 接頭辞をグループ化してキャプチャ Get-ChildItem .\apple-001.pdf | Where-Object {$_.Name -match $pattern} | ForEach-Object {$Matches}
このような結果になるはずです。
Name Value ---- ----- 1 apple 0 apple-001.pdf
$Matches
自動変数のインデックス 0
にはマッチした文字列全体が、1
には ()
でグループ化した部分のみが入っています。
お次はこちら。
# 正規表現 # 先頭から、ハイフン以外の文字の連続部分を接頭辞として使用 $pattern = '^([^-]+)-.+\.pdf$' # 接頭辞をグループ化してキャプチャ Get-ChildItem -File | Where-Object {$_.Name -match $pattern} | ForEach-Object {Write-Output "ファイル名: $($Matches[0])`t接頭辞: $($Matches[1])"}
このような結果になるはずです。
ファイル名: apple-001.pdf 接頭辞: apple ファイル名: apple-002.pdf 接頭辞: apple ファイル名: apple-003.pdf 接頭辞: apple ファイル名: caramel-001.pdf 接頭辞: caramel ファイル名: caramel-002.pdf 接頭辞: caramel ファイル名: caramel-003.pdf 接頭辞: caramel ファイル名: caramel-004.pdf 接頭辞: caramel ファイル名: potato-001.pdf 接頭辞: potato ファイル名: potato-002.pdf 接頭辞: potato ファイル名: potato-003.pdf 接頭辞: potato ファイル名: pumpkin-001.pdf 接頭辞: pumpkin ファイル名: pumpkin-002.pdf 接頭辞: pumpkin ファイル名: pumpkin-003.pdf 接頭辞: pumpkin ファイル名: pumpkin-004.pdf 接頭辞: pumpkin ファイル名: pumpkin-005.pdf 接頭辞: pumpkin
さらにこちら。
# 正規表現 # 先頭から、ハイフン以外の文字の連続部分を接頭辞として使用 # ハイフン以降、拡張子以前の部分を連番部分として使用 $pattern = '^([^-]+)-(.+)\.pdf$' # 接頭辞と連番部分をグループ化してキャプチャ Get-ChildItem -File | Where-Object {$_.Name -match $pattern} | ForEach-Object {Write-Output "ファイル名: $($Matches[0])`t接頭辞: $($Matches[1])`t連番部分: $($Matches[2])"}
このようになるはずです。
ファイル名: apple-001.pdf 接頭辞: apple 連番部分: 001 ファイル名: apple-002.pdf 接頭辞: apple 連番部分: 002 ファイル名: apple-003.pdf 接頭辞: apple 連番部分: 003 ファイル名: caramel-001.pdf 接頭辞: caramel 連番部分: 001 ファイル名: caramel-002.pdf 接頭辞: caramel 連番部分: 002 ファイル名: caramel-003.pdf 接頭辞: caramel 連番部分: 003 ファイル名: caramel-004.pdf 接頭辞: caramel 連番部分: 004 ファイル名: potato-001.pdf 接頭辞: potato 連番部分: 001 ファイル名: potato-002.pdf 接頭辞: potato 連番部分: 002 ファイル名: potato-003.pdf 接頭辞: potato 連番部分: 003 ファイル名: pumpkin-001.pdf 接頭辞: pumpkin 連番部分: 001 ファイル名: pumpkin-002.pdf 接頭辞: pumpkin 連番部分: 002 ファイル名: pumpkin-003.pdf 接頭辞: pumpkin 連番部分: 003 ファイル名: pumpkin-004.pdf 接頭辞: pumpkin 連番部分: 004 ファイル名: pumpkin-005.pdf 接頭辞: pumpkin 連番部分: 005
n 番目のグループ化部分は、 $Matches[n]
に入ります。
それから、'sweet-potato-001.pdf' のように、接頭辞にもハイフンが入っているファイル名があったら ^(.+)-\d+\.pdf$
などとします。ファイル名に合わせてがんばってください。
フォルダ作成
正規表現の結果をもとにフォルダを作りましょう。
次のファイルがあるディレクトリで、以下の正規表現を使って「完成形」のコードを試してください。
SCAN_20210929-090030111.pdf SCAN_20210930-120035111.pdf SCAN_20210930-120035222.pdf SCAN_20211001-094406111.pdf SCAN_20211001-094406222.pdf SCAN_20211001-094406333.pdf SCAN_20211002-123013111.pdf SCAN_20211002-123013222.pdf SCAN_20211002-182154111.pdf SCAN_20211002-182154222.pdf SCAN_20211002-182155111.pdf SCAN_20211003-083123111.pdf
'SCAN_20210929' というフォルダ名にする場合はこうです。
# 'SCAN_20210929' $pattern = '^(SCAN_\d{8})-\d{9}\.pdf$' # 「完成形」のコードを実行する
'20210929-0900' というフォルダ名にする場合はこうです。
# '20210929-0900' $pattern = '^SCAN_(\d{8}-\d{4})\d{5}\.pdf$' # 「完成形」のコードを実行する
'2021年09月29日' というフォルダ名にする場合は、以下のようにしてください。
# '2021年09月29日' $pattern = '^SCAN_(\d{4})(\d{2})(\d{2})-\d{9}\.pdf$' Get-ChildItem -File | Where-Object {$_.Name -match $pattern} | ForEach-Object {$destinationPath = "$($Matches[1])年$($Matches[2])月$($Matches[3])日"; if ((Test-Path $destinationPath) -eq $false) {New-Item -Name $destinationPath -ItemType Directory}; $_ | Move-Item -Destination $destinationPath}
'2021\0929' という階層にする場合は、以下のようにしてください。
# `yyyy\MMdd` $pattern = '^SCAN_(\d{4})(\d{4})-\d{9}\.pdf$' Get-ChildItem -File | Where-Object {$_.Name -match $pattern} | ForEach-Object {$destinationPath = "$($Matches[1])\$($Matches[2])"; if ((Test-Path $destinationPath) -eq $false) {New-Item -Name $destinationPath -ItemType Directory}; $_ | Move-Item -Destination $destinationPath}
'2021\0929' という階層にしつつ、その日付のファイルがひとつしかない場合はフォルダの作成と移動をしない場合は、以下のようにしてください。そんなことあるか分かりませんが。
# `yyyy\MMdd` $pattern = '^SCAN_(\d{4})(\d{4})-\d{9}\.pdf$' $patternSub = '(\d{4})\\(\d{4})' Get-ChildItem -File | Where-Object {$_.Name -match $pattern} | Group-Object -Property {"$($Matches[1])\$($Matches[2])"} | ? {$_.Count -gt 1} | ForEach-Object {$regexResult = [regex]::Match($_.Name, $patternSub); $destinationPath = "$($regexResult.Groups[1])\$($regexResult.Groups[2])"; if ((Test-Path $destinationPath) -eq $false) {New-Item -Name $destinationPath -ItemType Directory}; $_.Group | Move-Item -Destination $destinationPath}
まとめ
もしかしたらね、スキャナにね、フォルダ分けしながらスキャンする機能があるかもしれないけどね、PowerShell 使いたいからね。
今回の正規表現はもっとすっきり書けます。私は意図しないものがマッチするのを防ぐため、必要な部分以外も書いています。好みや状況に応じて改良や簡略化をしてください。