简体   繁体   中英

PowerShell/Batch script to rename each file to include original name + folder name

I have the following folder structure:

April reports
├─01-04-2018
│ ├─approved123.pdf
│ ├─approved123_V2.pdf
│ └─unapproved123.pdf
│
├─02-04-2018
│ ├─approved123.pdf
│ └─unapproved123.pdf
╎
╎
└─30-04-2018
  ├─approved123.pdf
  └─unapproved123.pdf

Each folder for each day in April contains an approved and an unapproved report with the same name ("approved123" or "unapproved123"). Some contain a V2.

I want to rename each one so that the "123" is removed and the folder name (the date) is included in the filename eg "approved_01-04-2018". I don't want to completely replace the filename with the name of the folder, I just want it added on at the end separated by an underscore if possible.

Once I've done that, I'd like to delete all the files that contain "unapproved" in the name and delete any "approved" files where there is an "approved_V2" in the same folder (version 2 supersedes the original).

For the first step I have tried writing a batch script (complete beginner in writing batch or powershell scripts) but it's not working or it's adding the folder name to each file multiple times:

@echo off
From the folder "April Reports"
rem Process each date
for /D %%d in (*) do (
    cd "%%d"
    rem rename all files
    for /d %%a in (*) do for %%b in ("%%~dpa\.") do ren "%%~a" "%%~nxb_%%~nxa"
)

The first solution works only on NTFS drives and expects that the batch file is in the directory containing April reports as subdirectory.

@echo off
for /D %%I in ("%~dp0April reports\??-??-????") do (
    for %%J in ("%%I\approved*.pdf") do (
        if exist "%%I\un%%~nxJ" del /F "%%I\un%%~nxJ"
        if exist "%%I\%%~nJ_V*.pdf" ( del "%%J" ) else ren "%%J" "%%~nJ_%%~nI.pdf"
    )
)

On NTFS drives the result is:

  • April reports
    • 01-04-2018
      • approved123_V2_01-04-2018.pdf
    • 02-04-2018
      • approved123_02-04-2018.pdf
    • 30-04-2018
      • approved123_30-04-2018.pdf

But this batch file does not work as expected on FAT32 or exFAT drives because of file April reports\01-04-2018\approved123_V2.pdf is processed three times resulting in having finally the name approved123_V2_01-04-2018_01-04-2018_01-04-2018.pdf because of list of file entries in file allocation table matching the pattern approved*.pdf changes while running the inner loop.

The solution for FAT32 and exFAT drives is using for inner loop a captured list of file names.

@echo off
for /D %%I in ("%~dp0April reports\??-??-????") do (
    for /F "delims=" %%J in ('dir "%%I\approved*.pdf" /A-D-H /B 2^>nul') do (
        if exist "%%I\un%%~nxJ" del /F "%%I\un%%~nxJ"
        if exist "%%I\%%~nJ_V*.pdf" ( del /F "%%I\%%J" ) else ren "%%I\%%J" "%%~nJ_%%~nI.pdf"
    )
)

Now the result is the same as with first batch file also on FAT32 and exFAT drives.

The command line dir "%%I\approved*.pdf" /ADH /B 2>nul is executed by FOR with using a separate command process started in background with cmd /C . DIR outputs just the file names without path which requires to reference the path with %%I where full qualified file name (file path + file name + file extension) is needed on the other command lines. The file names output by DIR to handle STDOUT of background command process are captured by FOR and then processed line by line. So the changes on file allocation table during running the commands DEL and REN do not matter anymore.

Read also the Microsoft article about Using Command Redirection Operators for an explanation of 2>nul . The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with using a separate command process started in background.

But let us assume the directory structure is as follows according to question Move files up one folder level :

  • April reports
    • 01-04-2018
      • dayreports
        • approved123.pdf
        • approved123_V2.pdf
        • unapproved123.pdf
    • 01-04-2018
      • dayreports
        • approved123.pdf
        • unapproved123.pdf
    • 30-04-2018
      • dayreports
        • approved123.pdf
        • unapproved123.pdf

By making a small modification to second batch file the same result can be achieved as posted above.

@echo off
for /D %%I in ("%~dp0April reports\??-??-????") do (
    for /F "delims=" %%J in ('dir "%%I\dayreports\approved*.pdf" /A-D-H /B 2^>nul') do (
        if exist "%%I\dayreports\un%%~nxJ" del /F "%%I\dayreports\un%%~nxJ"
        if exist "%%I\dayreports\%%~nJ_V*.pdf" ( del /F "%%I\dayreports\%%J" ) else move /Y "%%I\dayreports\%%J" "%%I\%%~nJ_%%~nI.pdf" >nul
    )
    rd "%%I\dayreports" 2>nul 
)

