Batch rename to numeric names with leading zeroes - batch-file

So I'm trying to write a batch that renames <randomname>.EPL to fbXYZ.EPL (XYZ - number with leading zeroes)
So something like bes_rush.EPL should turn into fb001.EPL and other files should get renamed too (fb002.EPL, fb003.EPL, etc.)
Here's what I have so far
setlocal ENABLEDELAYEDEXPANSION
set/a fileNum = 0
set fileNum=0000%fileNum%
set fileNum=%fileNum:~-3%
for %%f in (*.EPL) do (
ren %%~nf%%~xf fb!fileNum!%%~xf
set/a fileNum += 1
)
I can make it rename numerically and it actually works but I can't add the leading zeroes at all,
all my attempts lead to it renaming only one file and leaving the rest

The following would be my suggestion:
Please note however, that the example does not cater for any existing filenames in the directory which already match fb<num><num><num>.EPL, so those will also be renamed with possible new numbers too.
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "SourceDir=."
Set "FileGlob=*.EPL"
Set "Prefix=fb"
Set "FileNum=1000"
For /F Delims^= %%G In ('(Set PATHEXT^=^) ^& "%SystemRoot%\System32\where.exe"
/F "%SourceDir%":"%FileGlob%" 2^> NUL') Do (Set /A FileNum += 1
SetLocal EnableDelayedExpansion & Ren %%G "%Prefix%!FileNum:~-3!%%~xG"
EndLocal)
Insert your required source directory between the = and closing " on line 3, (I've used . for the current directory, as per your example, but you can use an absolute or other relative path as needed).
Insert your required file glob prefix string between the = and closing " on line 4, (I've used *.EPL to match your example).
Insert your required file name prefix string between the = and closing " on line 5, (I've used fb to match your example).
I've used 1000 as your starting file number, because Set /A uses integers, and sees multiple concurrent 0's as just 0. Adding the 1 will prevent that, and will be omitted, when the varible expansion, uses just the last three characters. I also adjusted it so that the file numbering begins with fb001.EPL, to match your question parameters, (your code was beginning the sequence with fb000.EPL).
I've enabled delayed expansion within the loop, because it is required, when both modifying and using a variable within the same parenthesized code block. It shouldn't normally be enabled for the entire script, because filenames and strings containing ! characters can be affected, (those characters will be omitted).
I've also gone 'belt and braces' with the file selection, by using where.exe. This utility is not affected by Windows using 8.3 naming, which, whilst it may not be an issue with your provided example, will match exactly extensions .EPL, not those which begin with .EPL. Standard For loops, (which you used), and the more commonly used Dir command are both affected by 8.3 naming.
For additional robustness, I've used the full path to where.exe with the system environment variable %SystemRoot%, to prevent reliance on the %Path% variable which is often broken, by its incorrect or accidental end user modification.
As a direct resolution to your own code without modification to the majority of it:
setlocal ENABLEDELAYEDEXPANSION
set /a fileNum = 1
set fileNum=1000%fileNum%
for %%f in (*.EPL) do (
ren "%%f" fb!fileNum:~-3!%%~xf
set /a fileNum += 1
)
I must add however, that's because your example code is using a standard for loop, you may have issues. The problem is that a your loop will pass the first file through to the do portion whilst it is still parsing the others under the all encompassing * glob. This means that fb001.EPL will be put back into the list for parsing, and could be picked up again for processing another time, this could continue for other files too!

#ECHO Off
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following settings for the source directory, destination directory, target directory,
rem batch directory, filenames, output filename and temporary filename [if shown] are names
rem that I use for testing and deliberately include names which include 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\t w o"
SET /a filenumber=1001
FOR /f "delims=" %%b IN ('dir /b /a-d "%sourcedir%\*.epl" ' ) DO (
SET "originalfilename=%%~nb"
SET "notnumber=Y"
rem do not rename files "fb999"
IF /i "!originalfilename:~0,2!!originalfilename:~5!" == "fb" IF "!originalfilename:~4!" neq "" CALL :isnum !originalfilename:~2,3!
IF defined notnumber (
CALL :nextvalidnum
IF NOT DEFINED filenumber ECHO Fail - no available fb999&GOTO :EOF
REN "%sourcedir%\%%b" "fb!filenumber:~-3!.epl"
)
)
GOTO :EOF
:: Determine whether %1 is purely numeric
:isnum
SET "notnumber=9%1"
FOR /l %%z IN (0,1,9) DO CALL SET "notnumber=%%notnumber:%%z=%%"
GOTO :eof
:: set filenumber to next valid fb(1)999 or empty if not available
:nextvalidnum
IF %filenumber% gtr 1999 SET "filenumber="&GOTO :EOF
IF EXIST "%sourcedir%\fb%filenumber:~-3%.epl" SET /a filenumber+=1&GOTO nextvalidnum
IF %filenumber% gtr 1999 SET "filenumber="
GOTO :eof
Setting filenumber to 1001 initially, with the expectation of using the last 4 characters as the new fb999 name.
Read each .epl name, process only those files not named fb999. This is accomplished by remembering that the first character in a string is "character 0";detecting the first 2 characters of the name are fb, there are no characters beyond the fifth, the fourth character in the filename exists and the three characters starting at character 2 are all numeric. The routine "isnum" sets notnumber to undefined if the value provided as %1 is all-numeric and defined otherwise.
If notnumber is defined, the filename is to be processed. Detect the next valid number by incrementing filenumber if the file fb+last 3 characters of "filenumber".epl exists. If filenumber exceeds 1999 then there are no available numbers. so clear filenumber and use it as a flag to show a message and exit.

