Batch rename files using find in content - windows

Long-time listener first-time caller.
I'm trying to use a windows batch script to search for specific strings in files contained in a directory, and classify them with a file name and random value for further processing.
This is what I have put together based on similar posts:
setlocal enabledelayedexpansion
for %%a in (C:\Temp\*.txt) do (
set t1=string1-%RANDOM%.txt
set t2=string2-%RANDOM%.txt
find "string1" %%~fa && ren %%~fa %t1%
find "string2" %%~fa && ren %%~fa %t2%
)
If I move the set commands outside the for loop it will work, but I need it to generate a random file name within the loop in case there's multiple files found with 'string1'.
Any help much appreciated!
Thanks.

In cmd, variables are replaced with it value when the block in which they are placed is readed. In your case, your set variables are replaced once, when the for is readed, and changes are not visible because once that replacement is done, there is no more variable replacement. That is the reason for the enabledelayedexpansion, but a change in code is needed
setlocal enabledelayedexpansion
for %%a in (C:\Temp\*.txt) do (
set t1=string1-!RANDOM!.txt
set t2=string2-!RANDOM!.txt
find "string1" "%%~fa" && ren "%%~fa" !t1!
find "string2" "%%~fa" && ren "%%~fa" !t2!
)
For delayed expansion of variables, it is necessary to indicate to cmd which of the variables should be replaced in each access. To do it, change % with ! when using the variable. This is not necessary for control variables in for command (your %%a), for which it is assumed.
Be careful, your code does not handle the posibility of duplicate file names. Random means random, not unique. Collisions are possible.

Related

Batch - Inner For-Loop token parameter does not accept delayed Expanded variable within Outer For Loop [duplicate]

This question already has answers here:
Issue with *For loop* and *delayed expansion*
(2 answers)
Closed 4 months ago.
I have numerous mp4 files inside a folder with can take the form below
Dancing_2022-10-30_00-05-32_015_Paris.mp4
Eating_in_the_Restaurant_2022-07-03_04-21-25_497_London.mp4
My goal is to create two folders and move the respective files
I want the last underscore (_) delimited item (in this case Paris and London) extracted to create the first folders and then extract the date part (2022-10-30 and 2022-07-03) and create the subfolder. Then move the files accordingly.
My biggest challenge is to extract the tokens. Usually does allows tokens to be extracted from left to right but this assumes the file names are consistent. However in my case the files names are not consistent. There is some consistency from right to left with regards to the placement of the _ delimited parts I want to extract.
I have a draft batch script but the issue I have is that CMD does not allow delayed expanded variable to be passed to the inner For-Loop within the Outer For loop. Any clues to the solution. Thanks
This is my draft batch script.
#echo off
Setlocal EnableDelayedExpansion
for %%F in ("*.mp4") do (
set file=%%~nF
echo !file!
set files="!file:_=" "!"
echo file=!files!
set cnt=-4
for %%a in (!files!) do set /a cnt+=1
echo !cnt!
for /F "tokens=!cnt!* delims=_" %%a in ("!file!") do set output=%%b
echo Output=%output%
for /F "tokens=1-4 delims=_" %%a in ("!output!") do echo %%d
)
endlocal
pause
#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"
FOR %%e IN ("%sourcedir%\*.mp4") DO (
SET "fname=%%~ne"
SET "fname=!fname:_=,!"
SET "fname=!fname: =#!"
FOR %%y IN (!fname!) DO SET "fdate=!ftime!"&SET "ftime=!fmsec!"&SET "fmsec=!flocation!"&SET "flocation=%%y"
ECHO MD "!flocation:#= !\!fdate!"
ECHO MOVE "%%e" "!flocation:#= !\!fdate!\"
)
GOTO :EOF
Always verify against a test directory before applying to real data.
The required MD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO MD to MD to actually create the directories. Append 2>nul to suppress error messages (eg. when the directory already exists)
The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)
Relatively easy to follow. Foe each filename, take the name part. replace each _ with , and each Space with #
The result in fname would thus be a comma-separated list of elements, so process each one, cascading them through the variables.
Finally, replace the # with Space when creating the subdirectory and filename.
Note that the switch between Space and # is intended to take care of places like New York and Los Angeles
== Supplemental == in response to comment:
FOR %%e IN ("%sourcedir%\*.mp4") DO (
SET "fname=%%~ne"
SET "fname=!fname:_=,!"
SET "fname=!fname: =#!"
FOR %%y IN (!fname!) DO SET "fdate=!ftime!"&SET "ftime=!fmsec!"&SET "fmsec=!flocation!"&SET "flocation=%%y"
SET "fname=!fname:,=_!"
CALL SET "fname=%%fname:_!fdate!_!Ftime!_!fmsec!_!flocation!=%%
ECHO MD "!flocation:#= !\!fname!\!fdate!"
ECHO MOVE "%%e" "!flocation:#= !\!fname!\!fdate!\"
ECHO MD "!flocation:#= !\!fname:_= !\!fdate!"
ECHO MOVE "%%e" "!flocation:#= !\!fname:_= !\!fdate!\"
)
After locating the required parts, replace each , with _ in fname, then replace the part from the date onwards with nothing, yielding the activity in fname.
Two sets of reports generated now - one for activity with underscores, the second with spaces.

