Reputation: 1003
If I want to call :foo in foo.bat from bar.bat I do:
::foo.bat
echo.wont be executed
exit /b 1
:foo
echo foo from foo.bat
exit /b 0
and
::bar.bat
call :foo
exit /b %errorlevel%
:foo
foo.bat
echo.will also not be executed
But if I don't know the label name but get it passed as a parameter I'm stuck
::bar.bat
:: calling a dynamic label is no problem
call :%~1
exit /b %errorlevel%
::don't know how to "catch-all" or set context of "current-label"
:%~1
foo.bat
Upvotes: 3
Views: 2065
Reputation: 34899
Labels are searched by the parser literally before resolving environment variables or argument references, so you cannot use such in labels.
However, hereby I want to provide an approach that allows to use dynamic labels, although I do not understand what is the purpose of that, so this is more kind of an academic answer...
The batch file parser of cmd
does not cache a batch file, it reads and executes it line by line, or correctly spoken, it reads and executes each command line/block individually. So we can make use of that and let the batch file modify itself during its execution, given that the modified part lies beyond the currently executed code portion. The following script accomplishes that:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Check whether a label name has been delivered:
if "%~1"=="" (
echo ERROR: No label name specified! 1>&2
exit /B 1
)
rem /* Call sub-routine to replace the literal label string `:%~1`
rem within this batch file by the given dynamic label name: */
call :REPLACE_LINE "%~f0" ":%%%%~1" ":%~1" || (
rem /* In case the given label is `:REPLACE_TEXT`, display error
rem message, clean up temporary file and quit script: */
>&2 echo ERROR: Label ":%~1" is already defined!
2> nul del "%~f0.tmp"
exit /B 1
)
rem // Perform call of the sub-routine with the dynamic label name:
call :%~1
rem /* Call sub-routine to replace the given dynamic label name
rem within this batch file by the literal label string `:%~1`: */
call :REPLACE_LINE "%~f0" ":%~1" ":%%%%~1"
endlocal
exit /B
:REPLACE_LINE val_file_path val_line_LOLD val_line_LNEW
::This sub-routine searches a file for a certain line
::case-insensitively and replaces it by another line.
::ARGUMENTS:
:: val_file_path path to the file;
:: val_line_LOLD line to search for;
:: val_line_LNEW line to replace the found line;
setlocal DisableDelayedExpansion
rem // Store provided arguments:
set "FILE=%~1" & rem // (path of the file to replace lines)
set "LOLD=%~2" & rem // (line string to search for)
set "LNEW=%~3" & rem // (line string to replace the found line)
set "LLOC=%~0" & rem // (label of this sub-routine)
rem // Write output to temporary file:
> "%FILE%.tmp" (
rem /* Read the file line by line; precede each line by a
rem line number and `:`, so empty lines do not appear as
rem empty to `for /F`, as this would ignore them: */
for /F "delims=" %%L in ('findstr /N "^" "%FILE%"') do (
rem // Store current line with the line number prefix:
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem // Check current line against search string:
if /I "!LINE:*:=!"=="!LOLD!" (
rem // Current line equals search string, so replace:
echo(!LNEW!
) else if /I not "!LNEW!"=="!LLOC!" (
rem // Current line is different, so keep it:
echo(!LINE:*:=!
) else (
rem /* Current line equals label of this sub-routine,
rem so terminate this and return with error: */
exit /B 1
)
endlocal
)
)
rem /* Searching and replacement finished, so move temporary file
rem onto original one, thus overwriting it: */
> nul move /Y "%FILE%.tmp" "%FILE%"
endlocal
exit /B
:%~1
::This is the sub-routine with the dynamic label.
::Note that it must be placed after all the other code!
echo Sub-routine.
exit /B
Basically, it first replaces the literal label string (line) :%~1
by the string provided as the first command line argument, then it calls that section by call :%~1
, and finally, it restores the original literal label string. The replacement is managed by the sub-routine :REPLACE_LINE
.
Upvotes: 1
Reputation:
As aschipfl already correctly stated you can't have a dynamic label, but you can dynamically call present labels, but you should check for the presence prior calling it like Stephan does but in the primary batch.
So this is a combination of Stephans and jebs batches.
:: bar.bat
@echo off
REM check, if the label is defined in this script:
If "%~1" neq "" findstr /xi "%~1" %~f0 >nul 2>&1||goto :error&&Call :%~1
call :Func1
echo Back from Foo
call :func2
echo Back from Foo
exit /b %errorlevel%
:error
echo wrong or missing label: "%~1"
exit /b 1
:func1
:func2
foo.bat
echo NEVER COMES BACK HERE
:: foo.bat
@Goto :Eof
:func1
echo reached foo.bat, label :func1
exit /b 0
:func2
echo reached foo.bat, label :func2
exit /b 0
Sample output:
> bar
reached foo.bat, label :func1
Back from Foo
reached foo.bat, label :func2
Back from Foo
> bar fx
wrong or missing label: "fx"
Upvotes: 0
Reputation: 82217
You can use a batch parser trick.
You don't need to do anything in foo.bat
for this to work
::foo.bat
echo.wont be executed
exit /b 1
:func1
echo foo from foo.bat
exit /b 0
:unknown
echo Hello from %0 Func :unknown
exit /b
You only need to add any labels into bar.bat
that you want to call in foo.bat
@echo off
::bar.bat
call :unknown
echo Back from Foo
call :func1
echo Back from Foo
exit /b %errorlevel%
:unknown
:func1
foo.bat
echo NEVER COMES BACK HERE
The trick is that after calling a label in bar.bat
and then start foo.bat without calling it (only foo.bat
), the foo.bat is loaded and the last called label is jumped to.
Upvotes: 2
Reputation: 56155
foo.bat (secondary):
@echo off
echo this is foo.bat
REM check, if the label is defined in this script:
findstr /xi "%~1" %~f0 >nul 2>&1 || goto :error
goto %~1
:foo
echo reached foo.bat, label :foo
exit /b 0
:error
echo wrong or missing label: "%~1"
exit /b 1
bar.bat (primary)
@echo off
echo this is bar.bat
call foo.bat :foo
echo back to bar.bat - %errorlevel%
call foo.bat :foe
echo back to bar.bat - %errorlevel%
call foo.bat
echo back to bar.bat - %errorlevel%
exit /b
Upvotes: 0