windowsfor-loopbatch-filecmddelayedvariableexpansion

Why does my batch script fail to execute Java program in a loop?


Following batch script is supposed to loop through files in specified directory. The directory contains files with two extensions which are .in and .out. For example, there is a dog.in file and a dog.out file. There are multiple such file pairs in that directory. Each file contains only 3-4 lines of text.

Then there is some Java program, which is supposed to get a single .out file as an input argument, and a single .in file redirected to stdin using < in Java execution command.

The batch script works fine if it only performs a single Java execution for a single pair of files like this:

@ECHO OFF
cd e:\WORK\NetBeansProjects\potyczki
pwd
set infname=D:\___Potyczki_Algorytmiczne\2024\mro_tests\mro1k.in
set outfname=D:\___Potyczki_Algorytmiczne\2024\mro_tests\mro1k.out
java -classpath target/classes/ pl.kazanik.potyczki._2024.Mrowki %outfname% < %infname%

When the batch script is changed to loop through files in a directory and executes the Java program for each pair of files in a loop, then an error is thrown with showing the error message: The system cannot find the file specified. The error occurs in the batch script, before the Java program execution starts. I know this because as a first line in main method of the Java program I have a print message which is not present in the output when the batch file is executed. There is only above error message printed for each file pair in the directory, i.e. for each iteration of the for loop.

@echo off
setlocal
cd e:\WORK\NetBeansProjects\potyczki
pwd
set testdir=D:\___Potyczki_Algorytmiczne\2024\mro_tests
for %%f in (%testdir%\*.in) do (
  set inname=%%f
  call set outname=%%inname:in=out%%
  call java -classpath target/classes/ pl.kazanik.potyczki._2024.Mrowki %%outname%% < %%inname%%
)

I will be grateful for telling what I am doing wrong in the batch script.


Solution

  • The solution for the single file pair can be enhanced for processing all *.in files in the specified directory with the following batch file:

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    cd /D "E:\WORK\NetBeansProjects\potyczki" || exit /B
    if exist java.exe (for %%I in (java.exe) do set "JavaExe=%%~fI") else for %%I in (java.exe) do set "JavaExe=%%~$PATH:I"
    if defined JavaExe for %%I in ("D:\___Potyczki_Algorytmiczne\2024\mro_tests\*.in") do "%JavaExe%" -classpath target\classes\ pl.kazanik.potyczki._2024.Mrowki "%%~dpnI.out" <"%%I"
    endlocal
    

    The first two command lines define the execution environment with:

    The batch file works also with *.in files containing one or more ! in file name because of disabled delayed variable expansion. The processing of the batch file is also faster because of disabled delayed variable expansion.

    The third line changes the current working directory to the specified directory. If that fails because of that directory does not exist anymore or is currently not available on being a network drive, the batch file processing is exited without doing anything.

    Java executable is run perhaps thousands of times on thousands *.in files in the specified directory. It is inefficient to let cmd.exe search for a file with name java with a file extension as defined in the local environment variable PATHEXT in the current working directory and when not found here in the directories of the local environment variable PATH again and again on each *.in file to process. The fifth line defines for that reason the environment variable JavaExe with its fully qualified file name on existing in the current directory E:\WORK\NetBeansProjects\potyczki or on being found in any directory of the local environment variable PATH.

    If java.exe could be found somewhere, Java is executed with its fully qualified file name in a simple FOR loop searching in the specified directory D:\___Potyczki_Algorytmiczne\2024\mro_tests for non-hidden files with the file extension .in for processing each found *.in file. The Windows Command Processor does not need to search anymore for this executable repeatedly in file system because of using the absolute file name of java.exe.

    The file name of a *.in file is assigned to the loop variable I with full path because of a full path was also used in the wildcard pattern. %%~dpnI.out references the drive, path and name of the current *.in input file with the file extension changed to .out. %%I references the absolute file name of the current *.in file.

    It would be useful in my opinion that the Java application would also support a command line parameter like "D:\___Potyczki_Algorytmiczne\2024\mro_tests\*.in" in which case the Java application would search itself in the directory for files matched by the wildcard pattern *.in and processes every input file with creation of the output file. In other words, the loop is inside the Java application instead of using a batch file for processing multiple input files. That would be even more efficient and is quite easy to code in Java.

    To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.

    See also single line with multiple commands using Windows batch file for an explanation of the conditional command operator ||.