2
::for /l %%n in (0, 1, 6) do (

for /F "skip=1 delims=" %%i in (path.txt) do set "dirvar=%%i"&goto nextline
:nextline

for /F "skip=1 delims=" %%i in (file.txt) do set "filevar=%%i"&goto nextline
:nextline

for /F "skip=1 delims=" %%i in (dotonefile.txt) do set "dotvar=%%i"&goto nextline
:nextline

SET dirvar=%dirvar%
SET filevar=%filevar%
SET dotvar=%dotvar%
SET dirfile=%dirvar%%filevar%
SET dirdotfile=%dirvar%%dotvar%

IF EXIST %dirfile% (
    del %dirdotfile%
) ELSE (
    rename %dirdotfile% %dirfile%
)

::)

My batch script above works fine in that it runs one time. It reads the 2nd line from three separate text files into variables. Then it tests to see if a filename is in a directory and if it is named IMG001.jpg it deletes IMG001.1.jpg. in the same directory. If IMG001.jpg is NOT found in the directory, it renames IMG001.1.jpg in the directory to IMG001.jpg.

path.txt is just a text file with a list of folder paths like:

F:\My Pictures\2005-Misc\
F:\My Pictures\2006-Misc\
F:\My Pictures\2007-Misc\

file.txt is just a text file with a list file names, where line 1 is a file in the directory that's also line 1 of the path.txt file. So there could be a IMG001.jpg in the 2005-Misc folder, could be a IMG001.jpg in the 2006-Misc folder, and there could be a IMG001.jpg in the 2007-Misc folder:

IMG001.JPG
IMG001.JPG
IMG001.JPG

Similarly with dotonefile.txt, it's a list of filenames that ARE in the corresponding directory listed in path.txt. So there IS a IMG001.1.jpg in folder 2005-Misc, there's one in 2006-Misc, and there's one in 2007-Misc.

IMG001.1.JPG
IMG001.1.JPG
IMG001.1.JPG

I want to loop this script and repeat it so it reads in lines 1 through n (n can be hard coded, above it is currently 7) from the text files to variables, then tests and renames for each filename.

I tried uncommenting the first and last lines and then in the three for loops, I replaced the hardcoded "1" with "%%n" but the batch file won't run erroring with "the sntax of the command is incorrect". Below is my attempt that doesn't work. Any advice on how to tweak it to run? I've tried all kinds of combinations of making a new count variable that increments by 1 at the end, using delayed expansion in various forms of variables, nothing works.

for /l %%n in (0, 1, 6) do (

for /F "skip=%%n delims=" %%i in (path.txt) do set "dirvar=%%i"&goto nextline
:nextline

for /F "skip=%%n delims=" %%i in (file.txt) do set "filevar=%%i"&goto nextline
:nextline

for /F "skip=%%n delims=" %%i in (dotonefile.txt) do set "dotvar=%%i"&goto nextline
:nextline

SET dirvar=%dirvar%
SET filevar=%filevar%
SET dotvar=%dotvar%
SET dirfile=%dirvar%%filevar%
SET dirdotfile=%dirvar%%dotvar%

IF EXIST %dirfile% (
    del %dirdotfile%
) ELSE (
    rename %dirdotfile% %dirfile%
)

)
Mike7143
  • 23
  • 3
  • You cannot use a metavariable as a `skip` value. you need either predefine value or instead use `for /f "delims=" %%I in ('type path.txt ^| more + %%n') do...` in your loop. Do not use te goto labels in the loop either, even if you could, they are all the same name. – Gerhard May 05 '19 at 07:43

1 Answers1

2

The main problem is that Windows command processor cmd.exe does not support labels inside command blocks which are parsed completely before executing the command making use of the command block. Please read for details How does the Windows Command Interpreter (CMD.EXE) parse scripts?

The solutions is using a subroutine.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /L %%N in (0,1,6) do call :ProcessFiles %%N
endlocal
goto :EOF

:ProcessFiles
if not %1 == 0 ( set "SkipOption=skip=%1 " ) else ( set "SkipOption=" )
set "DirVar="
for /F "%SkipOption%eol=| delims=" %%I in (path.txt) do set "DirVar=%%I" & goto GetFileVar
:GetFileVar
set "FileVar="
for /F "%SkipOption%eol=| delims=" %%I in (file.txt) do set "FileVar=%%I" & goto GetDotVar
:GetDotVar
set "DotVar="
for /F "%SkipOption%eol=| delims=" %%I in (dotonefile.txt) do set "DotVar=%%I" & goto CheckFile

