Call a subroutine in a batch from another batch file

前端 未结 4 1514
太阳男子
太阳男子 2020-12-06 13:46

file1.bat:

@echo off
 :Test
echo in file one
call file2.bat (Here i want to call only Demo routine in the file2.bat)

file2.bat:

<         


        
相关标签:
4条回答
  • 2020-12-06 14:13

    What about providing the target label as the first agrument of the called script? You needed to modify the called dscript then though.

    file1.bat (main):

    @echo off
    echo/
    echo File "%~0":  call "file2.bat" [no arguments]
    call "file2.bat"
    echo/
    echo File "%~0":  call "file2.bat" :DEMO
    call "file2.bat" :DEMO
    echo/
    echo File "%~0":  call "file2.bat" :DEMO A B C
    call "file2.bat" :DEMO A B C
    

    file2.bat (sub):

    @echo off
    set "ARG1=%~1" & if not defined ARG1 goto :TEST
    if "%ARG1:~,1%"==":" goto %ARG1%
    
    :TEST
    echo File "%~nx0", :TEST; arguments: %*
    goto :EOF
    
    :DEMO
    echo File "%~nx0", :DEMO; arguments: %*
    echo   before `shift /1`:
    echo     "%%~0" refers to "%~0"
    echo     "%%~1" refers to "%~1"
    shift /1
    echo   after  `shift /1`:
    echo     "%%~0" refers to "%~0"
    echo     "%%~1" refers to "%~1"
    goto :EOF
    

    Output:

    >>> file1.bat
    
    File "file1.bat":  call "file2.bat" [no arguments]
    File "file2.bat", :TEST; arguments:
    
    File "file1.bat":  call "file2.bat" :DEMO
    File "file2.bat", :DEMO; arguments: :DEMO
      before `shift /1`:
        "%~0" refers to "file2.bat"
        "%~1" refers to ":DEMO"
      after  `shift /1`:
        "%~0" refers to "file2.bat"
        "%~1" refers to ""
    
    File "file1.bat":  call "file2.bat" :DEMO A B C
    File "file2.bat", :DEMO; arguments: :DEMO A B C
      before `shift /1`:
        "%~0" refers to "file2.bat"
        "%~1" refers to ":DEMO"
      after  `shift /1`:
        "%~0" refers to "file2.bat"
        "%~1" refers to "A"
    
    0 讨论(0)
  • 2020-12-06 14:22

    You can write your functions file (in this sample it is library.cmd) as

    @echo off
        setlocal enableextensions
        rem Not to be directly called
        exit /b 9009
    
    :test
        echo test [%*]
        goto :eof
    
    :test2
        echo test2 [%*]
        goto :eof
    
    :testErrorlevel
        echo testErrorlevel
        exit /b 1
    

    And then the caller batch can be something like

    @echo off
        setlocal enableextensions disabledelayedexpansion
    
        call :test arg1 arg2 arg3
        call :test2 arg4 arg5 arg6
        call :testErrorlevel && echo no errorlevel || echo errorlevel raised
    
        goto :eof
    
    :test
    :test2
        echo calling function %0
        library.cmd %*
    
    :testErrorlevel
        echo calling function %0
        library.cmd 
    

    In this case, the labels need to be defined with the same name in both files.

    The direct invocation of the "library" batch file will replace the context of the call :label, and when the invoked batch is readed, a goto :label is internally executed and code continues inside the indicated label. When the called batch file ends, the context is released and the code after the call :label continues.

    edited

    As Jeb points in comments, there is a drawback in this method. The code running in the called batch file can not use %0 to retrieve the name of the function being called, it will return the name of the batch file. But if needed, the caller can do it as shown in the sample code.

    edited 2016/12/27

    Answering to dbenham, I have no way to know if it was a coding error or an intended feature, but this is how the process works

    The lines in batch file are processed inside the inner BatLoop function when a batch "context" is created. This function receives, as one of its arguments, a pointer to the command that caused the "context" to be created.

    Inside this function the commands in the batch file are iterated. The loop that iterates over the commands makes a test in each iteration: if extensions are enabled, it is the first line in the batch file and the arguments of the command that started the context starts with a colon (a label), a goto is generated to jump to the label.

    Up to here, I have to suppose that this is the intended behaviour to handle the call :label syntax: create a new "context", load the file, jump to the label.

    But the command argument received is never changed, a different variable is used to track the execution of the commands in the batch file. If a new batch file is loaded into / overwrites the current batch "context" (we have not used call command), after loading the new batch code, BatLoop resets the line count (we start at the first line of the loaded file) and, voila, the condition at the start of the loop (extensions enabled, first line, the colon) is true again (the pointed input command has not been changed) and a new goto is generated.

    0 讨论(0)
  • 2020-12-06 14:23

    To comment a bit more, here is the code I came out reading the answer. It basically give you a 'utility' file where you can add your sub/fnc in a common library for code maintenance.

    A) As a example, here is the 'utility_sub.cmd' file:

    REM ==============================================
    REM             CALL THE SELECTED SUB
    REM ==============================================
    REM echo %~1
    GOTO :%~1
    
    
    REM ==============================================
    REM               ERROR MANAGEMENT
    REM ==============================================
        REM Ref : https://ss64.com/nt/exit.html
    :RAISE_ERROR
        EXIT /B 1
    
    :RESET_ERROR
        EXIT /B 0
    
        REM Demo call
        REM =========
        REM CALL :RAISE_ERROR
        REM echo RAISE_ERROR ERRORLEVEL = %ERRORLEVEL%
    
        REM If %ERRORLEVEL% GTR 0 (
            REM set msg="%tab%- Error detected ..."
            REM CALL :SUB_STDOUT_MSG !msg!, 1
        REM )
    
        REM CALL :RESET_ERROR
        REM echo RESET_ERROR ERRORLEVEL = %ERRORLEVEL%
    
    
    REM ==============================================
    REM                SUB_STDOUT_MSG
    REM ==============================================
    :SUB_STDOUT_MSG
        REM CALL :SUB_STDOUT_MSG "%param1%", %param2%, %param3%
    
        REM Instead of this stdout sub, we can use Unix 'tee.exe'
        REM but there is no 'line counter' feature like this sub
        REM   Call example : 
        REM     EDI_Generate_Stat_Csv | tee c:\temp\voir.txt
        REM   Def : 
        REM   Capture output from a program and also display the output to the screen, at the same time.
    
        REM %~1 => Expand %1 removing any surrounding quotes (")
        set msg=%~2
        set sendtoLog=%3
        set addCounter=%4
    
        If !msg!==. (
            REM Write empty line
            echo!msg!
    
            If !sendtoLog! EQU 1 (
                echo!msg! >> %log_file%
            )
        ) else (
            REM (a) Write comment line (b) add counter if any
            If !addCounter! EQU 1 (
                set /a msgCounter+=1
                set msg=!msgCounter! - !msg!
    
                REM Pad counter left for single digit
                If !msgCounter! LSS 10 (
                    set msg=0!msg!
                )
            )
    
            REM Output to console
            echo !msg!
    
            REM Output to log
            If !sendtoLog! EQU 1 (
                echo !msg! >> %log_file%
            )
        )
    
        EXIT /B
    

    B) And here is how to call the 'SUB_STDOUT_MSG' in you 'main-logic' command file:

    REM ... some other code here
    
    REM ==============================================
    REM                PROGRAM END
    REM ==============================================
        set msg=.
        CALL :SUB_STDOUT_MSG !msg!, 1
        set msg="My programA - End"
        CALL :SUB_STDOUT_MSG !msg!, 1
        set msg="%date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%"
        CALL :SUB_STDOUT_MSG !msg!, 1
        set msg="+++++++++++++++"
        CALL :SUB_STDOUT_MSG !msg!, 1
    
        timeout 2 > Nul
    
    REM Skip all SUB ROUTINE
        GOTO :EOF
    
    
    REM ==============================================
    REM                CALL SUB ROUTINE
    REM ==============================================
    :SUB_STDOUT_MSG
        REM echo calling sub %0
    
        CALL "C:\Utilitaires\Financement\Utility_Sub.cmd" SUB_STDOUT_MSG %*
        EXIT /B
    
    :EOF
    
    0 讨论(0)
  • 2020-12-06 14:27

    the file with subroutines must look like:

    @echo off
    call :%*
    exit /b %errorlevel%
    
    :hello
    echo in hello
    exit /b 0
    :Demo
     echo in Demo with argument %1
     exit /b 0
    

    then from the other file you can call it like

    call file2.bat demo "arg-one"
    
    0 讨论(0)
提交回复
热议问题