Windows Batch rename, renames a file 2 times [duplicate] - windows

The command for can be used to enumerate a directory and apply (a) certain command(s) on each item. With the /R the same can be accomplished for a full directory tree.
What happens when the content of the enumerated directory (tree) is changed by the command(s) in the body of the for command?
Supposed we have the directory D:\data with the following content:
file1.txt
file2.txt
file3.txt
The output of for %F in ("*.txt") do echo %F when executed in said directory will reflect the above list obviously.
However, what is the output of the for loop when a command in the for body modifies the content of the directory? For instance, one of the files in the list is deleted, let's say file3.txt, before it is actually iterated? Or if a new file is created, like file4.txt, before completion of the loop?
How does for /R behave in that context? Supposed there are several sub-directories sub1, sub2, sub3, each containing the above list of files; for /R is currently iterating through sub2, sub1 has already been processed, but sub3 not yet; the contents of sub1 and sub3 are changed at that point (when currently walking through sub2 as mentioned); what will be enumerated then? I guess, the change of the content of sub1 won't be recognised, but what about sub3?
Finally, is there a difference in behaviour of for or for /R when being executed in the command prompt or from a batch file? And are there differences in different Windows versions?
Note:
See also my similar question about the forfiles command.

This is an excellent question!
Let's concentrate on plain for command for a moment. There is a known bug related to this command when it is used to rename files. For example:
set i=0
for %%a in (*.txt) do (
set /A i+=1
ren "%%a" !i!.txt
)
In this case is frequently that certain files be renamed twice, and even three times in certain cases. The problem is that this behavior depends on a series of factors that are not documented, like the position of the first renamed file inside the list of original files and several other points. Similarly, if a file is deleted before it is processed by the for, a "File not found" message is usually issued (although not ALL times). If a new file is created in the directory after the for started execution, then it may or may not be processed by the for depending (again) on a series of factors. The usual way to avoid the problem with rename is to force for command to first read the whole list of files and then process the list:
for /F "delims=" %%a in ('dir /B *.txt') do (
set /A i+=1
ren "%%a" !i!.txt
)
This way, don't matters the changes that can be made on the files in the disk: the for /F command will always process the original file list.
A similar problem happen with for /R command, but in this case the possibility of problems is greater because there are more directories where dynamic changes can be made. Again: the exact behavior depends on a series of unknown factors and the way to avoid them is via for /F ... in ('dir /S /B'). However, if you are really interested in this point, I encourage you to made a series of tests on the subject (and post the results). ;)

Related

At which point does `for` or `for /R` enumerate the directory (tree)?

The command for can be used to enumerate a directory and apply (a) certain command(s) on each item. With the /R the same can be accomplished for a full directory tree.
What happens when the content of the enumerated directory (tree) is changed by the command(s) in the body of the for command?
Supposed we have the directory D:\data with the following content:
file1.txt
file2.txt
file3.txt
The output of for %F in ("*.txt") do echo %F when executed in said directory will reflect the above list obviously.
However, what is the output of the for loop when a command in the for body modifies the content of the directory? For instance, one of the files in the list is deleted, let's say file3.txt, before it is actually iterated? Or if a new file is created, like file4.txt, before completion of the loop?
How does for /R behave in that context? Supposed there are several sub-directories sub1, sub2, sub3, each containing the above list of files; for /R is currently iterating through sub2, sub1 has already been processed, but sub3 not yet; the contents of sub1 and sub3 are changed at that point (when currently walking through sub2 as mentioned); what will be enumerated then? I guess, the change of the content of sub1 won't be recognised, but what about sub3?
Finally, is there a difference in behaviour of for or for /R when being executed in the command prompt or from a batch file? And are there differences in different Windows versions?
Note:
See also my similar question about the forfiles command.
This is an excellent question!
Let's concentrate on plain for command for a moment. There is a known bug related to this command when it is used to rename files. For example:
set i=0
for %%a in (*.txt) do (
set /A i+=1
ren "%%a" !i!.txt
)
In this case is frequently that certain files be renamed twice, and even three times in certain cases. The problem is that this behavior depends on a series of factors that are not documented, like the position of the first renamed file inside the list of original files and several other points. Similarly, if a file is deleted before it is processed by the for, a "File not found" message is usually issued (although not ALL times). If a new file is created in the directory after the for started execution, then it may or may not be processed by the for depending (again) on a series of factors. The usual way to avoid the problem with rename is to force for command to first read the whole list of files and then process the list:
for /F "delims=" %%a in ('dir /B *.txt') do (
set /A i+=1
ren "%%a" !i!.txt
)
This way, don't matters the changes that can be made on the files in the disk: the for /F command will always process the original file list.
A similar problem happen with for /R command, but in this case the possibility of problems is greater because there are more directories where dynamic changes can be made. Again: the exact behavior depends on a series of unknown factors and the way to avoid them is via for /F ... in ('dir /S /B'). However, if you are really interested in this point, I encourage you to made a series of tests on the subject (and post the results). ;)