:CheckFile
set "DirFile=%DirVar%%FileVar%"
set "DirDotFile=%DirVar%%DotVar%"
if exist "%DirFile%" (
    del "%DirDotFile%"
) else (
    rename "%DirDotFile%" "%DirFile%"
)
goto :EOF

A smarter approach would be using this batch file code without usage of text files at all.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I  in ('dir "F:\My Pictures\*.1.JPG" /A-D /B /S 2^>nul') do (
    for %%J in ("%%~dpnI") do (
        if exist "%%~dpnJ%%~xI" (
            del "%%I"
        ) else (
            ren "%%I" "%%~nJ%%~xI"
        )
    )
)
endlocal

The FOR loop starts with %ComSpec% /C one more cmd.exe command process in background to execute the command line:

dir "F:\My Pictures\*.1.JPG" /A-D /B /S 2>nul

DIR searches with the specified options for

  • files because of option /A-D (attribute not directory)
  • matching case-insensitive the pattern *.1.JPG
  • in directory F:\My Pictures and all its subdirectories because of option /S
  • and outputs in bare format because of option /B just
  • file name with file extension and with full path because of option /S.

DIR would output an error message in case of no file can be found in entire directory tree matching these criteria. This error message is suppressed by redirecting it to device 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 dir command line in a separate command process started in background.

FOR captures everything output to handle STDOUT of started command process and processes the captured text line by line after started cmd.exe finished.

FOR ignores empty lines which do not occur here at all. FOR would also ignore lines starting with ; because of being the default for end of line option. As DIR outputs the file names with full path, it is not possible that a line starts with ;. But eol=| is nevertheless used to define the vertical bar as end of line which no folder/file name can contain ever.

FOR splits up by default each line into substrings (tokens) using normal space and horizontal tab character as delimiters. This behavior is not wanted here as file path could contain a space character. For that reason delims= is used to define an empty list of delimiters which disables the line splitting behavior.

The inner FOR is used to get assigned to loop variable J just the string left to .1.JPG.

The IF condition checks if there is already a file *.JPG for current file *.1.JPG in same directory as current file in which case the file *.1.JPG is deleted or otherwise the renamed to *.JPG if this file deletion or file rename operation is permitted at all depending on read-only attribute, file permissions of current account and current file sharing access permissions.

But let us assume the image file names can be any file name matching *.jpg and there can be not only *.1.jpg, but also *.2.jpg to *.99.jpg image files, i.e. any number after a dot before file extension .jpg. In this case DIR is not enough to get the list of file names with file extension and full path. It is additionally necessary to use FINDSTR with a regular expression to filter the list of file names.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I  in ('dir "F:\My Pictures\*.*.jpg" /A-D /B /S 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R "\.[0123456789][0123456789]*\.jpg$"') do (
    for %%J in ("%%~dpnI") do (
        if exist "%%~dpnJ%%~xI" (
            del "%%I"
        ) else (
            ren "%%I" "%%~nJ%%~xI"
        )
    )
)
endlocal

First FINDSTR outputs just lines read from STDIN which

  • matches case-sensitive because of option /I
  • the regular expression \.[0123456789][0123456789]*\.jpg$
  • as explicitly declared with option /R.

The regular expression matches a string consisting of a dot, one or more digits, one more dot and the string jpg found at end of line. So a file name like Hello.World.jpg output by DIR is not matched by FINDSTR and therefore not output by FINDSTR and so not processed by FOR.
But a file name like Hello.World.393.jpg is processed and either deleted or renamed to Hello.World.jpg depending on existence of Hello.World.jpg in same directory.

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.

  • call /?
  • del /?
  • dir /?
  • echo /?
  • endlocal /?
  • findstr /?
  • for /?
  • goto /?
  • if /?
  • ren /? or rename /?
  • set /?
  • setlocal /?

See also Where does GOTO :EOF return to?

Mofi
  • 38,783
  • 14
  • 62
  • 115
  • Thanks for the help! That 2nd block that does it without using the text files worked great, even when I tested it with spaces in directory names and again with files in directory names with spaces AND file names with spaces. The first block of code, still using the text files, kinda worked. It'd delete the .1. file if the non-.1. file existed, but if there was only a .1. file and no copy without the .1. in the name, it'd error saying a duplicate filename exists or can't be found. Either way, it works! Thanks so much! – Mike7143 May 09 '19 at 23:09