A bat file to recursively traverse and find certain file type

I am trying to recursively traverse through a directory and find exe files in it.
This is the folder structure
C:\MyCave\iso\SDG\cmpdir
---------------\FolderA
------------------abc.txt
---------------\FolderB
------------------def.exe
---------------\FolderC
------------------ghi.dll
The below is my bat sinppet.
set f="C:\MyCave\iso\SDG\cmpdir\test-recur"
for /r %%f in ("*.exe") do if exist %%f echo %%f
Though it is working and listing out exe files in C:\MyCave\iso\SDG\cmpdir, but I want to list exe files present only in C:\MyCave\iso\SDG\cmpdir\test-recur directory.
This is my current output.
C:\MyCave\iso\SDG\cmpdir>exeRecur.bat
C:\MyCave\iso\SDG\cmpdir>set f="C:\MyCave\iso\SDG\cmpdir\test-recur"
C:\MyCave\iso\SDG\cmpdir>for /R %f in ("*.exe") do if exist %f echo %f
C:\MyCave\iso\SDG\cmpdir>if exist C:\MyCave\iso\SDG\cmpdir\cp.exe echo C:\MyCave\iso\SDG\cmpdir\cp.exe
C:\MyCave\iso\SDG\cmpdir\cp.exe
C:\MyCave\iso\SDG\cmpdir>if exist C:\MyCave\iso\SDG\cmpdir\dirB\Abcd.exe echo C:\MyCave\iso\SDG\cmpdir\dirB\Abcd.exe
C:\MyCave\iso\SDG\cmpdir\dirB\Abcd.exe
C:\MyCave\iso\SDG\cmpdir>if exist C:\MyCave\iso\SDG\cmpdir\test-recur\2\def.exe echo C:\MyCave\iso\SDG\cmpdir\test-recur\2\def.exe
C:\MyCave\iso\SDG\cmpdir\test-recur\2\def.exe
C:\MyCave\iso\SDG\cmpdir>if exist C:\MyCave\iso\SDG\cmpdir\wspace\defg.exe echo C:\MyCave\iso\SDG\cmpdir\wspace\defg.exe
C:\MyCave\iso\SDG\cmpdir\wspace\defg.exe
I know there is a small mistake somewhere. Requesting your help in solving it.
Based on Gerhard's and sst's answer I have modified my script like this.
set rootFolder=%1
set destFolder=%2
for /r %rootFolder% %%f in ("*.exe") do if exist %%f move %%f %destFolder%
I am passing command line arguments to bat like this...
exeRecur.bat "C:\MyCave\iso\SDG\cmpdir\test-recur" "C:\MyCave\iso\SDG\cmpdir\dirA"
Like this hard-coding of paths is avoided.
Regards
Ok, so you are setting a variable:
set f="C:\MyCave\iso\SDG\cmpdir\test-recur"
Firstly, it is bad practice to set single char variables, but more importantly is the way you wrap it with double quotes. It should be from the beginning of the variable to the end of the value.
set "f=C:\MyCave\iso\SDG\cmpdir\test-recur"
The real problem though is that you never tell the for /r loop where to recursively do the search.
set "mypath=C:\MyCave\iso\SDG\cmpdir\test-recur"
for /r "%mypath%" %%f in (*.exe) do echo %%~f
You forgot to specify the 'root path' argument of FOR /R, So it begins the enumeration from the current directory.
You should use a different name for your environment variable to avoid confusion with the FOR's parameter %%f.
set "RootFolder=C:\MyCave\iso\SDG\cmpdir\test-recur"
for /r "%RootFolder%" %%f in (*.exe) do echo %%f
You should also use the extended syntax of the set command: set "var=value" and use "%var%" wherever quoting is needed.
Since you are using the wildcard *.exe, There is no need for if exist in FOR /R as it will only enumerate the existing files.

