1

I have a Batch script :

@echo off

setlocal enableDelayedExpansion 
SET /P UserInput=Please Enter a Number:
SET /A number=UserInput
ECHO number=%number%
for %%i in (*.jpeg) do call :JPG %%~ni %%i 
goto :end

:JPG
set str=%1
set /a str2=%str:_color=%
set /a newnamej=%str2%+%number%
echo %1 ==> I can see the problem with it
set lastnamej=%newnamej%_color.jpeg
ren %2 %lastnamej%
goto :eof

:end

The goal of this script is to take all file in a folder. They are all named after a number (1_color.jpeg, 2_color.jpeg, 3_color.jpeg,..) and I want to rename them with an additionnal number (if user input is 5, 1_color.jpeg will become 6_color.jpeg, and so on). I have a problem with this script. if I use a number such as 555, the first file will pass in the for loop 2 times.

(little example : 1_color.jpeg and 2_color.jpeg,

I use my script with 5 so 1_color.jpeg => 6_color.jpeg and 2_color.jpeg => 7_color.jpeg but then, 6_color.jpeg will be read again once, and will become 11_color.jpeg, so my result will be 11_color.jpeg and 7_color.jpeg).
Do someone know how to fix this issue?

Thanks for all!

Raph Schim
  • 528
  • 7
  • 30
  • I went through my problem using an external file. First I copy all file in a .txt, then red from it line by line, and at the end, remove it. – Raph Schim May 11 '17 at 13:45
  • Renaming or removing files within a normal `for` loop is not a good idea as `for` does not enumerate all files in advance -- see this question: [At which point does `for` or `for /R` enumerate the directory (tree)?](http://stackoverflow.com/q/31975093) – aschipfl May 11 '17 at 23:06

2 Answers2

2

The problem have two parts: the for %%i in (*.jpeg) ... command may be dinamically affected by the position that a renamed file will occupy in the whole file list, so some files may be renamed twice and, in certain particular cases with many files, up to three times.

The solution is to use a for /F %%i in ('dir /B *.jpeg') ... command instead, that first get the list of all files, and then start the renaming process.

Also, the rename must be done from last file to first one order, to avoid duplicate numbers.

However, in this case the use of for /F combined with "tokens=1* delims=_" option also allows to process the first underscore-separated number in the file names in a simpler way:

@echo off
setlocal EnableDelayedExpansion

SET /P number=Please Enter a Number:
ECHO number=%number%

for /F "tokens=1* delims=_" %%a in ('dir /O:-N /B *.jpeg') do (
   set /A newNum=%%a+number
   ren "%%a_%%b" "!newNum!_%%b"
)
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • There might still occur collisions due to the purely reverse alphabetic sort order of `dir /O:-N`, because the order is not `10`, `9`,..., `2`, `1`, but `9`,...,`2`, `10`, `1`, so if the entered number is `1`, the `9` is tried to be renamed to `10`, which already exists. +1 anyway, because you addressed both issues of the original code... – aschipfl May 11 '17 at 23:12
  • Thanks for your response! It seems to be easier that what i've done. But as @aschipfl pointed out, the numerical order make it false... But thanks, and I'm using part of your code in my script! – Raph Schim May 12 '17 at 07:58
  • Well, in this case we need a method to know the _natural_ order of the files. If this order is the creation date-time, then you just need to process the files in reversed creation date order to avoid any collisions, that is, just change the `/O:-N` switch in `dir` command by `/O:-D`. – Aacini May 12 '17 at 09:39
1

User Aacini provided a nice solution in his answer, pointing out both issues at hand, namely the fact that for does not fully enumerate the directory in advance (see this thread: At which point does for or for /R enumerate the directory (tree)?) and the flaw in the logic concerning the sort order of the processed files.

However, there is still a problem derived from the purely (reverse-)alphabetic sort order of dir /B /O:-N *.jpeg, which can still cause collisions, as the following example illustrates:

9_color.jpeg
8_color.jpeg
7_color.jpeg
6_color.jpeg
5_color.jpeg
4_color.jpeg
3_color.jpeg
2_color.jpeg
10_color.jpeg
1_color.jpeg

So if the entered number was 1, file 9_color.jpeg is tried to be renamed to 10_color.jpeg, which fails because that file already exists as it has not yet been processed (hence renamed to 11_color.jpeg).

To overcome this problem, you need to correctly sort the items in reverse alpha-numeric order. This can be achieved by left-zero-padding the numbers before sorting them, because then, alphabetic and alpha-numeric sort orders match. Here is a possible implementation:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_LOCATION=."       & rem // (directory containing the files to rename)
set "_PATTERN=*_*.jpeg" & rem // (search pattern for the files to rename)
set "_REGEX1=^[0-9][0-9]*_[^_].*\.jpeg$" & rem // (`findstr` filter expression)
set "_TEMPFILE=%TEMP%\%~n0_%RANDOM%.tmp" & rem // (path to temporary file)

rem // Retrieve numeric user input:
set "NUMBER="
set /P NUMBER="Please Enter a number: "
set /A "NUMBER+=0"
if %NUMBER% GTR 0 (set "ORDER=/R") else if %NUMBER% LSS 0 (set "ORDER=") else exit /B
rem /* Write `|`-separated list of left-zero-padded file prefixes, original and new
rem    file names into temporary file: */
> "%_TEMPFILE%" (
    for /F "tokens=1* delims=_" %%E in ('
        dir /B "%_LOCATION%\%_PATTERN%" ^| findstr /I /R /C:"%_REGEX1%"
    ') do (
        set "NAME=%%F"
        setlocal EnableDelayedExpansion
        set "PADDED=0000000000%%E"
        set /A "NUMBER+=%%E"
        echo !PADDED:~-10!^|%%E_!NAME!^|!NUMBER!_!NAME!
        endlocal
    )
)
rem /* Read `|`-separated list from temporary file, sort it by the left-zero-padded
rem    prefixes, extract original and new file names and perform actual renaming: */
< "%_TEMPFILE%" (
    for /F "tokens=2* delims=|" %%K in ('sort %ORDER%') do (
        ECHO ren "%%K" "%%L"
    )
)
rem // Clean up temporary file:
del "%_TEMPFILE%"

endlocal
exit /B

After having successfully verified the correct output of the script, to not forget to remove the upper-case ECHO command in front of the ren command line.

The script uses a temporary file that receives a |-separated table with the padded numeric prefix in the first, the original file name in the second and the new file name in the third column, like this:

0000000010|10_color.jpeg|11_color.jpeg
0000000001|1_color.jpeg|2_color.jpeg
0000000002|2_color.jpeg|3_color.jpeg
0000000003|3_color.jpeg|4_color.jpeg
0000000004|4_color.jpeg|5_color.jpeg
0000000005|5_color.jpeg|6_color.jpeg
0000000006|6_color.jpeg|7_color.jpeg
0000000007|7_color.jpeg|8_color.jpeg
0000000008|8_color.jpeg|9_color.jpeg
0000000009|9_color.jpeg|10_color.jpeg

The temporary file is read and sorted by the sort command. The strings from the second and third columns are extracted and passed over to the ren command.

Community
  • 1
  • 1
aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • 1
    Hi, thanks for your answer! It is great since it permit me understand what's happening. However, I used another trick to make my program works since it seem a little complicated for me : I writed the result of my for loop in another file after renaming all my file adding a after. After that, I read my file line by line and rename files using the name in the file minus the "_" that I added. 1.JPEG => 1_.JPEG => 1_.JPEG in my file => renamed to 2.JPEG, it works since 2.JPEG is named 2_.JPEG after the rename 2_.JPEG will be 3.JPEG and so on :) – Raph Schim May 17 '17 at 06:54