dayreports is additionally added to file paths and command MOVE is used instead of command REN to move the remaining file up one level in folder hierarchy with new file name. Command RD is used to delete the folder dayreports if being finally empty.

Here is one more variant which simply processes all approved*.pdf in entire directory tree of the directory containing the batch file working independent on file system and for both directory structures with either the PDF files in directory with date in directory name or in a subdirectory dayreports . It avoids processing approved*.pdf more than once by checking if a file to move/rename contains already the folder name (date) and so can be executed multiple times on entire directory tree.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "delims=" %%I in ('dir "%~dp0approved*.pdf" /A-D-H /B /S 2^>nul') do (
    if exist "%%~dpI\un%%~nxI" del /F "%%~dpI\un%%~nxI"
    if exist "%%~dpI\%%~nI_V*%%~xI" ( del /F "%%I" ) else call :MoveFile "%%I"
)
endlocal
rem Avoid a fall through to the subroutine.
goto :EOF

rem The subroutine MoveFile assigns path of passed file name ending with
rem a backslash to environment variable FilePath. Next the backslash is
rem removed from file path. Then is checked if the file path ends with
rem folder name "dayreports" in which case also this folder is removed
rem from file path. The file path is processed by command FOR to get
rem the string after last backslash referenced with %%~nxJ which is the
rem name of the folder of which name should be in new file name. Before
rem moving the file with new name containing the date, it is checked if
rem the current file name ends already with an underscore and the name
rem of the folder which should be the date. The subroutine is exited
rem if this condition is true to avoid double processing an already
rem processed file in a previous execution of this batch file.

:MoveFile
set "FilePath=%~dp1"
set "FilePath=%FilePath:~0,-1%"
if /I "%FilePath:~-11%" == "\dayreports" set "FilePath=%FilePath:~0,-11%"
for %%J in ("%FilePath%") do set "FolderName=%%~nxJ"
set "FileName=%~n1"
if "%FileName:~-11%" == "_%FolderName%" goto :EOF
move /Y %1 "%FilePath%\%~n1_%FolderName%%~x1" >nul
goto :EOF

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • call /? ... explains also %~dp0 which expands to drive and path of argument 0 which is the full path of the batch file ending always with a backslash.
  • del /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • move /?
  • rd /?
  • ren /?
  • set /?
  • setlocal /?

Here a PowerShell solution diving into any subdirectories.

  • it uses a Regular Expression to identify all relevant name parts
  • if it detects the un prefix or a file with the same name and _V2 suffix,
    it sets a delete flag ( $Del )
  • it then either deletes the current file or renames by the parts replacing the number (123) with the name of the parent directory.

Get-ChildItem *.pdf -Recurse | 
  Where-Object BaseName -match '(un)?(approved)(\d+)(_v2)?' |
    ForEach-Object {$Del = $False
      If ($matches[1] -eq 'un')       {$Del = $True}
      If (Test-Path (Join-Path $_.Directory.Fullname ($_.BaseName+"_V2.pdf"))){$Del = $True }
      If ($Del) {
        Remove-Item $_.FullName -whatif
      } else {
        Rename-Item $_.FullName -NewName ("{0}_{1}{2}{3}" -f `
           $matches[2],$_.Directory.Name,$Matches[4],$_.Extension) -WhatIf
      }
    }

The Remove-Item and Rename-Item cmdlets have the -WhatIf paramter appended to only show what would be executed - if the output looks OK remove the _WhatIf s

Sample tree after running the script (with a fake May folder):

> tree /f
Auflistung der Ordnerpfade für Volume RamDisk
Volumeseriennummer : 5566-7788
A:.
├───April reports
│   ├───01-04-2018
│   │       approved_01-04-2018_V2.pdf
│   │
│   ├───02-04-2018
│   │       approved_02-04-2018.pdf
│   │
│   └───30-04-2018
│           approved_30-04-2018.pdf
│
└───May reports
    ├───01-05-2018
    │       approved_01-05-2018_V2.pdf
    │
    ├───02-05-2018
    │       approved_02-05-2018.pdf
    │
    └───30-05-2018
            approved_30-05-2018.pdf

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM