Search code examples
replacecmdappendjrepl

How to replace + append with jrepl via cmd?


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:

  • in the name of the folder there is not the year set in input (%excludeName%)
  • that file is not listed in a text file (%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
  • I have to change the path of the files in 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.


Solution

  • 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.

    • FOR with option /F reads one line after the other from file C:\BBB\CCC\list.txt.
    • Each non-empty line is split up into substrings using backslash as string delimiters because of option delims=\.
    • The first substring is drive letter and colon which is ignored because of option tokens=2*, with the exception of looking on starting with end of line character in which case the entire line would be ignored, too.
    • The first substring is always 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 ;.
    • The second substring is on each line AAA which is assigned to specified loop variable I according to tokens=2.
    • But of real interest is the remaining part after 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

    • the string \AAA\ whereby each backslash must be escaped with one more backslash as the backslash is the escape character in a search regular expression and
    • with .*$ for 0 or more characters up to end of the line
    • within a marking group defined with ( and )

    with replacing each found string with

    • the string \EEE (with backslash not escaped by JScript exception) and
    • with back-referencing with $1 the found string to keep it and
    • with .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

    • first argument being the first string "%rootPath:~-1%";
    • space as argument separator;
    • second argument being the comparison operator ==;
    • space as argument separator;
    • third argument being the second string "\".

    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!