santa
santa

Reputation: 1003

How to call a dynamic label in different .bat file

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

Answers (4)

aschipfl
aschipfl

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

user6811411
user6811411

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

jeb
jeb

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

Stephan
Stephan

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

Related Questions