Related

How to get a text file name in located in current directory to a variable with a batch file

I have a .txt file of which name is used as a reference with the format AS2204-1 according to the naming scheme ASyymm-sn with yy being the current year without century, mm being the current month and sn being a serial number with the current month. I try to get the current file name and increment the serial number by 1 on year and month unchanged, copy the new file name to the clipboard and then rename the text file.
This is my code so far:
#echo off
set yy=%date:~12,2%
set mm=%date:~4,2%
set /a sn=0
for %%a in ('dir *.txt') do (set filename=%%a)
set Fmm=%filename:~5,2%
if %Fmm%==%mm% (set /a sn=sn+1) else (set /a sn=1)
echo AS%yy%%mm%^-%sn% |clip
ren "%filename%.txt" "AS%yy%%mm%^-%sn%.txt"
I can't get the file name assigned to the variable filename.
What is wrong with my code and what would be a correct FOR loop?
#echo off
setlocal
pushd "?:\whichever\directory\contains your\target files"
set "yy=%date:~12,2%"
set "mm=%date:~4,2%"
set "filename="
for /f %%a in ('dir /b /a-d /od AS%yy%%mm%-*.txt 2^>nul') do (set "filename=%%~na")
rem use this line if you use suppressed leading zero
if defined filename (set /a "sn=%filename:*-=%+1") else (set /a sn=1)
rem use this line if you use 2-digit
if defined filename (set /a "sn=1%filename:~-2%+1") else (set /a sn=101)
IF %sn% gtr 100 SET "sn=%sn:~-2%"
echo AS%yy%%mm%-%sn%
ren "whatever.txt" "AS%yy%%mm%-%sn%.txt"
POPD
goto :eof
The setlocal ensures that the environment changes (new variables) are removed when the batch terminates.
pushd switches to the specified directory. I've no idea what your directory is.
The syntax SET "var=value" (where value may be empty; in which case var becomes undefined) is used to ensure that any stray trailing spaces are NOT included in the value assigned.
The for /f assigns each line of the dir "report" to %%a in turn. In consequence, filename will be set to the name part only (%%~na) of the filename found. dir /b produces a list of files to be processed, in basic form (filename only). /a-d excludes directorynames, /od produces the list in date order. The filemask AS%yy%%mm%-*.txt asks for all .txt files which match AS+thecurrentyearnumber+thecurrentmonthnumber+-, so it's not necessary to check the year/month part. The 2^>nul suppresses error messages should no filename matching the mask be found.
If a filename was found, filename will be defined. That's why it's set to nothing (which undefines it) before the for loop.
The filenames must be produced in date order because ASyymm-10 will sort before ASyymm-2 in the default name-order list.
Well, I've no idea whether you use 1- or 2-digit serial numbers - the processing is different.
If you use the first if defined... then sn is set to 1 more than (that part of filename that exists when all characters up to the - are removed) or 1 if no prior file was found.
If you use the second if defined... then sn is set to 1 more than (1+the last 2 characters of filename) or 101 if no prior file was found, so ASyymm-07 would produce sn=108. This is necessary because batch interprets a numeric string beginning 0 as OCTAL in calculations.
if the result is >100, then use the last 2 characters of sn.
I've just echoed the required string to the screen. Echo to the clipboard if you wish.
Well - the rename. It's unclear what the file to be renamed to the calculated name is, so I've used whatever. Possibly it's a file named ASyymm.txt - if so, replace whatever with AS%yy%%mm%.
The popd returns to the original directory.
Documentation can be obtained by executing commandname /? for most commands but it can be a little cryptic at times. Simply search SO for examples.
There could be used the following batch file for this task:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for /F "tokens=1,2 delims=/" %%G in ('%SystemRoot%\System32\robocopy.exe "%SystemDrive%\|" . /NJH') do set "YearMonth=%%G%%H" & goto SearchFile
:SearchFile
set "SerialNumber=0"
set "FileNameScheme=AS%YearMonth:~2%-"
for %%G in ("%FileNameScheme%*.txt") do for /F "tokens=2 delims=-" %%H in ("%%~nG") do if %%H GTR !SerialNumber! (set "SerialNumber=%%H" & set "FileName=%%G")
set /A SerialNumber+=1
echo %FileNameScheme%%SerialNumber%| %SystemRoot%\System32\clip.exe
if defined FileName (
ren "%FileName%" "%FileNameScheme%%SerialNumber%.txt"
) else (
rem del /Q "AS????-*.txt" >nul 2>nul
echo %FileNameScheme%%SerialNumber%>"%FileNameScheme%%SerialNumber%.txt"
)
endlocal
The first two lines define the required execution environment completely which is:
command echo mode turned off
command extensions enabled
delayed variable expansion enabled
Please read the chapter Usage of ROBOCOPY to get current date/time in my answer on Time is set incorrectly after midnight for an explanation of the FOR command line which uses ROBOCOPY to get current year and month independent on which country (region) is configured for the used account which determines the date format of the dynamic variable DATE.
The environment variable SerialNumber is defined first with default value 0.
The environment variable FileNameScheme is defined with AS at beginning, the current year without the century with always two digits, the current month with always two digits and - which results today in the string AS2204-.
The command FOR is used to search in current directory (can by any directory) for one or more non-hidden files matching the wildcard pattern AS2204-*.txt.
It is unclear how my files can be in the current directory matching the wildcard pattern AS????-*.txt. Therefore the code is written to work also with multiple files matching this wildcard pattern in the current directory.
For each file name matching the wildcard pattern with current year and month already included one more FOR loop is used to get the string between the hyphen and the file extension which is hopefully always a number in range 0 to 2147483647 without leading zeros.
This number string is compared with the current value of the environment variable SerialNumber. Delayed variable expansion must be enabled because of this number comparison. If the number of the current file is greater the current serial number, the greater number is used further as serial number and the name of the current file is assigned to the environment variable FileName.
Please note that GTR does not work on number in file has a leading zero as in this case the number would be interpreted as an octal instead of a decimal number which means 08 and 09 would be interpreted as invalid octal numbers with using in this case value 0 for the number comparison.
The default value 0 or the greatest number of files matching AS2204-*.txt is next incremented by one using an arithmetic expression.
The file name with the incremented serial number is copied without file extension to the clipboard for whatever purpose.
If there was really found a file with current year and month in its name, this file is now renamed to contain the serial number incremented by one. Otherwise a new file is created with the new file name and containing the file name without file extension as data, for example AS2204-1.
There is a line commented out with command REM which would delete all files matching the wildcard pattern AS????-*.txt, except hidden files ignored by command DEL by default and read-only files which are not deleted by command DEL by default and matching files being currently opened by an application. It is not clear what to do with the other file(s) matching this pattern from former month(s).
The batch file code could be optimized to following command lines if there is either no or always just one file matching the wildcard pattern AS????-*.txt in the current directory:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "tokens=1,2 delims=/" %%G in ('%SystemRoot%\System32\robocopy.exe "%SystemDrive%\|" . /NJH') do set "YearMonth=%%G%%H" & goto SearchFile
:SearchFile
set "SerialNumber=0"
set "FileNameScheme=AS%YearMonth:~2%-"
for %%G in ("%FileNameScheme%*.txt") do for /F "tokens=2 delims=-" %%H in ("%%~nG") do set "SerialNumber=%%H"
set /A SerialNumber+=1
echo %FileNameScheme%%SerialNumber%| %SystemRoot%\System32\clip.exe
if exist "AS????-*.txt" (
ren "AS????-*.txt" "%FileNameScheme%%SerialNumber%.txt"
) else (
echo %FileNameScheme%%SerialNumber%>"%FileNameScheme%%SerialNumber%.txt"
)
endlocal
Delayed variable expansion is no longer needed which makes the processing of the batch file a very little bit faster. Serial numbers with one or more leading zeros would be still a problem because of command SET on evaluation of an arithmetic expression converts the number string also to an integer with interpreting the number as octal number on having the character 0 at beginning of the number string.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
del /?
echo /?
endlocal /?
for /?
goto /?
if /?
rem /?
robocopy /?
set /?
setlocal /?
See also:
Microsoft documentation about Using command redirection operators
Single line with multiple commands using Windows batch file for an explanation of the unconditional command operator &.
first of all thanks for all the answers!
I did it that way - it works fine until the renaming part that just doesn't work - any idea why?
set "yy=%date:~12,2%"
set "mm=%date:~4,2%"
set "filename=\root\*.txt"
for %%A in (%filename%) do (set x=%%~nA)
set Fmm=%x:~4,2%
set sn=%x:7.1%
if %Fmm%==%mm% (set /a sn=sn+1) else (set /a sn=1)
echo AS%yy%%mm%^-%sn% |clip
set newfilename=AS%yy%%mm%-%sn%.txt
set oldfilename=%x%.txt
ren %oldfilename% %newfilename%

