1

Let's say I have a couple of images and I need to rename them and on every iteration add an incremented number.

For this situation I have three images no matter how they name is and I want to rename them like this.

1239.jpg => file1.jpg
file.jpg => file2.jpg
image.jpg => file3.jpg

My commands executed in a command prompt window for this task are:

setlocal EnableDelayedExpansion
set filename=file
set counter=1
for /f "usebackq delims=*" %i in ('dir /b *.jpg') do (set /a counter+=1 ren "%i" "%filename%!counter!.jpg")

But this results in the error message Missing operator.

Can anyone help me with this?

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • If you are not familiar with the Windows command line, why do you not use free software like multimedia viewer IrfanView (for private usage) which has in menu __File__ the menu item __Batch Conversion/Rename__ to open a dialog which lets every user rename lots of files with a view clicks with an incrementing number? Or what about shareware tool Total Commander on which a list of files can be selected with Ctrl+A and then the multi-rename tool is opened with Ctrl+M to rename files with options defined in the dialog window and with seeing the resulting file names even before execution? – Mofi Apr 24 '20 at 10:38
  • I agree with @Mofi that an existing program might be easier than writing your own. Since you did not paste the error message into the question, I am guessing that since "usebackq" is specified that it is not finding a GRAVE ACCENT (backtick). It appears that the code uses only an APOSTROPHE. – lit Apr 24 '20 at 10:59
  • @Mofi because i want to learn it? Of course i can use some of these programs but that's not the way i want to do it. I read a lot of topics about how to loop and rename but any of these solutions here is not worked for me. – Sebastianvs7 Apr 24 '20 at 11:08

1 Answers1

0

The commands SETLOCAL and ENDLOCAL can be used only in a batch file. Please read this answer for details about the commands SETLOCAL and ENDLOCAL. These two commands do nothing on being executed in a command prompt window. It is necessary to start cmd.exe with option /V:ON to use delayed expansion in a command prompt window as explained by the help output on running cmd /? in a command prompt window.

The usage of usebackq requires enclosing the command line to be executed in ` instead of ' as usual. usebackq is mainly used for processing the lines of a text file of which name is specified in the round brackets enclosed in ".

The following command line with the two commands SET and REN is not of valid syntax. The command SET interprets everything after /a as arithmetic expression to evaluate. In this case the expression misses an operator between 1 and ren whereby ren would be interpreted here as name of an environment variable and not as command to execute next after set.

(set /a counter+=1 ren "%i" "%filename%!counter!.jpg")

The valid command line would be:

set /A "counter+=1" & ren "%i" "%filename%!counter!.jpg"

Enclosing the arithmetic expression in double quotes makes it clear for command SET where the arithmetic expression starts and where it ends. The conditional execution operator & is interpreted by Windows command processor before executing the command SET and results in execution of command REN after command SET even on SET would fail to evaluate the arithmetic expression.

A file renaming task done with Windows command processor is no easy to achieve if

  1. the file extension of the files should not change and
  2. files with any name including those with one or more &()[]{}^=;!'+,`~ should be supported and
  3. there can be already files in the directory with one of the new file names.

For testing the batch file below I created first in a directory following files:

file.jpg
file1.jpg
file2.jpg
file3.jpg
file 4.jpg
File8.jpg
hello!.jpg
image.jpg

The directory was on a FAT32 drive. The file systems FAT16, FAT32 and exFAT return a list of matching directory entries not sorted by name as NTFS which means the list output by command DIR in the main FOR loop in code below is in an unsorted and therefore unpredictable order.

It would be of course possible to append the DIR option /ON to get the list of file names ordered by DIR according to name, but in fact that is not real help in this case, especially because of DIR makes a strict alphabetical sort and not an alphanumeric sort.

A strict alphabetic sort returns a list of ten file names as file1.jpg, file10.jpg, file2.jpg, file3.jpg, ..., file9.jpg while an alphanumeric sort returns a list of ten file names as file1.jpg, file2.jpg, file3.jpg, ..., file9.jpg, file10.jpg.

So here is the commented batch file for this file rename task:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileName=file"

