2

Windows batch file renames filename unexplainably in FOR loop

I have a small Windows 7 batch file to rename a library of MP3 filenames. I've included only the last few lines of code in this question. It works fine except one problem with the section of code that appends a string of text (artist name) from a variable to each filename in the directory. (Once solved, my batch file will use "set /p var=" to prompt for text to fill the variable.)

Two sample filenames used for this test are:

01-serious song.mp3
02-happy song.mp3

This section of code results in a problem:

@echo off
setlocal enabledelayedexpansion
::Temporay test variable
set str=Artist-
for %%a in (*.mp3) do (
  set oldName=%%a
  set newName=!str!!oldName!
  ren "!oldName!" "!newName!"
)
endlocal

The result is that the 02 file ends up with the string appended once (as intended), while the 01 filename gets the string appended twice (error):

Artist-Artist-01-serious song.mp3
Artist-02-happy song.mp3

I've combed the Internet and not found one discussion of this happening. And I hate to say that I've spent about 8 hours working on it myself. After many tests and research, I believe that the rename statement was somehow causing this.

So I changed the batch file to call a subroutine with the rename command in it, thinking it was interferring with the for statement. That still had the problem. Then I edited the batch file with echo commands as shown below to test it.

Version Two Test:

@echo off
setlocal enabledelayedexpansion
set str=Artist-
for %%a in (*.mp3) do (
set oldName=%%a
set newName=!str!!oldName!
  call :Action
echo.
echo back in For loop
)
echo.
echo For loop completed
echo !oldName!
echo !newName!
endlocal
goto:eof

:Action
echo.
echo start of Action loop
echo !oldName!
echo !newName!
ren "!oldName!" "!newName!"
echo end of Action loop

Here is the resulting output shown on the CMD screen:

start of Action loop
01-serious song.mp3
Artist-01-serious song.mp3
end of Action loop

back in For loop

start of Action loop
02-happy song.mp3
Artist-02-happy song.mp3
end of Action loop

back in For loop

start of Action loop
Artist-01-serious song.mp3
Artist-Artist-01-serious song.mp3
end of Action loop

back in For loop

For loop completed
Artist-01-serious song.mp3
Artist-Artist-01-serious song.mp3

This was interesting as it showed the subroutine properly being called and returned twice. Then it somehow got called a third time, where it apparently renamed the 01 file again, appending the variable a second time.

If I put echo before the ren statement, then the screen output shows the subroutine is only called twice, but it doesn't rename the files, of course. I also tried using the move command instead of rename, and got the same problem result.

So, my question is, Why is the for loop calling the subroutine three times and making the 01 file get processed twice, and how can I make it to work as intended?

Thank you, Pete.

Peter Buxton
  • 125
  • 1
  • 8

2 Answers2

5

Try

for /f "delims=" %%a in (' dir /b /a-d *.mp3') do (

The problem is that your original syntax finds the next filename - which finds your renamed file since the new filename (with the prefix) is logically 'greater than' the old.

Using dir builds a list of filenames, and processes the list once it has been completely built - hence the list doesn't vary as the files are renamed.

The "delims=" clause ensures that the default parsing of the filename is ineffective - otherwise, the filename would be interpreted as a series of [space-separated] tokens.

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • +1, The simple FOR loop is cached, so nailing down this behavior can be tricky. The loop reads a block of file names from disk into memory and processes them. If the command detects the end of the file list during the read, then it doesn't matter if a file is renamed later. But if the command knows it needs to read more, then it could find a newly renamed (or created) file. The size of the cache can vary between Windows versions (or even machines?). I once succeeded in creating a virtual "infinite" loop using this behavior. It would eventually exceed max name length, but not in my lifetime :-) – dbenham Jun 20 '13 at 14:26
  • Peter, I changed the line as you said and that fixed the whole problem. I'll study up more about the options you used in the FOR statement that made it work. This whole project was an attempt to learn more about this batch language, so you've helped me immensely. Thanks! - Pete – Peter Buxton Jun 20 '13 at 16:20
  • @dbenham thank you for your explanation. I see now that knowing the deep inner workings of how these commands are cached and interpretted is essential. I've never seen the details of it before finding this website and forum. – Peter Buxton Jun 20 '13 at 16:28
2

replace this:

for %%a in (*.mp3) do (
  set oldName=%%a
  set newName=!str!!oldName!
  ren "!oldName!" "!newName!"
)

with that:

for /f "delims=" %%a in ('dir  /b /a-d *.mp3') do (
  set "oldName=%%~a"
  set "newName=%str%!oldName!"
  ren "!oldName!" "!newName!"
)

I can explain this later, if you want so, but Peter was faster :)

Endoro
  • 37,015
  • 8
  • 50
  • 63