Search code examples
batch-fileenvironment-variablescall

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

Solution

  • 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.