Splitting file path in batch using for loop and variable substitution

I have searched around for quite a while without any luck in getting my script working. I feel like I'm pretty close, but need a little help. I am attempting to use a FOR loop to recursively scan "srcdir" (set at the beginning of my script), then once the loop returns files/paths (%%f), then I can substitute part of the file path with something else (eg; C:\rootpath\src for C:\rootpath\des).
I am able to do something just like this by using a script like this one:
set subdir=C:\rootpath\src
set subdir=%subdir:src=des%
echo %subdir%
However, what makes this difficult is that the root path of my "srcdir" may change (eg; C:\roothpath) and everything recursively after the "srcdir" may change (eg. anything after folder "src" in C:\rootpath\src). The only constant paths the folder src and the folder des (located in the same directory where I am running my batch file from).
So, by using the same technique in the previous example, I want to use a FOR loop to recursively find the full path of the files in "srcdir" (%%f) and substitute the folder "src" with the folder "des" in the path string. Therefore, I am trying to set "%%f" as a variable (subdir) and replace the folders using variable substitution.
Here is my current non-working script:
set srcdir=C:\rootpath\src
for /r "%srcdir%" %%f in (*.txt) do (
set subdir=%%f
set subdir=%subdir:src=des%
echo %subdir%
)
Any help would be greatly appreciated! Thanks!
You need to enable delayed expansion since you are assigning and reading variables within a block of code like a for loop:
setlocal EnableDelayedExpansion
set "srcdir=C:\rootpath\src"
for /R "%srcdir%" %%F in ("*.txt") do (
set "subdir=%%~fF"
set "subdir=!subdir:\src\=\des\!"
echo(!subdir!
)
endlocal
The setlocal EnableDelayedExpansion command enables delayed expansion; it also localises the environment, meaning that changes to environment variables are available only before endlocal is executed or the batch file is terminated.
To actually use delayed expansion, you need to replace the percent signs by exclamation marks, so %subdir% becomes !subdir!.

batch script variable unset in for loop has no effect

Below is my script. I am trying to look into folders one level below and pick out only those folders, hence the ~-9 which extracts the last 9 chars from the path. But the set var= does not unset the variable because the output comes back with the same folder name repeated # times. Also batch doesn't allow me to do this extract trick directly on %%i, hence the need for the local variable.
How do I clear this variable so that it takes the new value in the next iteration?
#echo off
for /d %%i in (%1\*) do (
set var=%%i
echo %var:~-9%
set "var="
)
http://judago.webs.com/variablecatches.htm has an explanation for my problem. The magic lines were setlocal enabledelayedexpansion and calling var as echo !var:~-9!. ! vs % ...wow! cmd still amazes me.
You found the source of your problem, as well as the solution - delayed expansion.
But using FOR while delayed expansion is enabled can cause problems if any of the filenames contain the ! character. The expansion of the for variable %%i will be corrupted if the value contains ! and delayed expansion is enabled. This is not a frequent problem, but it happens.
The solution is to toggle delayed expansion on and off within the loop
#echo off
setlocal disableDelayedExpansion
for /d %%i in (%1\*) do (
set var=%%i
setlocal enableDelayedExpansion
echo !var:~-9!
endlocal
)
I'm also wondering what you mean by "I am trying to look into folders one level below and pick out only those folders, hence the ~-9 which extracts the last 9 chars from the path". I suspect your are trying to get the name of the child folder, without the leading path information. If that is so, then using the substring operation is not a good solution because the length of folder names varies.
There is a very simple method to get the name of the folder without the leading path info:
for /d %%i in (%1\*) do echo %%~nxi

Loop over folder string and parse out last folder name

I need to grab the folder name of a currently executing batch file. I have been trying to loop over the current directory using the following syntax (which is wrong at present):
set mydir = %~p0
for /F "delims=\" %i IN (%mydir%) DO #echo %i
Couple of issues in that I cannot seem to pass the 'mydir' variable value in as the search string. It only seems to work if I pass in commands; I have the syntax wrong and cannot work out why.
My thinking was to loop over the folder string with a '\' delimiter but this is causing problems too. If I set a variable on each loop then the last value set will be the current folder name. For example, given the following path:
C:\Folder1\Folder2\Folder3\Archive.bat
I would expect to parse out the value 'Folder3'.
I need to parse that value out as its name will be part of another folder I am going to create further down in the batch file.
Many thanks if anyone can help. I may be barking up the wrong tree completely so any other approaches would be greatly received also.
After struggling with some of these suggestions, I found an successfully used the following 1 liner (in windows 2008)
for %%a in (!FullPath!) do set LastFolder=%%~nxa
You were pretty close to it :) This should work:
#echo OFF
set mydir="%~p0"
SET mydir=%mydir:\=;%
for /F "tokens=* delims=;" %%i IN (%mydir%) DO call :LAST_FOLDER %%i
goto :EOF
:LAST_FOLDER
if "%1"=="" (
#echo %LAST%
goto :EOF
)
set LAST=%1
SHIFT
goto :LAST_FOLDER
For some reason the for command doesn't like '\' as a delimiter, so I converted all '\' to ';' first (SET mydir=%mydir:\=;%)
I found this old thread when I was looking to find the last segment of the current directory.
The previous writers answers lead me to the following:
FOR /D %%I IN ("%CD%") DO SET _LAST_SEGMENT_=%%~nxI
ECHO Last segment = "%_LAST_SEGMENT_%"
As previous have explained, don't forget to put quotes around any paths create with %_LAST_SEGMENT_% (just as I did with %CD% in my example).
Hope this helps someone...
This question's a little old, but I've looked for a solution more than once so here's a completely new take on it that I've just put together.
The trick is that we take the desired path, back up one level to create a folder mask for substitution and then replace the folder mask with nothing.
To test it, simple copy and paste into a command script (.cmd) in any directory, then run it. It will spit out only the deepest directory you're currently in.
Notes:
Replace %~dp0 with whatever path you like (as it is, it will return the deepest folder the batch file is run from. This is not the same as %cd%.)
When specifying the 'pathtofind' variable ensure there are no quotes e.g. c:\some path and not "c:\some path".
The original idea for folder masking is mine
Spaces in the path are no problem
Folder depth is not a problem
It was made possible by the genius of this batch scripting tip http://www.dostips.com/DtCodeBatchFiles.php#Batch.FindAndReplace
Hope this helps someone else.
#echo off
set pathtofind=%~dp0
if not exist %pathtofind% echo Path does not exist&pause>nul&goto :eof
cd /d %pathtofind%
set path1=%cd%
cd ..
set path2=%cd%
call set "path3=%%path1:%path2%\=%%"
echo %path3%
pause>nul
3 lines of script gets the result...
Found 2 additional ways to accomplish the goal, and unlike the other answers to this question, it requires no batch "functions", no delayed expansion, and also does not have the limitation that Tim Peel's answer has with directory deepness :
#echo off
SET CDIR=%~p0
SET CDIR=%CDIR:~1,-1%
SET CDIR=%CDIR:\=,%
SET CDIR=%CDIR: =#%
FOR %%a IN (%CDIR%) DO SET "CNAME=%%a"
ECHO Current directory path: %CDIR%
SET CNAME=%CNAME:#= %
ECHO Current directory name: %CNAME%
pause
REVISION: after my new revsion, here is an example output:
Current directory path: Documents#and#Settings,username,.sqldeveloper,tmp,my_folder,MY.again
Current directory name: MY.again
Press any key to continue . . .
This means that the script doesn't handle '#' or ',' in a folder name but can be adjusted to do so.
ADDENDUM: After asking someone in the dostips forum, found an even easier way to do it:
#echo off
SET "CDIR=%~dp0"
:: for loop requires removing trailing backslash from %~dp0 output
SET "CDIR=%CDIR:~0,-1%"
FOR %%i IN ("%CDIR%") DO SET "PARENTFOLDERNAME=%%~nxi"
ECHO Parent folder: %PARENTFOLDERNAME%
ECHO Full path: %~dp0
pause>nul
To return to the original poster's issue:
For example, given the following path:
C:\Folder1\Folder2\Folder3\Archive.bat
I would expect to parse out the value 'Folder3'.
The simple solution for that is:
for /D %%I in ("C:\Folder1\Folder2\Folder3\Archive.bat\..") do echo parentdir=%%~nxI
will give 'Folder3'. The file/path does not need to exist. Of course, .... for the parent's parent dir, or ...... for the one above that (and so on) work too.
Slight alteration for if any of the folders have spaces in their names - replace space to ':' before and after operation:
set mydir="%~p0"
set mydir=%mydir:\=;%
set mydir=%mydir: =:%
for /F "tokens=* delims=;" %%i IN (%mydir%) DO call :LAST_FOLDER %%i
goto :EOF
:LAST_FOLDER
if "%1"=="" (
set LAST=%LAST::= %
goto :EOF
)
set LAST=%1
SHIFT
goto :LAST_FOLDER
Sheesh guys, what a mess. This is pretty easy, and it's faster to do this in memory without CD.
This gets the last two directories of a path. Modify it as required to get the last tokens of any line. My original code I based this on has more complexity for my own purposes.
Fyi, this probably doesn't allow paths with exclamation marks since I'm using enabledelayedexpansion, but that could be fixed.
It also won't work on a plain drive root. This could be averted in a number of ways. Check what the input path ends with, or a counter, or modifying the token and check behaviour, etc.
#echo off&setlocal enableextensions,enabledelayedexpansion
call :l_truncpath "C:\Windows\temp"
----------
:l_truncpath
set "_pathtail=%~1"
:l_truncpathloop
for /f "delims=\ tokens=1*" %%x in ("!_pathtail!") do (
if "%%y"=="" (
set "_result=!_path!\!_pathtail!"
echo:!_result!
exit/b
)
set "_path=%%x"
set "_pathtail=%%y"
)
goto l_truncpathloop
I modified answer given by #Jonathan, since it did not work for me in a batch file, but this below does work, and also supports folders with spaces in it.:
for %%a in ("%CD%") do set LastFolder=%%~nxa
echo %LastFolder%
This takes the current directory and echoes the last, deepest folder, as in below example, if the folder is this:
C:\Users\SuperPDX\OneDrive\Desktop Environment\
The batch code echoes this: Desktop Environment
In batch files in the FOR command you'll need to prepend %whatever with an extra % (e.g. %%whatever).
'echo %~p0' will print the currently directory of the batch file.
This is what we had in the end (little bit more crude and can only go so deep :)
#echo off
for /f "tokens=1-10 delims=\" %%A in ('echo %~p0') do (
if NOT .%%A==. set new=%%A
if NOT .%%B==. set new=%%B
if NOT .%%C==. set new=%%C
if NOT .%%D==. set new=%%D
if NOT .%%E==. set new=%%E
if NOT .%%F==. set new=%%F
if NOT .%%G==. set new=%%G
if NOT .%%H==. set new=%%H
if NOT .%%I==. set new=%%I
if NOT .%%J==. set new=%%J
)
#echo %new%
I don't know if it's the version of windows I'm on (win2k3), but the FOR loop isn't giving me anything useful for trying to iterate through a single string.
According to my observation (and the FOR /? info) you get one iteration for each line of input to FOR, and there is no way to change this to iterate within a line. You can break into multiple tokens for a given line, but it is only one invocation of the FOR loop body.
I do think the CALL :LABEL approach in these answers does a great job. Something I didn't know until looking at this was that ";" and "," are both recognized as argument separators. So once you replace backslashes with semicolons, you can call your label and iterate through with SHIFT.
So working off of what is posted by others here, I have the below solution. Instead of grabbing the last folder name, I actually wanted to find everything up until some known directory name.. this is what is implemented below.
#echo off
if "%1"=="" goto :USAGE
set FULLPATH=%~f1
set STOPDIR=%2
set PATHROOT=
:: Replace backslashes with semicolons
set FULLPATH=%FULLPATH:\=;%
:: Iterate through path (the semicolons cause each dir name to be a new argument)
call :LOOP %FULLPATH%
goto :EOF
:LOOP
::Exit loop if reached the end of the path, or the stop dir
if "%1"=="" (goto :EOF)
if "%1"=="%STOPDIR%" (goto :EOF)
::If this is the first segment of the path, set value directly. Else append.
if not defined PATHROOT (set PATHROOT=%1) else (set PATHROOT=%PATHROOT%\%1)
::shift the arguments - the next path segment becomes %i
SHIFT
goto :LOOP
:USAGE
echo Usage:
echo %~0 ^<full path to parse^> ^<dir name to stop at^>
echo E.g. for a command:
echo %~0 c:\root1\child1\child2 child2
echo The value of c:\root1\child1 would be assigned to env variable PATHROOT
Unfortunatelly, this is working great only when put on some depth but have problems with being on the very top of the mountain... Putting this program into "C:\Windows" e.g. will result with... "C:\Windows", not expected "Windows". Still great job, and still damage can be repaired. My approach:
#echo off
set pathtofind=%~dp0
if not exist %pathtofind% echo Path does not exist&pause>nul&goto :eof
cd /d %pathtofind%
set path1=%cd%
cd ..
set path2=%cd%
set path4=%~dp1
call set "path3=%%path1:%path2%\=%%"
call set "path5=%%path3:%path4%*\=%%"
echo %path5%
pause>nul
And it's working just fine for me now, thanks for the idea, I was looking for something like that for some time.

Resources