windowsbatch-filedirectoryrenameexplorer

.Bat file to remove all text from filenames after a certain three letter code


In my work we frequently run into a situation where we have folders of hundreds of outputted PDFs. These all have the same naming scheme which is:

[Number/Text String]-XYZ [More Text]

They all have a 3 letter code after the initial document title - it's not always XYZ it can be any 3 capitalized letters.

In order to submit these the file names need to be reduced down, everything after the 3 letter code needs to be removed and everything before needs to be retained. The string before the code and the text after the code vary in both the content and the number of characters so the code doesn't have a consistent position within the name.

At the moment the solution is to manually go through and delete the text from the end of the file name, this takes ages and I thought there must be a better solution.

What I would ideally like is a .Bat file that I (or my colleagues) can drop into any given folder and run it to mass rename the files. I have done some testing of my own but I cannot quite get it to work as I would like.

This is what I've got at the moment:

@echo off
setlocal enabledelayedexpansion

echo Starting the renaming process...

REM Loop through all PDF files in the current directory
for %%f in (*.pdf) do (
    set "filename=%%~nf"
    set "extension=%%~xf"

    REM Debug: Show the current filename being processed
    echo Processing: %%f

    REM Check if "XYZ" is in the filename
    echo !filename! | findstr /C:"XYZ" >nul
    if !errorlevel! equ 0 (
        echo Found "XYZ" in: !filename!

        REM Retain everything up to and including "XYZ"
        for /f "tokens=1 delims=XYZ" %%a in ("!filename!") do (
            set "newname=%%a"
        )
        
        REM Append "XYZ" to the new name
        set "newname=!newname!XYZ"

        REM Debug: Show the new name before renaming
        echo New name will be: "!newname!!extension!"

        REM Check for duplicates and rename
        if not exist "!newname!!extension!" (
            ren "%%f" "!newname!!extension!"
            echo Renamed "%%f" to "!newname!!extension!"
        ) else (
            echo Skipping "%%f" because a file named "!newname!!extension!" already exists.
        )
    ) else (
        echo "XYZ" not found in: !filename!
    )
)

REM Pause to see the output
echo Finished processing. Press any key to exit.
pause

endlocal

I am aware this is terrible and am expecting to be criticized for my poor scripting, but this actually works decently. It locates the code and removes the stuff after it.

The issue I'm having is if the string before the code contains any of the letters in the code then they are removed also. It also feels clunky and as someone not well versed in this I thought it would be prudent to seek advice.

Would appreciate any help or tips you could give me.

P.S. I am aware that Windows Powershell would be a far better tool for this. However it's blocked on our system for some reason.


Solution

  • @ECHO OFF
    SETLOCAL ENABLEDELAYEDEXPANSION
    rem The following setting for the directory is a name
    rem that I use for testing and deliberately includes spaces to make sure
    rem that the process works using such names. These will need to be changed to suit your situation.
    
    SET "sourcedir=u:\your files"
    
    SET "targetstring=XYZ"
    SET "requiredextension=pdf"
    
    FOR %%e IN ("%sourcedir%\*%targetstring%*.%requiredextension%") DO (
     rem %%e has the full name of the file detected
     ECHO processing %%e
     
     rem ensure targetstring found is upper-case
     SET "originalname=%%~ne"
     ECHO %%~ne |FINDSTR /L /c:"%targetstring%" >NUL
     IF ERRORLEVEL 1 (
      ECHO skipping %%~nxe - does NOT contain upper-case "%targetstring%"
     ) ELSE (
      CALL :subsuconly
      IF EXIST "%sourcedir%\!newname!%%~xe" (
       ECHO Cannot RENAME "%%~nxe" to "!newname!%%~xe" as "!newname!%%~xe" already exists
      ) ELSE (
       ECHO Renaming "%%~nxe" to "!newname!%%~xe"
       ECHO REN "%%e" "!newname!%%~xe"
      )
     )
    )
    
    GOTO :EOF
    
    ::
    :: Substitute only the upper-case targetstring
    :subsuconly
    SET "newname="
    :: first 3 characters of copyname are targetstring?
    :subsloop
    IF "%originalname:~0,3%"=="%targetstring%" SET "newname=%newname%%targetstring%"&GOTO :eof
    SET "newname=%newname%%originalname:~0,1%"
    SET "originalname=%originalname:~1%"
    GOTO subsloop
    

    Your problem is that the delims strin is not a string, but a set of characters which are the delimiters, so your delimiters are X, Y and Z, not the string XYZ.

    By including the filemask *%targetstring%*.%requiredextension%, the for will only select filenames that contain XYZ and end pdf. This avoids the clutter of names that do not fit the mask.

    However, the match is case-insensitive, so it will detect xyz for instance.

    So, detect the required string. findstr sets errorlevel to 0 if the string is found. IF ERRORLEVEL 1 means IF ERRORLEVEL 1 OR GREATER.

    If the string is found, then the new name is generated by simply accumulating each character from the beginning of original name until the first 3 characters of the remaining string in original is the required upper-case string. Think in terms of "zelxyzloXYZ World.pdf" where the string xyz appears before the XYZ

    Then try to rename. Note that the REN command is disarmed and simply echoed for verification. Remove the echo to activate (after verification, naturally).