I am trying to figure out how file1.bat can call file2.bat at a specified label.
I figured I can do it like this:
File1.bat
:config
You can use a strange trick!
You can goto a label in a secondary batch without calling it in the secondary batch!
First.bat
@echo off
call :label
echo returned
exit /b
:label
second.bat
exit /b
Second.bat
@echo off
echo Main of second.bat
exit /b
:label
echo This is second.bat at LABEL
exit /b
OUTPUT
This is second.bat at LABEL
returned
There seems to be no cause why the label is called, nor why the control should return to the first.bat, as second batch was called without a CALL.
The cause for the first point seems to be the internal code of the goto
command.
The second point can be explained, as there is one call prior to the dummy label in the first batch file.
The exit /b
in second.bat returns directly to the call (line 3) of first.bat
not to the invocation of second.bat
at line 7
EDIT: How to disable the odd behaviour
if you append a command at second.bat
it will no longer implicit jump to the label in second.bat.
second.bat & rem
will change the output to
OUTPUT
Main of second.bat
returned
I just created a script that might prove useful to you. I think it's strength is that you don't have to modify your called script in any way. I'm sure with a bit of time you can perfect it and make it fit your purpose. I also can figure that it isn't fast enough for production use when the called script is large. This method consists on placing my script to the folder of your own scripts, and using it like so:
EDIT
Now this script works from any directory and can be input with either full or relative paths to the called script, with or without file extension (C:\scripts\script, C:\scripts\script.bat, .\scripts\script.cmd, etc)
Given a sample script script.bat:
:LABEL_1
ECHO This is label number one!
GOTO :EOF
:LABEL_2
ECHO This is label number two!
GOTO :EOF
The following input:
CALLAT script :LABEL_2
Should output:
This is label number two!
@ECHO OFF
SETLOCAL EnableDelayedExpansion
@ECHO OFF
rem Create a folder in the temporary files directory
MKDIR "%TEMP%\CALLAT\"> NUL
rem Chose a name for a temporal script
SET TEMP_SCRIPT="%TEMP%\CALLAT\temp-script.bat"
rem Get the full path to the called script
FOR /F "delims=" %%F IN ('DIR /B /S "%1*" 2^>NUL') DO SET ORIGINAL_SCRIPT=%%F
rem Add this lines to go directly to the desired label in the temp script
ECHO @ECHO OFF> %TEMP_SCRIPT%
ECHO GOTO %2>> %TEMP_SCRIPT%
rem Concatenate the called script to the end of the temp script
COPY /B %TEMP_SCRIPT%+%ORIGINAL_SCRIPT% %TEMP_SCRIPT%>NUL
rem Call the modified script
CALL %TEMP_SCRIPT%
rem Clean up
RD "%TEMP%\CALLAT\" /S /Q>NUL
ENDLOCAL
You could pass the label you want to go to as a parameter
Example scripts
First.bat
@echo off
set label=GOHERE
call Second.bat %label%
pause >nul
Second.bat
@echo off
goto %1
echo This line should be skipped
:GOHERE
echo Jumped here
There are several methods or tricks that allows to do this. In this solution the trick consists in change the context of the running program to the second Batch file. After this change, all labels in the second Batch file becomes local labels of the running program, so they may be directly called via the usual call :label
command that may also include arguments in an entirely standard way. The context of the running program must be recovered before the program terminates.
The way to change the context consist is just rename the second Batch file with the same name of the first one (and rename the first one to any other name) so when the Batch file processor looks for a label, it really search the contents of the second file! The files must be renamed back to their original names before the program terminate. The key point that makes this method work is that both sets of rename commands (and all code between them) must be enclosed in parentheses; in this way, the code is first parsed and then executed from memory, so the rename of the files on disk don't affect it. Of course, this point means that the access to the values of all variables in the code must be done via delayed !expansion! (or using the call %%variable%%
trick).
First.bat
@echo off
rem Change the running context to the second/library batch file:
(
ren first.bat temporary.bat
ren second.bat first.bat
rem From this point on, you may call any label in second.bat like if it was a local label
call :label2
echo Returned from :label2
call :label1
echo Returned from :label1
rem Recover the running context to the original batch file
ren first.bat second.bat
ren temporary.bat first.bat
)
echo This is first.bat ending...
pause > nul
Second.bat
@echo off
:label1
echo I am label1 subroutine at second.bat
goto :EOF
:label2
echo This is the second subroutine at second.bat
goto :EOF
OUTPUT
This is the second subroutine at second.bat
Returned from :label2
I am label1 subroutine at second.bat
Returned from :label1
This is first.bat ending...
You should note that this method allows to develop the subroutines in the first file in the usual way (with no context change) and then just move the completed subroutines to the second/library file, because the way to call them is exactly the same in both cases. This method does not require any change in the second/library file nor in the way the subroutines are called; it just needs to insert the two small blocks of rename commands in the first file.
EDIT: Restoring files when a run-time error happen
As indicated in a comment, if a run-time error happen after the files were renamed, the files are not renamed back to their original names; however, it is very easy to detect this situation by the presence of the second.bat file and restore the files if such a file does not exists. This test may be done in a supervisor section that run the original code in a separate cmd.exe context, so this section will always complete correctly even if the original code was cancelled by an error.
First.bat
@echo off
if "%~1" equ "Original" goto Original
rem This supervisor section run the original code in a separate cmd.exe context
rem and restore the files if an error happened in the original code
(
cmd /C "%~F0" Original
if not exist second.bat (
echo Error detected! Restoring files
ren first.bat second.bat & ren temporary.bat first.bat
)
goto :EOF
)
:Original
( ren first.bat temporary.bat & ren second.bat first.bat
call :label1
echo Returned from :label1
call :label2
echo Returned from :label2
ren first.bat second.bat & ren temporary.bat first.bat )
echo This is first.bat ending...
Second.bat
@echo off
:label1
echo I am label1 subroutine at second.bat
exit /B
:label2
set "var="
if %var% equ X echo This line cause a run-time error!
exit /B
OUTPUT
I am label1 subroutine at second.bat
Returned from :label1
No se esperaba X en este momento.
Error detected! Restoring files
As I said in the comment that I attached to my answer, I awarded the bounty, because @NicoBerrogorry took the time to devise an excellent solution, and provide a complete write-up.
Nevertheless, there was one syntax error that he overlooked, probably because he doesn't use CMD files, while I have almost entirely abandoned BAT files. The following CALLAT script incorporates one correction and four enhancements.
@ECHO OFF
SETLOCAL EnableDelayedExpansion
rem Create a folder in the temporary files directory.
IF NOT EXIST "%TEMP%\CALLAT\" MKDIR "%TEMP%\CALLAT\"
rem Chose a name for the modified script.
SET MODIFIED_SCRIPT="%TEMP%\CALLAT\modified_%1.bat"
rem Convert the called script name to the corresponding .bat
rem or .cmd full path to the called script.
rem Then load the script. - 2017/05/08 - DAG - The second if exist test duplicated the first. Instead, it must evaluate for the .CMD extension.
SET "COMMAND_FILE=%~dp0%1"
IF EXIST "%COMMAND_FILE%.bat" (
SET "COMMAND_FILE=%COMMAND_FILE%.bat"
) ELSE IF EXIST "%COMMAND_FILE%.cmd" (
SET "COMMAND_FILE=%COMMAND_FILE%.cmd"
) else if exist "%COMMAND_FILE%" (
echo INFO: Using "%COMMAND_FILE%"
) else (
echo ERROR: Cannot find script file %COMMAND_FILE%.bat
echo or %COMMAND_FILE%.cmd
)
rem Add some lines to go directly to the desired label.
ECHO @ECHO OFF> %MODIFIED_SCRIPT%
ECHO GOTO %2>> %MODIFIED_SCRIPT%
rem Append the called script.
copy %MODIFIED_SCRIPT%+%COMMAND_FILE% %MODIFIED_SCRIPT% > NUL:
rem Call the modified script.
CALL %MODIFIED_SCRIPT%
rem Pack out your trash. When a script ends, you get an ENDLOCAL for free.
del %MODIFIED_SCRIPT%
rd "%TEMP%\CALLAT\"
Finally, this version of CALLAT eliminates a redundant ECHO OFF and the concluding ENDLOCAL, which I discovered long ago is unnecessary, because exiting a script implies ENDLOCAL.
Since virtually all of my production scripts inhabit a single directory that is accessible via the PATH directory list, the requirement that both scripts inhabit the same directory isn't a significant hindrance. Moreover, with a little more work, it could almost certainly be relaxed. I'll leave that as an exercise for interested readers, but I'll give you one clue; it might involve calling an internal subroutine that can parse the name of the input file by leveraging the technique described [in Command Line Parameters].1
The script shown above is about to find its way into production.