Why is an extra file being created in FOR loop of batch program?

I've written the following batch file to create multiple files using FOR loop:
#echo off
cls
FOR /L %%i IN (1 1 10) DO (
echo.> file%%i.txt
IF ERRORLEVEL 0 echo Successfully created file 'file%%i.txt'.
)
dir /b *.txt
FOR %%i IN (*.txt) DO (
echo.> file%%i.txt
IF ERRORLEVEL 0 echo Successfully created file 'file%%i.txt'.
)
Here, 10 files (viz. file1.txt .... file10.txt) are created in the first FOR loop.
And in the second FOR loop I've used these files to frame the name of next new files. (viz.filefile1.txt.txt ... filefile10.txt.txt)
But, an extra file is being created : filefilefile1.txt.txt.txt
What logical issue is causing the creation of this extra file ?
EDITED - Seems i've not explained it properly and people doesn't see how it works. My fault. I'm going to try to explain it better.
The reason is the way the for command works internally.
When the line for var in (files) is reached, the directory is checked to see if any files match and need to be processed.
Then, for command (cmd really), issues a directory query to enumerate the files. This query returns only the first file in set. If there are any aditional files that match the file mask in for command, a flag is set that indicates to the caller (cmd) that there are more files to be processed, but the list of the remaining files is not yet retrieved.
When execution of code inside for reachs the end of an iteration, and there are files pending to be read, a query is sent to get the remaining of the list of files pending to be processed and that match the for file selection.
System fills a buffer with the list of files remaining. If at that point the list of files is short enough to be full readed in buffer, the query will not be repeated. If the list of files is big enough to not fit in buffer, partial list is retrieved and when files in retrieved list are processed, the query will be sent again to get more files to process.
Number of files in buffer depends on length of filename. Shorter filenames, more files in buffer and less queries to filesystem.
This behaviour (retrieving remaining list of files at end of first file processing) is only done if the query for files returns that there are files pending. When one query does not return that flag, no more files are retrieved.
EXCEPTIONS
If working in NTFS, the files only get included in "requeries" if they are alphabetically greater than the last file that has been processed in for command.
If working if FAT, the query will include all new file generated that match the for command file selection independly of it name. And yes, it can get into an infinite loop. (In tests, the system buffer only retrieve one filename, and requery in each iteration). You can try
break > a.txt && cmd /v:on /c "for %f in (*.txt) do break > !random!.txt"
All my tests has been made on a windows 7 64bit, NTFS and FAT32 partition (this on a USB drive). No way to test other configurations. If anyone see a diferent behaviour, please comment.
For more information, ZwQueryDirectoryFile
I don't know why, but when you write ... IN (*.txt) ... in the second for loop, it is trying to find files that are just created within the body of the loop.
To eliminate that, I would make my filter a bit more specific.
FOR %%i IN (file??.txt) DO (
I ran this and it creates only 20 files as expected.
Like adarshr said, the second FOR loop can find even the new created files.
You can avoid this by using FOR/F with a command, as the result of the dir is completely fetched before the body of the loop is executed.
...
FOR /F "delims=" %%i IN ('dir /b *.txt') DO (
echo.> file%%i.txt
IF ERRORLEVEL 0 echo Successfully created file 'file%%i.txt'.
)

Windows batch file loop running one extra iteration [duplicate]

This question already has an answer here:
At which point does `for` or `for /R` enumerate the directory (tree)?
(1 answer)
Closed 3 years ago.
I'm using a windows batch file to rename a very large number of files in one go. The renaming simply adds a prefix to the file name. The file executes fine except for the last file, to which the prefix is somehow applied twice. I'm not sure what I'm doing wrong. Can anyone point out the problem to me?
for %%i in (*.csv) do ren %%i myprefix_%%i
#echo off
SETLOCAL
DEL *.csv
XCOPY ..\*.csv .
(
for %%i in (*.csv) do (
echo ====================
echo %%i
dir *.csv
ren %%i x%%i
dir *.csv
echo ====================
)
)>u:\junk.txt&edit u:\junk.txt
I ran the above batch in a clean directory below a directory that contained a few .CSVs. All it does is zap the current directory's .csvs, copy those from the parent directory and then rename by prefixing. It revealed quite a bit.
(U: is a RAMDRIVE, EDIT executes EDITPLUS; NOTEPAD would do as well here)
The operation depends on quite what prefix is added. A prefix starting "a" works differently from a prefix starting "x". AND the result depends on the filesystem being used. NTFS presents the filenames alphabetically but the FAT system on the RAMDRIVE doesn't sort the names.
I believe the problem is caused by the 'findnext' filename mechanism not dealing as expected with a changing list of filenames. As it changes one, that name may move in the list and hence the file may be reprocessed as its new name is encountered again - possibly multiple times.
A cure is to replace the for selection with
for /f "delims=" %%i in ('dir /b *.csv') do (
where the DIR list is created and THEN processed, so the changes don't affect it.
You may be having some spaces in your filenames. Use double-quotes around filenames. Try the following:
for %%i in (*.csv) do ren "%%I" "myprefix_%%I"
This will execute as:
ren "tmp - Copy.csv" "myprefix_tmp - Copy.csv"
Also, you should look for any hidden files.

Combining two FOR commands (/f /l) in a batch file

Okay guys, I am fairly new to working with batch files and I have two files I have previously created which are both working independantly.
I am looking to combine them but I still do not fully understand the FOR command. I was hoping someone could combine these two sets of code into one and if possible explain how the came up with the code they used from my two sources.
This file copies another file (in this case test.txt) to every subdirectory in a directory
FOR /R d:\ %%A IN (test.txt) DO copy d:\%username%\Desktop\Test\Resources\test.txt %%A
FOR /R h:\ %%A IN (test.txt) DO copy d:\%username%\Desktop\Test\Resources\test.txt %%A
This file copies and renames another file X amount of times (in this case 5) renaming each succesive copy in increments of 1.
For /l %%1 in (1,1,10) do (
copy test.txt test%%1.txt > nul
)
Basically I want the selected file (test.txt) to be copied from a set location to every subdirectory within a directory and then copied in each folder X amount of times and renamed with increasing values e.g.
test1.txt
test2.txt
test3.txt
etc.
Thankyou in advance.
This is actually very straight forward.
You already have functioning code that copies from the source to each subdirectory. In pseudo code: FOR (each directory) DO COPY source to target.
You also have code that can copy the file 10 times with incrementing names. You want to do this for each directory in the 1st step. So, again in pseudo code, it will look something like this:
FOR (each directory) DO (
COPY source to target
FOR (N=1 TO 10) DO COPY source to targetN
)
None of the syntax above is real, except that the parentheses after the DO are actually how you allow a batch FOR command to execute a block of multiple commands. (Actually there are other techniques to do this, but the parens work just fine.)
The part that you are missing is how to insert the incrementing number into the %%A target name. This is done by using FOR variable modifiers, as described at the end of the FOR documentation that you can access from the command line by typing HELP FOR, or FOR /?.
The modifiers allow you to deconstruct a file specification into its component parts. Note that the file doesn't have to physically exist, the file spec can still be broken down into the constituent parts.
%%~dpnA = drive:\path\baseName (no extension)
%%~xA = .extension, including the dot.
You've already got the incrementing number - I'm going to use %%N instead of %%1. So the full target will be the concatenation of the 3 components: %%~dpnA%%N%%~xA.
Putting it all together gives the full solution:
FOR /R d:\ %%A IN (test.txt) DO (
copy d:\%username%\Desktop\Test\Resources\test.txt %%A
FOR /L %%N IN (1 1 10) DO copy d:\%username%\Desktop\Test\Resources\test.txt %%~dpnA%%N%%~xA
)

How to rename and add incrementing number suffix on multiple files in Batch Script?

I have 500 files coming in and I need to first check if any file(s) exist then rename all of them regardless of what their filename is (the files are named in a different language).
No need to process them in any order.
Rename:
1. “¦X¼d¬f-20110703-¦+¦dñHÑ-ª-¦=¬¦.xls”
2. “¦X¼d¬f-20110707-¦+¡¦-+¡8.xls”
3. “¦X¼d¬f-20110707-¦+¡¦ñj¦«.xls”
4. “¦X¼d¬f-20110708-¦+¡¦¬M¼n.xls”
5. “¦X¼d¬f-20110713-¦d¼O¼n¦hÑP.xls”
.
.
.
500
To:
“TWN_CH_INV_VISIT_FORM_01.xls”
“TWN_CH_INV_VISIT_FORM_02.xls”
“TWN_CH_INV_VISIT_FORM_03.xls”
“TWN_CH_INV_VISIT_FORM_04.xls”
“TWN_CH_INV_VISIT_FORM_05.xls”
.
.
.
“TWN_CH_INV_VISIT_FORM_500.xls”
Hope you could help me on this one. I’ve been trying to do this for weeks.
a simple FOR with a count (SET /A) should do what you need.
setlocal enabledelayedexpansion
SET /A COUNT=0
FOR %%A IN (*.xls) DO (
SET /A COUNT+=1
REN "%%A" "TWN_CH_INV_VIST_FORM_!COUNT!.xls"
)
See HELP FOR and HELP SET
This is a deceptively difficult question to solve.
The 5 year old PA answer has a few problems.
1) The FOR loop begins iterating without buffering the entire directory tree, so it has the potential to rename a file that has already been renamed. I believe that is why the 7 file is missing within r0mmel's comment.
2) Delayed expansion occurs after for variables are expanded, so the file name will be corrupted and the rename will fail if the name contains a ! character.
3) A rename can fail if there already exists a TWN_CH_INV_VIST_FORM_n.xls file with the same number.
At first I thought I could solve the problem using the following:
#echo off
for /f "delims=: tokens=1*" %%A in (
'dir /b *.xls ^| findstr /n "^"'
) do ren "%%B" "TWN_CH_INV_VIST_FORM_%%A.xls.new"
ren *.txt.new *.
I use DIR /B to list the files, and pipe that result to FINDSTR to prefix each file name with a line number, followed by a colon.
I then use FOR /F to iterate and parse the results into the number and the file name. FOR /F buffers the entire result before iterating, so I don't need to worry about renaming the same file twice.
I first give the renamed files a .xls.new "extension", just in case your directory already has files that meet the TWN_CH_INV_VIST_FORM_n.xls pattern. You don't want any name collisions. The final REN command then simply removes the .new extension to leave the desired .xls.
BUT, I just noticed that the original file names have lots of weird characters that could involve unicode that is not in the current code page. FOR /F does not play well with unicode.
There is one other minor issue in that the above does not pad the number to a fixed width. (this could have been solved easily enough)
So at this point it is time to break out my JREN.BAT regular expression renaming utility. It is pure script (hybrid batch / JScript) that runs natively on any Windows machine from XP onward. It has a built in facility to incorporate a fixed width incrementing number in the new name, and it works fine with unicode. I still temporarily give the new name the ".xls.new" extension to avoid any name collisions.
#echo off
call jren "^.*" "'TWN_CH_INV_VIST_FORM_'+$n+'.xls.new'" /j /npad 3 /fm *.xls
ren *.xls.new *.
I chose to pad the incrementing number to 3 digits instead of 2 because the OP said there could be 500 files.
Full documentation for JREN.BAT is available from the command line via jren /?, or jren /?? if you want paged output.

Resources