rem The user can run this batch file with a folder path in which all *.jpg
rem files should be renamed with an incremented number. Otherwise the
rem directory of the batch file is search for *.jpg files to rename.
if not "%~1" == "" (
    pushd "%~1" || exit /B
) else (
    pushd "%~dp0" || exit /B
)

set "FileCount=0"
set "DelayedLoopCount=0"
set "DelayedRenameCount=0"

rem Remove all existing environment variables in local environment of which
rem name starts with DelayedRename_ whereby the underscore is very important
rem because there is also the environment variable DelayedRenameCount.
for /F "delims==" %%I in ('set DelayedRename_ 2^>nul') do set "%%I="

rem Get a captured list of all *.jpg files in current directory and then
rem rename one file after the other if that is possible on no other file
rem has by chance already the new file name for the current file.
for /F "eol=| delims=" %%I in ('dir *.jpg /A-D /B 2^>nul') do call :RenameFile "%%I"
goto DelayedRenameLoop

:RenameFile
set /A FileCount+=1
set "NewName=%FileName%%FileCount%%~x1"

rem Has the file case-sensitive already the right name?
if %1 == "%NewName%" goto :EOF

rem Is the new file name the same as the current name
rem with exception of the case of one or more letters?
if /I %1 == "%NewName%" (
    echo Rename %1 to "%NewName%"
    ren %1 "%NewName%"
    goto :EOF
)

rem Is there no other file which has already the new name?
if not exist "%NewName%" (
    echo Rename %1 to "%NewName%"
    ren %1 "%NewName%"
    goto :EOF
)

rem Another file or folder has already the new name. Remember the name
rem of this file and the new file name with an environment variable for
rem a delayed rename after all other files have been renamed as far as
rem possible.
set /A DelayedRenameCount+=1
set "DelayedRename_%DelayedRenameCount%=|%~1|%NewName%"
goto :EOF

rem It could happen that "file15.jpg" should be renamed to "file3.jpg"
rem while "file3.jpg" exists already which should be renamed to "file12.jpg"
rem while "file12.jpg" exists already which should be renamed to "file20.jpg".
rem This extra loop is used for such worst case scenarios which is executed
rem in a loop until all files have been renamed with a maximum of 50 loop
rem runs in case of one file cannot be renamed and therefore blocking
rem renaming of another file. An endless running loop should be avoided.
rem A file cannot be renamed if a folder has by chance the new file name.
rem A file cannot be renamed if an application has opened the file with
rem a sharing access mode preventing the rename of the file as long as
rem being opened by this application.

:DelayedRenameLoop
if %DelayedRenameCount% == 0 goto EndBatch
for /F "tokens=1-3 delims=|" %%I in ('set DelayedRename_ 2^>nul') do if not exist "%%K" (
    echo Rename "%%J" to "%%K"
    ren "%%J" "%%K"
    set "%%I"
    set /A DelayedRenameCount-=1
)
set /A DelayedLoopCount+=1
if not %DelayedLoopCount% == 50 goto DelayedRenameLoop

:EndBatch
popd
endlocal

This batch file output on execution:

Rename "file3.jpg" to "file4.jpg"
Rename "file 4.jpg" to "file5.jpg"
Rename "File8.jpg" to "file6.jpg"
Rename "hello!.jpg" to "file7.jpg"
Rename "image.jpg" to "file8.jpg"
Rename "file2.jpg" to "file3.jpg"
Rename "file1.jpg" to "file2.jpg"
Rename "file.jpg" to "file1.jpg"

The files in the directory were finally:

file1.jpg
file2.jpg
file3.jpg
file4.jpg
file5.jpg
file6.jpg
file7.jpg
File8.jpg

What about the last file?

It has the file name File8.jpg instead of file8.jpg although executed was ren "image.jpg" "file8.jpg". Well, FAT32 is a bit problematic regarding to updates of the file allocation table on a table entry changes only in case of one or more letters.

The solution is using this batch file with two extra FOR loops with # as loop variable and optimized by removing the comments.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileName=file"

if not "%~1" == "" (pushd "%~1" || exit /B) else (pushd "%~dp0" || exit /B)

set "FileCount=0"
set "DelayedLoopCount=0"
set "DelayedRenameCount=0"
for /F "delims==" %%I in ('set DelayedRename_ 2^>nul') do set "%%I="
for /F "eol=| delims=" %%I in ('dir *.jpg /A-D /B 2^>nul') do call :RenameFile "%%I"
goto DelayedRenameLoop

