batch-file

Batch (.bat): get the name of the first script, not the current one


I have first.bat and second.bat.

first.bat is: call second.bat

Second is: echo %~n0 (displays filename of the executing batch)

The output is Second.bat, but I want it to display the caller filename, not it's own.

Is this possible?


Solution

  • This batch detects the name of the caller script or even if it's called directly from the command line

    @echo off
    setlocal DisableDelayedExpansion
    set "func=%~0"
    for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
    if ":" == "%func:~0,1%" (
        goto %func%
    )
    REM *** Get the name of the caller
    (
        (goto) 2>nul
        setlocal DisableDelayedExpansion
        call set "caller=%%~f0"
        call set _caller=%%caller:*%%~f0=%%
        if defined _caller (
            set "callType=batch"
            call "%~d0\:mainFunc\..%~pnx0" %*
        ) ELSE (
            set "callType=cmd-line"
            cmd /c "call "%~d0\:mainFunc\..%~pnx0" %*"
        )
        endlocal
    )
    echo NEVER REACHED
    exit /b
    
    :mainFunc
    echo :mainFunc of %~nx0 arg1=%1 is called from '%caller%'/%callType%
    exit /b
    

    It uses the fact, that a (goto) statement will remove one level from the stack.
    This results into leaving the current batch file and %~f0 will be the name of the caller script (and %~0 the current function of that batch) or the text %~f0 in the case of called from the command line.

    Then the own script is called again with "%~d0\:mainFunc\..%~pnx0"

    External Script

    For easy use you could add a helper batch file.
    Use it in your own scripts with this line

    @echo off
    <:GetCaller <nul call GetCaller.bat myCallerVar
    echo This batch was called from "%myCallerVar%"
    

    Name the helper batch file GetCaller.bat

    @echo off
    setlocal DisableDelayedExpansion
    set "func=%~0"
    for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
    if ":" == "%func:~0,1%" (
        goto %func%
    )
    
    REM *** STEP1
    REM *** Get the filename of the caller of this script, needed for later restart that
    (
        (goto) 2>nul
        setlocal DisableDelayedExpansion %= it could be reenabled by the GOTO =%
        set "_returnVar=%~1"
        call set "_lastCaller=%%~f0"
        call set "_argToLastCaller=%%*"
        call "%~d0\:Step2\..%~pnx0" %*
    )
    exit /b %= This is never reached =%
    
    :Step2
    REM *** STEP2
    REM *** Get the filename/cmd-line of the caller of the script
    (
        (goto) 2>nul
        (goto) 2>nul
        setlocal DisableDelayedExpansion %= it could be reenabled by the GOTO =%    
        set "_returnVar=%_returnVar%"
        set "_lastCaller=%_lastCaller%"
        set "_argToLastCaller=%_argToLastCaller%"
        call set "caller=%%~f0"
        call set _caller=%%caller:*%%~f0=%%
        if defined _caller (
            set "callType=batch"
            call "%~d0\:Step3batch\..%~pnx0"
        ) ELSE (
            set "callType=cmd-line"
            cmd /c "call "%~d0\:Step3batch\..%~pnx0" "
        )
        endlocal
    )
    exit /b %= This is never reached =%
    
    :Step3batch
    REM *** STEP3 Restart the requester batch, but jump to the label :GetCaller
    call :GetCaller
    exit /b %= This is never reached =%
    
    :GetCaller
    REM *** This uses the trick, that starting a batch without CALL will jump to the last used label
    if "%_returnVar%" NEQ "" set "%_returnVar%=%_caller%"
    %_lastCaller% %_argToLastCaller%
    
    echo #6 never reached