Batch Script that checks a directory for duplicate files - file

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"

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%

Batch rename to numeric names with leading zeroes

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.

Batch Script To Identify Missing Numerical File Name

I have a custom service that automatically generates files every 60 mins into a particular directory with part of the filename incrementing numerically, Eg:
File_00004.job
File_00003.job
File_00002.job
File_00001.job
Currently I have an issue where on occasion a file isn't generated, which results in gaps in the file sequence. This issue then causes a number of issues if not identified ASAP.
I'd like a batch file to identify if I have a gap in the file name sequence.
Tried looking for solutions from existing posts, but haven't found something that fits, so apologies if this has been covered elsewhere.
#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 "mask=file_??????.job"
SET "lowest="
SET "highest="
FOR /f "delims=" %%a IN (
'dir /b /a-d /on "%sourcedir%\%mask%" '
) DO (
IF NOT DEFINED lowest SET "lowest=%%~na"
SET "highest=%%~na"
)
SET "lowest=%lowest:*_=1%"
SET "highest=%highest:*_=1%"
ECHO checking range %lowest:~1% to %highest:~1%
:: See whether an entry in the range is missing; report&create an empty file if so.
FOR /L %%a IN (%lowest%,1,%highest%) DO SET "name=%%a"&SET "name=file_!name:~1!.job"&IF NOT EXIST "%sourcedir%\!name!" echo !name! missing&(copy nul "%sourcedir%\!name!" >nul)
GOTO :EOF
Alternative structure for the for /L loop:
FOR /L %%a IN (%lowest%,1,%highest%) DO (
SET "name=%%a"
SET "name=file_!name:~1!.job"
IF NOT EXIST "%sourcedir%\!name!" (
echo !name! missing
copy nul "%sourcedir%\!name!" >nul
copy "d:\path to\template.file" "wherever\!name!" >nul
copy "d:\path to\template.file" "anotherplace\!name!" >nul
echo Batch is fun and powerful
copy "d:\path to\template.file" "a third place\!name!" >nul
)
)
The critical point is the positioning of the ( - must be directly after and on the same line as do or else or the logical comparison clause of if and must be matched by a ) (which doesn't need to be on its own line - I find it easier that way, to align indentation.) )s that are not intended to close a block need to be escaped with ^, thus: ^)

Get directory name from array in Batch [duplicate]

This question already has answers here:
Arrays, linked lists and other data structures in cmd.exe (batch) script
(11 answers)
Closed 3 years ago.
I have a list of paths from which I want to extract folder name
I wrote:
#echo off
set paths[0]="C:\p\test1"
set paths[1]="C:\p\test2"
set paths[2]="C:\p\test3"
(for %%p in (%paths%) do (
for %%F in (%%p) do echo Processing %%~nxF
))
but seems that nothing is shown.
I expected to see:
Processing test1
Processing test2
Processing test3
It makes a big difference if first " is specified on a set command line left to variable name or left to variable value. In most cases it is better to specify it left to the variable name, especially if a variable value holding a path should be concatenated later with a file name to a full qualified file name.
See also: Why is no string output with 'echo %var%' after using 'set var = text' on command line?
The solution for this task is:
#echo off
set "paths[0]=C:\p\test1"
set "paths[1]=C:\p\test2"
set "paths[2]=C:\p\test3"
for /F "tokens=1* delims==" %%I in ('set paths[ 2^>nul') do echo Processing %%~nxJ
The command FOR with option /F and a set enclosed in ' results in starting one more command process running in background with %ComSpec% /c and the command line specified between the two ' appended as further arguments. So executed is in this case with Windows installed to C:\Windows:
C:\Windows\System32\cmd.exe /c set paths[ 2>nul
The command SET outputs all environment variables of which name starts with paths[ line by line using the format VariableName=VariableValue to handle STDOUT of started background command process.
It could be that there is no environment variable of which name starts with paths[ which would result in an error message output to handle STDERR by command SET which would be redirected from background command process to handle STDERR of the command process which is processing the batch file and for that reason would be displayed in console window. For that reason a possible error message is redirected by the background command process to device NUL to suppress it with using 2>nul.
Read the Microsoft article about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded set command line with using a separate command process started in background.
FOR captures in this case everything written to handle STDOUT of started background command process and process this output line by line after started cmd.exe terminated itself.
Empty lines are ignored by FOR which does not matter here as there are no empty lines to process.
FOR would split up a non-empty line into substrings using normal space and horizontal tab as string delimiters and would assign just first space/tab separated string to specified loop variable, if it does not start with default end of line character ;. This default line splitting behavior is not wanted here. For that reason the option delims== defines the equal sign as string delimiter.
The option tokens=1* instructs FOR to assign in this case the variable name to specified loop variable I and assign everything after the equal sign(s) after variable name without any further string splitting on equal signs to next loop variable according to ASCII table which is in this case J. That is the reason why loop variables are interpreted case-sensitive while environment variables are handled case-insensitive by the Windows command processor.
In this case only the variable value is of interest in the body of the FOR loop. For that reason just loop variable J is used on ECHO command line while I is not used at all.
The modifier %~nxJ results in removing surrounding double quotes from string value assigned to loop variable J and next get the string after last backslash or beginning of string in case of the string value does not contain a backslash at all. This is the name of the last folder in folder path 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.
echo /?
for /?
set /?
UPDATE:
There is a big advantage of this solution in comparison to the other two solutions posted up to now here:
There is not used delayed environment variable expansion which is always problematic on working with file or folder names on not being 100% sure that no folder and no file contains ever an exclamation mark in its name.
Let us compare the three solutions with unusual folder names containing !.
#echo off
rem Make sure there is no environment variable defined of which name starts with
rem paths[ as suggested by Compo which is a very valuable addition on my code.
for /F "delims==" %%I in ('set paths[ 2^>nul') do set "%%I="
set "paths[0]=C:\p\test1!"
set "paths[1]=C:\p\!test2"
set "paths[2]=C:\p\!test!3"
echo/
echo Results of solution 1:
echo/
for /F "tokens=1* delims==" %%I in ('set paths[ 2^>nul') do echo Processing %%~nxJ
echo/
echo Results of solution 2:
echo/
SetLocal EnableDelayedExpansion
for /L %%i in (0,1,2) do (
for %%j in (!paths[%%i]!) do echo Processing %%~nxj
)
endLocal
echo/
echo Results of solution 3:
echo/
Setlocal EnableDelayedExpansion
Call :process paths "!paths[0]!" "!paths[1]!" "!paths[2]!"
Endlocal
echo/
pause
goto :EOF
:process
Set P_C=0
Set /a P_C-=1
For %%a in (%*) DO (
CALL :populate %1 "%%~a"
)
Set /a P_C-=1
For /L %%b in (0,1,!P_C!) DO (
ECHO Processing %1[%%b] = "!%1[%%b]!"
)
GOTO :EOF
:populate
Set "%1[!P_C!]=%~2"
Set /a P_C+=1
GOTO :EOF
The output on running this batch file is:
Results of solution 1:
Processing test1!
Processing !test2
Processing !test!3
Results of solution 2:
Processing test1
Processing test2
Processing 3
Results of solution 3:
Processing paths[0] = "C:\p\test1\p\\p\3"
Solution 1 as posted here works for all three folder names correct.
Solution 2 omits for first and second folder name the exclamation mark which will most likely cause errors on further processing. The third folder name is modified to something completely different. Enabled delayed expansion results in parsing a second time echo Processing %%~nxj after %~nxj being replaced by !test!3 with interpreting test in folder name now as environment variable name of which value is referenced delayed. There was no environment variable test defined on running this batch file and so !test!3 became just 3 before echo was executed by Windows command processor.
Solution 3 produces garbage on any folder name contains an exclamation mark, even on full qualified folder name defined before enabling delayed expansion and referenced with delayed expansion on calling the subroutine process.
Well, folder and file names with an exclamation mark in name are fortunately rare which makes the usage of delayed expansion usually no problem. But I want to mention here nevertheless the potential problems which could occur on any folder name containing one or more !.
Something like that should work :
#echo off
set paths[0]="C:\p\test1"
set paths[1]="C:\p\test2"
set paths[2]="C:\p\test3"
SetLocal EnableDelayedExpansion
for /L %%i in (0,1,2) do (
for %%j in (!paths[%%i]!) do echo Processing %%~nxj
)
pause
Define the Array within the function.
This approach can be used to define multiplay Arrays.
#ECHO OFF
Setlocal EnableDelayedExpansion
:: REM P_C is used to define the range of the Array. The -1 operations on P_C is to shift the paths parameter out of the Arrays working Index.
::REM the first parameter passed is used as the Arrays Name. all other parameters are assigned to index values 0 +
Call :process paths "C:\p\test1" "C:\p\test2" "C:\p\test3"
pause
:process
Set P_C=0
Set /a P_C-=1
For %%a in (%*) DO (
CALL :populate %1 "%%~a"
)
Set /a P_C-=1
For /L %%b in (0,1,!P_C!) DO (
ECHO Processing %1[%%b] = "!%1[%%b]!"
)
GOTO :EOF
:populate
Set "%1[!P_C!]=%~2"
Set /a P_C+=1
GOTO :EOF

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