:RenameFile
set /A FileCount+=1
set "NewName=%FileName%%FileCount%%~x1"
if %1 == "%NewName%" goto :EOF
if /I %1 == "%NewName%" (
    echo Rename %1 to "%NewName%"
    ren %1 "%NewName%"
    goto :EOF
)
if not exist "%NewName%" (
    echo Rename %1 to "%NewName%"
    ren %1 "%NewName%"
    for %%# in ("%NewName%") do if not "%%~nx#" == "%NewName%" ren "%%~nx#" "%NewName%"
    goto :EOF
)
set /A DelayedRenameCount+=1
set "DelayedRename_%DelayedRenameCount%=|%~1|%NewName%"
goto :EOF

:DelayedRenameLoop
if %DelayedRenameCount% == 0 goto EndBatch
for /F "tokens=1-3 delims=|" %%I in ('set DelayedRename_ 2^>nul') do if not exist "%%K" (
    echo Rename "%%J" to "%%K"
    ren "%%J" "%%K"
    for %%# in ("%%K") do if not "%%~nx#" == "%%K" ren "%%~nx#" "%%K"
    set "%%I"
    set /A DelayedRenameCount-=1
)
set /A DelayedLoopCount+=1
if not %DelayedLoopCount% == 50 goto DelayedRenameLoop

:EndBatch
popd
endlocal

The result of this enhanced batch file is even on FAT32:

file1.jpg
file2.jpg
file3.jpg
file4.jpg
file5.jpg
file6.jpg
file7.jpg
file8.jpg

The reason for using | as string separator on execution of

set "DelayedRename_%DelayedRenameCount%=|%~1|%NewName%"

resulting, for example, in execution of

set "DelayedRename_1=|file.jpg|file1.jpg"
set "DelayedRename_2=|file1.jpg|file2.jpg"
set "DelayedRename_3=|file2.jpg|file3.jpg"

is that the vertical bar is not allowed in a file folder name. So it is a very good character to separate the name of the environment variable with the equal sign appended from current file name and from new file name. This makes it possible to use later delims=| for renaming the file and deleting the environment variable.

See also the Microsoft documentations:

The equal sign is allowed in a file name. It is even possible that a *.jpg file has as file name =My Favorite Picute=.jpg which is another reason for using | to get executed for example

set "DelayedRename_4=|=My Favorite Picute=.jpg|file9.jpg"

which later results in assigned DelayedRename_4= to loop variable I, =My Favorite Picute=.jpg to loop variable J and file9.jpg to loop variable K in the FOR loop doing the delayed file renames.

Note: Each FOR loop with '...' in the round brackets results

  • in starting in background one more command process with %ComSpec% /c '...' and
  • capturing the output written to handle STDOUT like the output of the cmd.exe internal commands DIR and SET
  • while cmd.exe processing the batch file waits until started cmd.exe terminated (closed) itself after execution of the command line
  • and then processing the captured lines one after the other by FOR with ignoring empty lines and lines starting with the defined end of line character after doing the string delimiting which is the reason why eol=| is used on main FOR loop as a file name can start with default end of line character ; and which of course should not be ignored here.

The redirection operator > must be escaped with caret character ^ on those FOR command lines to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir or set command line in the separate command process started in background.

The batch file does not use delayed expansion as this would cause troubles on a file name having one or more exclamation marks which would be interpreted as beginning/end of a delayed expanded environment variable reference on command lines like ren "%%J" "%%K". Therefore a subroutine is used for the main file rename loop on which it is necessary to access the two incremented counter values.

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

  • call /?
  • dir /?
  • echo /?
  • endlocal /?
  • exit /?
  • goto /?
  • if /?
  • popd /?
  • pushd /?
  • rem /?
  • ren /?
  • set /?
  • setlocal /?

I suggest further to look on:

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • Hey sorry for my late answer but thanks so much for your deeply explanation of the code. It is very helpful and also it give me more new experiences with the command prompt. Anyway most important for me is that my renaming is now very fast. – Sebastianvs7 May 05 '20 at 09:13