Let us assume the current directory on starting the main batch file is C:\Temp\Test
containing following folders and files:
- Development & Test
- Hello World!
- VersionInfo
- Main.bat
Batch file Development & Test.bat
contains just the line:
@dir ..\Development & Test
Batch file Hello World!.bat
contains just the line:
@echo Hello World!
Batch file VersionInfo.bat
contains just the line:
@ver
Batch file main.bat
contains following lines:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "exit_code=0"
for /D %%I in ("%~dp0*") do (
echo ******************** %%~nxI ********************
echo(
for %%J in ("%%I\*.bat") do (
echo Calling %%J
echo(
pushd "%%I"
call "%%J"
if errorlevel 1 set "exit_code=1"
popd
)
echo(
echo(
)
echo Exit code is: %exit_code%
endlocal & exit /B %exit_code%
A command prompt is opened in which next the following command lines are executed manually one after the other:
C:\Temp\Test\Main.bat
echo Errorlevel is: %errorlevel%
ren "C:\Temp\Test\Development & Test\Development & Test.bat" "Development & Test.cmd"
C:\Temp\Test\Main.bat
echo Errorlevel is: %errorlevel%
The first execution of Main.bat
results in exit with value 1
as it can be seen in command prompt window on the line:
Errorlevel is: 1
The reason is the wrong coded dir
command with the directory name not enclosed in double quotes resulting in interpreting Test
as command to execute. For that reason the dir
command line results in following error output:
Volume in drive C is TEMP
Volume Serial Number is 14F0-265D
Directory of C:\Temp\Test
File Not Found
'Test' is not recognized as an internal or external command,
operable program or batch file.
The exit code of this batch file is not 0
due to the error and for that reason the condition if errorlevel 1
is true and set "exit_code=1"
is executed already on first executed batch file.
The processing of the other two batch files always end with 0
as exit code.
The command ren
is used to change the file extension of Development & Test.bat
to have the batch file next with name Development & Test.cmd
resulting in ignoring it by main.bat
. The second execution of Main.bat
results in exit with 0
as it can be seen on the line:
Errorlevel is: 0
Please read the following pages for the reasons on all the code changes:
Summary of the changes:
- Delayed expansion is not enabled in
Main.bat
as not required here to process also correct directory and file names containing an exclamation mark like C:\Temp\Test\Hello World!\Hello World!.bat
.
I
and J
are used as loop variables instead of s
and f
because of the latter two letters could be misinterpreted as loop variable modifiers in some cases. Therefore it is better to avoid the letters which have a special meaning for command for
on referencing the loop variables.
%~dp0
is used instead of .\
to make sure that the batch file searches for non-hidden subdirectories in the directory of the batch file independent on what is the current directory on starting the batch file. This expression references drive and path of argument 0 which is the full path of currently executed batch file Main.bat
. The referenced path of the batch file always ends with a backslash and for that reason %~dp0
is concatenated with *
without an additional backslash.
- Directory and file name arguments are enclosed in double quotes to work also for names containing a space or one of these characters
&()[]{}^=;!'+,`~
. %%~nxI
and %%J
in the two echo
command lines are not enclosed in double quotes as not necessary as long as delayed expansion is not enabled. The batch file makes sure that this is not the case for Main.bat
.
- The usage of
"%~dp0*"
instead of just .\*
in first FOR loop results in getting assigned to loop variable I
the directory names with full path never ending with a backslash. The usage of "%%I\*.bat"
makes sure to get assigned to loop variable J
the full qualified file name of a non-hidden batch file. It is in general better to use full qualified directory/file names wherever possible. This helps also quite often on errors.
- The two
cd
commands are replaced by pushd
and popd
and moved inside the inner FOR loop. Then it does not matter if a called batch file works only with current directory being the directory of the called batch file or works independent on current directory like Main.bat
. Further it does not longer matter if a called batch file changes the current directory as with popd
the initial current directory on starting Main.bat
is restored as current directory which could be the directory in which files are stored to be processed by the called batch files. The usage of pushd
and popd
makes this batch file also working on being stored on a network resource and Main.bat
is started with its UNC path.
The most important modification is on last command line:
endlocal & exit /B %exit_code%
This command line is first parsed by Windows command processor cmd.exe
with replacing %exit_code%
by current value of environment variable exit_code
defined inside the local environment setup with setlocal EnableExtensions DisableDelayedExpansion
. So the command line becomes either
endlocal & exit /B 0
or
endlocal & exit /B 1
Then Windows command processor executes command endlocal
to restore the previous environment defined outside Main.bat
which results in exit_code
no longer defined if not defined in initial execution environment. Then the command exit
with option /B
to exit just processing of batch file Main.bat
is executed with returning 0
or 1
to the parent process which is cmd.exe
which assigns the exit code to variable errorlevel
.
Well, there is one issue left with the batch file code of Main.bat
. A called batch file could modify the value of environment variable exit_code
of Main.bat
on using the same environment variable without definition of a local environment by using command setlocal
. The solution would be to use additionally the commands setlocal
before and endlocal
after calling a batch file.
@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "exit_code=0"
for /D %%I in ("%~dp0*") do (
echo ******************** %%~nxI ********************
echo(
for %%J in ("%%I\*.bat") do (
echo Calling %%J
echo(
pushd "%%I"
setlocal
call "%%J"
endlocal
if errorlevel 1 set "exit_code=1"
popd
)
echo(
echo(
)
echo Exit code is: %exit_code%
endlocal & exit /B %exit_code%
The command endlocal
does not modify errorlevel
.
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.
call /?
cls /?
echo /?
endlocal /?
for /?
if /?
popd /?
pushd /?
set /?
setlocal /?