Why is asterisk special in CMD character replacing syntax? [duplicate]

I'm trying to remove an asterisk from an environmental variable string, but can't seem to do it.
I'm creating an m3u file based around search strings, so for instance I if I want to make an m3u file containing every song with the word love in it, I would enter:
m3u *Love*
And m3u.bat would create the file:
xLovex.m3u
But the regular method of replacing characters does not work with an asterisk. (Though I don't have that problem with the question mark.)
set nam=%nam:*=x%.m3u
Instead creates the filename
x.m3u
The easy answer is no.
The problem that you're encountering stems from the fact that the asterisk * is a special character when used with the SET search and replace method. It matches multiple characters in a limited, but still useful, way. You can learn about that here.
The hard answer is Yes!
I will provide you with two solutions. One an incomplete solution but elegent,
the other complete and inelegent.
Both methods will search for * and replace it with an x.
Both methods will both search and modify the following string:
*love*
The first method that comes to mind is using a 'FOR /L' statement, and requires that you know how many characters long the environmental variable is.
::Major Edit::
I thought I knew the various maximum size strings of environmental variables, but dbenham has taken me to school, shown me a kick-in-the-behind length function, and in the mean time completely reversed my opinions of the two solutions I'm presenting.
Other than for the Windows 95/98/ME limitation of a 256 Character maximum environmental variable size. It seems that all versions of Windows using CMD.EXE have a limitation of 8,192 characters, well below what the documentation suggests.
Both versions require delayed environmental variable expansion, but for two different reasons. One because I'm operating inside a FOR statement. The other because you cannot put a % pair inside another % pair because the command processor matches the second % that it encounters to the first one it encounters, but we need to use a variable inside another variable expression. (You'll see.)
This solution uses the strLen function (in line 3) from DosTips.com that can be found Here. Just slap it into a file called strLen.bat and be amazed at it's speed!
Solution 1: (FOR /L Solution) :: Preferred Solution ::
setlocal ENABLEDELAYEDEXPANSION
set nam=*love*
rem calling strLen
call :strLen nam len
for /l %%x in (0,1,%len%) do if not "!nam:~%%x,1!"=="" if "!nam:~%%x,1!"=="*" (
set /a plusone=%%x+1
for /l %%y in (!plusone!, 1, !plusone!) do (
set nam=!nam:~0,%%x!x!nam:~%%y!
)
)
echo %nam%
ENDLOCAL
I think this is a quick and elegant solution It could be sped up by adding the contents of strLen.bat to the routine, but I wanted no confusion as to the author.
If you, for some reason, do not wish to use strLen, then the next quickest method would probably use a GOTO loop.
Solution 2: (Goto Solution)
setlocal ENABLEDELAYEDEXPANSION
set nam=*love*
set num=0
:loop
set /a plusone=%num%+1
if "!nam:~%num%,1!"=="*" set nam=!nam:~0,%num%!x!nam:~%plusone%!
set /a num=%num%+1
if not "!nam:~%num%,1!"=="" goto :loop
echo %nam%
EndLocal
Special thanks to dbenham for pointing out the strLen function. It works faster than any batch based function has a right to!
Although there were already some very good and robust ways explained here, I'd still like to add another option for the sake of completion.
It's not as good as the other options but I personally use it in some cases where I'd like to keep the code clean and where I know that it will suffice:
The way it works is by using for /f's delims to cut the string into two parts, which are then put back together, getting rid of the * in the process:
for /f "tokens=1,* delims=*" %%a in ("a*b") do (set string=%%a%%b)
>>> string=ab
Obviously, the downside to this is that it can only be used to remove one *.
To remove more, we can either just use more tokens...
for /f "tokens=1-3,* delims=*" %%a in ("a*b*c*d") do (set string=%%a%%b%%c%%d)
>>> string=abcd
... or we can put the first line in a for /l-loop:
setlocal enableDelayedExpansion
set string=a*b*c*d
for /l %%a in (1, 1, 3) do (
for /f "tokens=1,* delims=*" %%b in ("!string!") do (set string=%%b%%c)
)
>>> string=abcd
Another thing to note is that you can define more than one character in delims, and they will all be removed at once:
for /f "tokens=1,* delims=+-*/" %%a in ("a*-/+b") do (set string=%%a%%b)
>>> string=ab
Another solution to the stated problem is to use a PowerShell replace command within your batch script.
set var=*Love*
echo %var%>var.txt | powershell -command "((get-content var.txt) -replace '[\x2A]','x') -replace '.{1}$' | set-content var.txt"
set /p var=<var.txt
set var=%var%.m3u
echo %var%
In the above code, the second line
writes your string into a text file
calls a PowerShell command to get the contents of that file
replaces the * character with null
overwrites the text file with the new value
Once that is done, you read the value back into your variable.
To further explain the replace command, the first single quotes is what you are searching for. We are using square brackets to identify the * character as a hex character (\x2A is the hex value for *). After the comma, the second set of single quotes contains no value so that the searched object is removed. To prevent a space between xLovex and the .m3u, we have to use -replace '.{1}$' before writing the result to the text file.
Once you are done with the text file, enter a line to delete it.
if exist var.txt del var.txt
Here is an approach that does not walk through all characters of a string, but it uses a for /F loop to split the string at every occurrence of a (sequence of a) certain character. The actual functionality is packed into a sub-routine for easy reuse, so the main section of the following script just contains some code to test:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
::This is the main routine of the script holding code for test and demonstration:
rem // Definition of some sample text to test (note that `%%` becomes one literal `%`):
set "DATA=some text,"^&"&;0'#%%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_"
echo/
call :REPL_CHAR TEXT DATA "*" "?"
setlocal EnableDelayedExpansion
echo(In: !DATA!
echo(Out:!TEXT!
echo/
echo(In: !TEXT!
call :REPL_CHAR TEXT TEXT "=" "/"
echo(Out:!TEXT!
endlocal
endlocal
exit /B
:REPL_CHAR
::This function replaces in a string every occurrence of a sequence of a certain character
::by another character or a string. It even correctly handles the characters `*` and `=`.
:: USAGE:
:: call :REPL_CHAR ref_output_string ref_input_string val_search_char val_replace_char
:: PARAMETERS:
:: ref_output_string reference to (name of) variable to receive the resulting string;
:: ref_input_string reference to variable that holds the original string; if empty
:: (`""`), the variable referenced by `ref_output_string` is used;
:: val_search_char single character that is to be replaced;
:: val_replace_char character or string to replace every sequence of `val_search_char`
:: with; this may even be empty;
rem // Localise environment and detect whether delayed expansion is enabled (needed later):
setlocal & set "$NDX=!"
setlocal DisableDelayedExpansion
rem // Fetch arguments and verify them:
set "#RET=%~1" & if not defined #RET endlocal & endlocal & exit /B 2
set "#STR=%~2" & if not defined #STR set "#STR=%#RET%"
set "CHR=%~3"
if not defined CHR endlocal & endlocal & exit /B 1
set "RPL=%~4"
setlocal EnableDelayedExpansion
rem // Initialise several auxiliary variables:
set "TST=!%#STR%!" & set "CHR=!CHR:~,1!" & set "INS="
if "!CHR!"=="_" (set "BUF=#" & set "WRK=!TST!#") else (set "BUF=_" & set "WRK=!TST!_")
:REPL_CHAR_LOOP
rem // Check whether the end of the string has been reached:
if not defined TST set "BUF=!BUF:~1,-1!" & goto :REPL_CHAR_NEXT
rem // Split the string at the next sequence of search characters:
for /F tokens^=1*^ delims^=^%CHR%^ eol^=^%CHR% %%S in ("!BUF!!INS!!WRK!") do (
rem // Store the portions before and after the character sequence:
endlocal & set "BUF=%%S" & set "TST=%%T" & set "WRK=%%T" & setlocal EnableDelayedExpansion
)
rem // Loop back and find the next character sequence:
set "INS=!RPL!" & goto :REPL_CHAR_LOOP
:REPL_CHAR_NEXT
rem // Return the resulting string with all special characters properly handled:
if not defined $NDX if defined BUF set "BUF=!BUF:"=""!^"
if not defined $NDX if defined BUF set "BUF=!BUF:^=^^^^!"
if not defined $NDX if defined BUF set "BUF=%BUF:!=^^^!%" !
if not defined $NDX if defined BUF set "BUF=!BUF:""="!^"
for /F "delims=" %%S in (^""!BUF!"^") do endlocal & endlocal & endlocal & set "%#RET%=%%~S" !
exit /B
The input and output data of this script (let us call it repl_char_demo.bat) are:
>>> repl_char_demo.bat
In: some text,"&"&;0'#%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'#%~#`$:wild?card??.re<dir>=|+([{parens}])-^/equal==to=!_
In: some text,"&"&;0'#%~#`$:wild?card??.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'#%~#`$:wild?card??.re<dir>/|+([{parens}])-^/equal/to/!_
This is a script that uses for /L loops to walk through all characters of the string, to check each character against a predefined one and replaces it as specified. This method replaces every single matching character rather than sequences. Again the functionality is put into a sub-routine (the main section is dismissed this time):
:REPL_CHAR
::This function replaces in a string every occurrence of one certain character by another
::character or a string. It even correctly handles the characters `*` and `=`, as well as
::sequences of search characters so that every single one becomes replaced.
:: USAGE:
:: call :REPL_CHAR ref_output_string ref_input_string val_search_char val_replace_char
:: PARAMETERS:
:: ref_output_string reference to (name of) variable to receive the resulting string;
:: ref_input_string reference to variable that holds the original string; if empty
:: (`""`), the variable referenced by `ref_output_string` is used;
:: val_search_char single character that is to be replaced;
:: val_replace_char character or string to replace every single `val_search_char`
:: with; this may even be empty;
rem // Localise environment and detect whether delayed expansion is enabled (needed later):
setlocal & set "$NDX=!"
setlocal DisableDelayedExpansion
rem // Fetch arguments and verify them:
set "#RET=%~1" & if not defined #RET endlocal & endlocal & exit /B 2
set "#STR=%~2" & if not defined #STR set "#STR=%#RET%"
set "CHR=%~3"
if not defined CHR endlocal & endlocal & exit /B 1
set "RPL=%~4"
setlocal EnableDelayedExpansion
rem // Initialise several auxiliary variables:
set "WRK=!%#STR%!" & set "CHR=!CHR:~,1!" & set "BUF="
rem // Loop through all characters and check for match:
if defined WRK for /L %%J in (0,1,63) do for /L %%I in (0,1,127) do (
set /A "POS=%%J*64+%%I" & for %%P in (!POS!) do (
set "TST=!WRK:~%%P,1!" & if not defined TST goto :REPL_CHAR_QUIT
rem // Store character or replacement depending on whether there is a match:
if "!TST!"=="!CHR!" (set "BUF=!BUF!!RPL!") else (set "BUF=!BUF!!TST!")
)
)
:REPL_CHAR_QUIT
rem // Return the resulting string with all special characters properly handled:
if not defined $NDX if defined BUF set "BUF=!BUF:"=""!^"
if not defined $NDX if defined BUF set "BUF=!BUF:^=^^^^!"
if not defined $NDX if defined BUF set "BUF=%BUF:!=^^^!%" !
if not defined $NDX if defined BUF set "BUF=!BUF:""="!^"
for /F "delims=" %%S in (^""!BUF!"^") do endlocal & endlocal & endlocal & set "%#RET%=%%~S" !
exit /B
There are actually two nested for /L loops rather than a single one, both of which become broken as soon as the end of the string is reached, using the goto command. Breaking a for /L loop means that it completes iterating in the background although its body is no longer executed. Therefore, using a single loop takes much more time to finish after being broken rather than two nested ones.
The input and output data of this script (with the same main section as above) are:
>>> repl_char_demo.bat
In: some text,"&"&;0'#%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'#%~#`$:wild??card??.re<dir>=|+([{parens}])-^/equal==to=!_
In: some text,"&"&;0'#%~#`$:wild??card??.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'#%~#`$:wild??card??.re<dir>/|+([{parens}])-^/equal//to/!_
See this answer, and with set-ast.bat you'll want to put set-ast nam "x" in your file where needed.
set-ast takes the parameters <variable-to-modify> <string-to-replace-asterisks-with>

Batch Script that checks a directory for duplicate files

Trying to look through a file for example,
C:\Users\Admin\Desktop\Test\12345
C:\Users\Admin\Desktop\Test\45635
C:\Users\Admin\Desktop\Test\12345-2018-04-21
and create a subfolder named "12345" and will store the two files (12345 and 12345-2018-04-21) in subfolder "12345". and this will loop through the whole folder to make sure there is no copies
:: ---------------------------------------------------------------
:: - This is a program that searches for files with the same 5
:: digit number and puts them in a subfolder.
:: ---------------------------------------------------------------
#ECHO off
SETLOCAL
SET "sourcedir=C:\Users\Admin\Desktop\Test"
SET "destdir=C:\Users\Admin\Desktop\Test"
SET /a lastnum=200000
if "%first5%" equ "%lastnum%"(
SET "destdir=%destdir%\%first5%-sub"
md %destdir%
)
FOR /f "delims=" %%a IN (
'dir /b /a-d /on "%sourcedir%\*" '
) DO (
CALL :detect "%%a"
IF DEFINED dupnum (ECHO(MOVE "%sourcedir%\%%a" "%destdir%\")
)
GOTO end
:: Routine to detect whether the first 5 characters of the filename "%1"
:: are all numeric and if so, whether they match the previous 5-digit number
:: found.
:detect
SET "dupnum="
:: Get the first 5 characters of the filename; prefix with a `1`
SET "fullfilename=%~1"
SET "first5=1%fullfilename:~0,5%"
IF "%first5%" neq "%lastnum%" (
SET "lastnum=%first5%"
GOTO end
)
:: First 5 chars of this filename = first 5 of previous filename
:: Check to see whether numeric
SET /a dummy=first5 + 0
IF "%dummy%" neq "%first5%" (
SET "lastnum=%first5%"
GOTO end
)
SET "dupnum=Y"
GOTO end
ENDLOCAL
:end
PAUSE
Not sure why i can not get it to make a folder and store the duplicated files into it. It will read through my test folder and notice the duplicates and prompt to move them but i am not sure what i need to do to move it to a new folder.
It's difficult to determine where to start if you've not encountered the language before.
The first issue is to properly specify what your terms mean. A "duplicate file" for instance would normally mean duplicate contents or a duplicate name (but since a directory can only hold one file with any particular name, this would imply different directories). In your case, your individual definition is "files whose names start with the same 5 digits".
Since this leads to a batch which uses many techniques (and there can be many approaches) then this is one that could be used:
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET /a lastnum=200000
md %destdir% 2>nul
FOR /f "delims=" %%a IN (
'dir /b /a-d /on "%sourcedir%\*" '
) DO (
CALL :detect "%%a"
IF DEFINED dupnum (ECHO(MOVE "%sourcedir%\%%a" "%destdir%\")
)
GOTO :EOF
:: Routine to detect whether the first 5 characters of the filename "%1"
:: are all numeric and if so, whether they match the previous 5-digit number
:: found.
:detect
SET "dupnum="
:: Get the first 5 characters of the filename; prefix with a `1`
SET "fullfilename=%~1"
SET "first5=1%fullfilename:~0,5%"
IF "%first5%" neq "%lastnum%" (
SET "lastnum=%first5%"
GOTO :eof
)
:: First 5 chars of this filename = first 5 of previous filename
:: Check to see whether numeric
SET /a dummy=first5 + 0
IF "%dummy%" neq "%first5%" (
SET "lastnum=%first5%"
GOTO :eof
)
SET "dupnum=Y"
GOTO :eof
You would need to change the settings of sourcedir and destdir to suit your circumstances.
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)
Fundamentally, batch code is case-insensitive with the notable exception of the metavariable in a for statement (%%a above)
Batch is however quite layout-sensitive, so best to cut-and-paste rather than retyping and attempting to re-format to suite some more-pleasing-to-the-eye style.
It's normally assumed that batch code is run from the prompt, not by 'clicking'.
The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned. set /a can normally be used "quoteless".
The #echo off statement turns command-echoing off so that the command is not echoed to the console before being executed.
The setlocal statement invokes a 'local environment' so that any alterations or additions to the environment are disposed of when the batch finishes.
The main loop first executes a dir command to produce a directory list. The options chosen are /b - basic (no header/footer - names only) /a-d - no directorynames and /on - order-by-name (ie alphabetical)
The output of the dir command is then processed by a for /f with the "delims=" option. This assigns the entire line to the metavariable %%a.
For each filename found, the subroutine :detect is executed. Since that rouine is located within this batch file, a : is used in the call statement. Without the :, the routine called would be a batchfile named detect (either detect.bat or detect.cmd, the former being more popular)
After the routine :detect has been executed, a variable named dupnum may or may not be set. If it's set, echo the string. If not, don't.
The routine :detect first "set"s dupnum (the result of the routine) to an empty string, which removes the variable from the environment if it exists.
The variable fullfilename is then set to the value passed from the call statement. %1 means "first parameter". %~1 means "and remove enclosing quotes". The quotes are required to allow the filename being passed to contain spaces.
We then get the first 5 characters from the full filename (from "character 0" for 5 characters) and prefix this with a 1. Batch regards a string which starts with a 0 as an octal string if used in a mathematical operation, so the 1 prefix ensures the now 6 characters are interpreted as decimal.
Now - if the first5 is not equal to the lastnum, then we record the new lastnum and goto :eof which exits the subroutine. dupnum has not be set, so the calling loop will not show this filename.
Next, we only want to do the move if the first 5 (well, now 6) chars are all numeric. We can add 0 to the string in mathematical mode using a set /a which will either result in the same number (if these chars are all-numeric) or with a different number (up to the first non-numeric) if the "first5" contains a non-numeric.
So - test the result - if different, "first5" contains a non-numeric, so bail out with dupnum not set. If the same, then set dupnum so the filename is reported.
Please also note that the official remark instruction is rem, but :: (which is a broken label - one which can't be reached) is often used instead for remarks as it is easier to type and less intrusive to the eye. Note however that the :: style can't be used within code-blocks (statements grouped between parentheses)
I've added a new line, md ... which should create the destination directory. The 2>nul suppresses error messages should that directory already exist. If you want this directory to be a subdirectory of the source directory, then set destdir thus:
set "destdir=%sourcedir%\subdirectorynamedesired"

Batch file - output csv to different folders

everything in my code is working fine except the last part.
I am wanting to output each text file to the folder with the same name. It is outputing the three text files into the one folder PentahoOutputs. However I am wanting to output it as the following:
folder system2.object2.assets contains file system2.object2.assets
folder system3.object3.assets contains file system3.object3.assets
folder system4.object4.assets contains file system4.object4.assets
#echo off SetLocal EnableDelayedExpansion
SET DELIMS=,
SET COMMAND=AddChange
SET EN=EN
SET ASSETS=Assets
SET DIREC = C:\Users\user.username\Documents\Training\BatchFiles\PentahoOutputs\
SET DELIMS2=.
FOR /D %%a IN (C:\Users\user.username\Documents\Training\BatchFiles\PentahoOutputs\*) DO ( SET subdirs=%%a
result=!subdirs:~71,7!
result2=!subdirs:~79,7!
set "concats=!result!!delims!!result2!!DELIMS!!COMMAND!!DELIMS!!EN!"
echo !concats!
echo !CONCATS! >>C:\Users\user.username\Documents\Training\BatchFiles\PentahoOutputs\!result!!delims2!!result2!!delims2!!assets!.CSV
)
PAUSE>NUL
edit ********** below
changing the problem code to the following puts each of the three files in each of the three folders... however i want one file in each folder
for /d %%b in (C:\Users\usre.username\Documents\Training\BatchFiles\PentahoOutputs\*) DO ( echo !CONCATS! >>%%b\!result!!delims2!!result2!!delims2!!assets!.csv )
From your posted code - given aschipfl's change as noted (although you don't attempt to use direc)
Your posted code has been mangled in an attempt, I assume, to disguise usernames. It also appears that you've cut down the actual code to show only the relevant section. This is good and understandable (but your edit has a glaring typo in the code - which is why you should cut-and-paste as far as possible.)
So - the setlocal following the #echo off must be separated by a & command-concatenator or be (my preference) on a separate line.
Within your for ... %%a ... block, you've removed the required set keyword for result*.
The fixed values you've used for substringing don't suit the changes you've made to the pathname, so the result in result is (eg) "tem3.ob"
If a value does not change within a block (like delims) then it's probably best to use %delims% - result changes, so you'd use !result! not %result%. !delims! also works, of course - but using the delayed-expansion form primes the reader to believe it's going to vary. (opinion)
'tis best with a string assignment to use set "var=value" as the quotes ensure that stray trailing spaces are not included in the value assigned. You only ever need to have that happen once...
OK - here's a revision
#echo OFF
SetLocal
SET DELIMS=,
SET COMMAND=AddChange
SET EN=EN
SET ASSETS=Assets
SET DIREC=U:\Users\user.username\Documents\Training\BatchFiles\PentahoOutputs\
SET DELIMS2=.
FOR /D %%a IN (%direc%*) DO (
FOR /f "tokens=1,2,3 delims=." %%p IN ("%%~nxa") DO IF /i "%%r"=="%assets%" (
echo %%p%delims%%%q%DELIMS%%COMMAND%%DELIMS%%EN%
echo %%p%delims%%%q%DELIMS%%COMMAND%%DELIMS%%EN% >> %%a\%%~na.CSV
)
)
GOTO :EOF
Note that I've used U: for the test directory (it's a ramdrive on my machine)
Given the outer loop, %%a is assigned the full pathname to the directory.
Since you imply that your target directorynames are system2.object2.assets then %%~nxa (the Name and eXtension of %%a) conveniently holds this string. Parsing that using delims of . and selecting the first 3 tokens would assign system2 to %%p, object2 to %%q and assets to %%r This avoids the substringing problem and permits system and object to be any length - not just 7.
The if statement ensures that the main block for for...%%p is only executed for directories found which fit ..asset (/i makes the if case-insensitive)
The required line can then be constructed from the metavariables and constants, as can the destination filename, so the enabledelayedexpansion is not required.

Determine filename length from a batch file

I need to work two things into a .bat file I am working on for a little project. First things first, I have to know if any filename contained into the same folder (recursively) I launch my .bat in is any longer than 100 characters. If so, I need to make it 92 characters long and keep the extensions.
For example, I have this filename:
IncrediblyLongFileNameIAmSorryForThisItLooksLikeSomeDamnSpamJesusIAintEvenCloseTo100yetalmostwaitforitYEAH.omg
The above filename is 110 characters. I need to keep the extension, therefore the program should rename the file as this:
IncrediblyLongFileNameIAmSorryForThisItLooksLikeSomeDamnSpamJesusIAintEvenCloseTo100yetalmos.omg
So far, my main problem is that I don't know how to work with filename strings in batch. I used this code:
#echo off & setlocal enableextensions
FOR /R %%i IN (*.*) DO (
ECHO %%~nxi
FOR /f "delims=:" %%a in ('
^(echo."%%~nxi"^& echo.^)^|findstr /o .'
) DO set lenght=%%a-5
echo The length of "%%~nxi" is %lenght%
)
endlocal & goto :EOF
But I can't SET inside a FOR, and it can't do basic math either (i.e. it can't do the -5 operation).
The second thing, which I believe should be easier once the first one is done, is simply to compare all the filenames in the folder (recursive, once again) and make sure no filenames are the same. If the program finds any filenames that are the same, the second occurrence should be renamed to add something like l1l at the end. (I can't use parentheses here, therefore I use two ls instead to cover the number.) The only thing you need to take care of is the file extensions, because I can't add anything after the file extensions, lest they become unusable.
Can anyone offer explanations for how to accomplish this? I would really like to be able to work this out myself, but I simply lack experience in batch programming.
#ECHO OFF
SETLOCAL
SET "sourcedir=c:\sourcedir"
SET "tempfile=%temp%\##fn##.92"
ECHO ::>"%tempfile%"
FOR /f "delims=" %%a IN (
'dir /s /b /a-d "%sourcedir%\*" '
) DO (
SET "fullname=%%a"
SET "name=%%~na"
SET "ext=%%~xa"
CALL :chgname
)
del "%tempfile%"
GOTO :EOF
:chgname
:: Proposed new name part - first 92 characters of existing name
:: also prepare for adding modifier
SET "newname=%name:~0,92%"
SET /a modifier=0
:modl
:: See whether this name has already been found
ECHO %newname%%ext%|FINDSTR /b /e /i /g:"%tempfile%" >NUL
IF ERRORLEVEL 1 GOTO makechange
:: existing name - modify it
SET "newname=%name:~0,92%#%modifier%#"
SET /a modifier+=1
GOTO modl
:makechange
IF "%name%" NEQ "%newname%" ECHO REN "%fullname%" "%newname%%ext%"
>>"%tempfile%" ECHO %newname%%ext%
GOTO :eof
Reasonably simple problem.
Get a directory-list in basic form (full-filename only) and apply the full filename, name part and extension part to appropriately-named variables.
Manipulate the filename to a new name consisting of the first 92 characters of the original name part. Anticipate the need to modify this new name by establishing a modifier to optionally be applied.
See whether the proposed new name already exists in the temporary file of NEW names already processed. If not found on that file, safe to rename (if required) and record name used.
If the filename has already been used, modify it to the original first 92+ "#anumber#", increment the modifier in anticipation and try again.
Only two comments required further - first, I used # rather than ! because ! has a special meaning to batch. Second, writing :: to the tempfile (the name of the tempfile is irrelevant - I chose one that's unlikely to exist...) means that findstr doesn't complain because the file is empty, but :: can't possibly be a real filename.
The /b /e /i options to findstr mean that the name echoed in must exactly match a line (matches both /b - begin and /e - end) but /i - case is irrelevant.

Resources