In Windows, I have a batch file for processing the text file C:\BBB\CCC\list.txt
for deciding which files to move. It should move all the files that are in a folder (%folder%
) and its subfolders, but only if:
%excludeName%
)%excludeFile%
).In that list.txt
I have millions of rows like:
C:\AAA\XXX\ZZZ\image_1.jpg
C:\AAA\XXX\KKK\image_2.jpg
C:\AAA\XXX\ZZZ\pdf_1.pdf
This is the batch file and it's working fine for that purpose:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "folder=C:\AAA"
set "excludeFile=C:\BBB\CCC\list.txt"
set /p excludeName="Year not to delete: "
echo:
set /p rootPath="Backup folder path: "
FOR /f %%a in ('WMIC OS GET LocalDateTime ^| find "."') DO Set _DTS=%%a
Set _date=%_DTS:~0,4%%_DTS:~4,2%%_DTS:~6,2%
if "%rootPath:~-1%"=="\" (set rootPath= %rootPath:~0,-1%)
set localPath=%rootPath%\backup_deleted_media_%_date%
echo:
rem // Change to the root directory:
pushd "%folder%" && (
rem // Loop through all files but exclude those listed in the list file:
for /F "delims= eol=|" %%F in ('
dir /B /S /A:-D "*.*" ^| findstr /V /L /I /X /G:"%excludeFile%"') do (
for /D %%I in ("%%F\..") do (
echo.%%~nxI|findstr /C:"%excludeFile%" >nul 2>&1
if not errorlevel 1 (
echo Found
) else (
if not exist %localPath%\%%~pF md %localPath%\%%~pF
move %%F %localPath%\%%~pF
)
)
)
)
rem // Return from currently iterated directory to root directory:
endlocal
cmd /k
What I need now is another batch file for doing more or less the same but:
folder
is not C:\AAA
, but is C:\EEE\AAA
list.txt
replacing C:\AAA
by C:\EEE\AAA
and I have to add .jpg
to every single row of that list.txt
(because, by mistake, all the files in C:\EEE\AAA
and its subfolders have that extension, so like image_1.jpg.jpg
, pdf_1.pdf.jpg
, ...) before doing the same move. And I want these changes to be in a new file (%newExcludeFile%
) instead of the original list.txt
.So I've added:
set "newExcludeFile=C:\BBB\CCC\list_new.txt"
set "SEARCHTEXT=\AAA\"
set "REPLACETEXT=\EEE\AAA\"
and I was doing this for deleting and creating the file %newExcludeFile%
from the file %excludeFile%
if exist "%newExcludeFile%" del "%newExcludeFile%"
call jrepl "%SEARCHTEXT%" "%REPLACETEXT%" /x /m /l /f "%excludeFile%" /o "%newExcludeFile%"
Now I'm missing the part for appending .jpg
at the end of every record in the file %newExcludeFile%
and I was thinking if there is a way for doing it without iterating all the rows again after that replace.
I recommend reading first the Stack Overflow page with the question:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
I post next the simple solution for the task to create the output file C:\BBB\CCC\list_new.txt
from input file C:\BBB\CCC\list.txt
with replacing C:\AAA
at beginning of each line by C:\EEE\AAA
and append additionally at end of each line .jpg
so that the lines in C:\BBB\CCC\list.txt
C:\AAA\XXX\ZZZ\image_1.jpg
C:\AAA\XXX\KKK\image_2.jpg
C:\AAA\XXX\ZZZ\pdf_1.pdf
become in file C:\BBB\CCC\list_new.txt
C:\EEE\AAA\XXX\ZZZ\image_1.jpg.jpg
C:\EEE\AAA\XXX\KKK\image_2.jpg.jpg
C:\EEE\AAA\XXX\ZZZ\pdf_1.pdf.jpg
This task can be done with:
@echo off
set "excludeFile=C:\BBB\CCC\list.txt"
set "newExcludeFile=C:\BBB\CCC\list_new.txt"
(for /F "usebackq tokens=2* delims=\" %%I in ("%excludeFile%") do echo C:\EEE\AAA\%%J.jpg)>"%newExcludeFile%"
That's really all.
/F
reads one line after the other from file C:\BBB\CCC\list.txt
.delims=\
.tokens=2*
, with the exception of looking on starting with end of line character in which case the entire line would be ignored, too.C:
and for that reason the default eol=;
can be kept in this use case. There is no line ignored because of end of line character as there is no line starting with a semicolon and so no first substring starting with ;
.AAA
which is assigned to specified loop variable I
according to tokens=2
.C:\AAA\
on each line which is assigned without further line splitting according to *
after tokens=2
to next but one loop variable J
.It would be also possible to use the FOR command line:
(for /F "usebackq tokens=1,2* delims=\" %%I in ("%excludeFile%") do echo %%I\EEE\AAA\%%K.jpg)>"%newExcludeFile%"
This variant copies drive letter and colon (first substring) from source to destination file.
I am a fan of JREPL.BAT, but this batch/JScript hybrid is not really necessary for this task.
However, here is the command line doing the same using jrepl.bat
as the command line with for /F
.
call jrepl.bat "(\\AAA\\.*)$" "\EEE$1.jpg" /F "%excludeFile%" /O "%newExcludeFile%"
It runs a regular expression search for
\AAA\
whereby each backslash must be escaped with one more backslash as the backslash is the escape character in a search regular expression and.*$
for 0 or more characters up to end of the line(
and )
with replacing each found string with
\EEE
(with backslash not escaped by JScript exception) and$1
the found string to keep it and.jpg
appended.Next I want to let all readers of this answer know what was not good coded in the few lines of the batch file posted in the question with the reasons.
It is recommended to modify
if "%rootPath:~-1%"=="\" (set rootPath= %rootPath:~0,-1%)
set localPath=%rootPath%\backup_deleted_media_%_date%
to
if "%rootPath:~-1%" == "\" set "rootPath=%rootPath:~0,-1%"
set "localPath=%rootPath%\backup_deleted_media_%_date%"
The arguments for command IF are specified in this case with 100% correct syntax for a string comparison as described extensively by my answer on Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files with
"%rootPath:~-1%"
;==
;"\"
.It can be seen on debugging the batch file that Windows command processor corrects automatically if "%rootPath:~-1%"=="\"
with the missing spaces around ==
to if "%rootPath:~-1%" == "\"
with spaces before executing the command IF. Therefore it is best to write the string comparison condition 100% correct in the batch file with spaces around ==
.
The space right of =
is removed in argument string of command SET in improved command line as rootPath
should not be redefined with a space at beginning as described in detail by my answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line?
The argument string of the two SET commands are additionally enclosed in "
to work also for paths containing an ampersand character as otherwise &
in path would be interpreted as AND operator for an additional command to execute after command SET. See my answer on single line with multiple commands using Windows batch file for meaning of &
which is not within a double quoted argument string.
See also my answer on syntax error in one of two almost-identical batch scripts: ")" cannot be processed syntactically here which describes several very common syntax issues. Issue 1 is not enclosing file/folder argument strings in double quotes as required on file/folder argument string containing a space or one of these characters &()[]{}^=;!'+,`~
as described by help of Windows command processor output on running cmd /?
in a command prompt window.
The two command lines
if not exist %localPath%\%%~pF md %localPath%\%%~pF
move %%F %localPath%\%%~pF
are also very problematic if localPath
is for example defined with the string C:\Temp\Test & Development
.
The command IF is designed to run one command on condition being true. There should not be used (
and )
if just a single command needs to be executed on true condition although it is always possible to define a command block with just one command. This is the second common syntax issue on batch file coding.
There are lots of characters in the ASCII table which have no special meaning for neither cmd.exe
processing a batch file and nor for its internal command FOR, but beginners in batch file writing tend towards using characters from the small set as loop variable which have a special meaning like a
or F
which must be used very carefully on being used as loop variables.
The command popd
should be used always after a successful execution of pushd
, especially if pushd
assigns a network resource access with a UNC path to a drive letter. Otherwise it could happen on repeated execution of a batch file that all drive letters are used finally.
It is very good practice to use the fully qualified file name of an executable wherever possible to make a batch file independent on the environment variables PATH
and PATHEXT
and avoid unnecessary file system accesses by Windows command processor to find the files which are usually specified only with its file name like find
or findstr
or wmic
.
Here is the batch file code as posted in question with all more or less small issues fixed:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "folder=C:\AAA"
set "excludeFile=C:\BBB\CCC\list.txt"
set /P "excludeName=Year not to delete: "
echo:
set /P "rootPath=Backup folder path: "
for /F %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime ^| %SystemRoot%\System32\find.exe "."') do set "_DTS=%%I"
set "_date=%_DTS:~0,4%%_DTS:~4,2%%_DTS:~6,2%"
if "%rootPath:~-1%" == "\" set "rootPath=%rootPath:~0,-1%"
set "localPath=%rootPath%\backup_deleted_media_%_date%"
echo:
rem // Change to the root directory:
pushd "%folder%" && (
rem // Loop through all files but exclude those listed in the list file:
for /F "eol=| delims=" %%I in ('dir /B /S /A:-D "*.*" ^| %SystemRoot%\System32\findstr.exe /V /L /I /X /G:"%excludeFile%"') do (
for /D %%J in ("%%I\..") do (
echo.%%~nxJ|%SystemRoot%\System32\findstr.exe /C:"%excludeName%" >nul
if errorlevel 1 (
if not exist "%localPath%\%%~pI" md "%localPath%\%%~pI"
move "%%I" "%localPath%\%%~pI"
) else echo Found
)
)
popd
)
rem // Return from currently iterated directory to root directory:
endlocal
%ComSpec% /K
There is additionally corrected /C:"%excludeFile%"
to /C:"%excludeName%"
in most inner FOR loop.
Note: This batch file was not tested by me as I have never executed it!