【问题标题】:Call a subroutine in a batch from another batch file从另一个批处理文件中批量调用子程序
【发布时间】:2015-05-11 12:48:18
【问题描述】:

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:

:hello
echo in hello
:Demo
 echo in Demo

从批处理文件 1 我想调用批处理文件 2 中的子例程。
例如,我尝试了call file2.bat:Demo,但它没有给出正确的结果。

我怎样才能做到这一点?

【问题讨论】:

    标签: batch-file


    【解决方案1】:

    带有子程序的文件必须如下所示:

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

    然后你可以从另一个文件中调用它

    call file2.bat demo "arg-one"
    

    【讨论】:

    • 你看过MCND的答案了吗?我想你喜欢这种技巧还是你已经知道了?
    • 这对我来说看起来更干净
    • 我更喜欢将 GOTO 与 SHIFT 一起使用而不是 CALL,因为 CALL 会导致 % 加倍、毒字符转义和插入符号加倍的问题。
    【解决方案2】:

    您可以将函数文件(在此示例中为library.cmd)编写为

    @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
    

    然后调用者批处理可以是类似的东西

    @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 
    

    在这种情况下,标签需要在两个文件中定义为相同的名称。

    “库”批处理文件的直接调用将替换call :label 的上下文,当读取调用的批处理时,会在内部执行goto :label,代码在指示的标签内继续。当被调用的批处理文件结束时,上下文被释放,call :label之后的代码继续执行。

    已编辑

    正如 Jeb 在 cmets 中指出的那样,这种方法有一个缺点。在被调用的批处理文件中运行的代码不能使用%0 来检索被调用函数的名称,它将返回批处理文件的名称。但如果需要,调用者可以按照示例代码所示进行。

    于 2016 年 12 月 27 日编辑

    回答dbenham,我无法知道这是编码错误还是预期的功能,但这就是过程的工作方式

    当创建批处理“上下文”时,批处理文件中的行在内部BatLoop 函数中进行处理。该函数接收作为其参数之一的指向导致创建“上下文”的命令的指针。

    在这个函数中,批处理文件中的命令被迭代。遍历命令的循环在每次迭代中进行测试:如果启用了扩展,则它是批处理文件中的第一行,并且启动上下文的命令的参数以冒号(标签)开头,@987654330生成@ 以跳转到标签。

    到这里为止,我不得不假设这是处理call :label 语法的预期行为:创建一个新的“上下文”,加载文件,跳转到标签。

    但是收到的命令参数永远不会改变,一个不同的变量用于跟踪批处理文件中命令的执行。如果一个新的批处理文件被加载到/覆盖当前批处理“上下文”(我们没有使用call命令),在加载新的批处理代码后,BatLoop重置行数(我们从第一行开始加载的文件),瞧,循环开始的条件(启用扩展,第一行,冒号)再次为真(指向的输入命令未更改)并生成新的goto

    【讨论】:

    • 我喜欢这个技巧,只有一个小缺点,在二次批处理中被调用的函数%0不包含函数名它包含批处理文件名,但由于几乎没有人使用%0 来检索当前函数名,所以差别很小
    • @jeb,你当然是对的。考虑到有人可能需要函数名,发布的示例代码包括一个echo,在将其余工作委托给“库”之前检索函数名。现在它包含在答案中,谢谢。
    • 隐式 GOTO 可以任意深度链接。 library.cmd 中的函数可以执行 library2.cmd(无需 CALL),并且 library2.cmd 也会执行 GOTO。我想知道这个功能是故意的吗?我无法想象为什么每次新脚本启动时 GOTO 都会被缓存并执行,除非设计人员想要这个功能。无论哪种方式都非常酷。
    • @dbenham, answer 更新解释了为什么goto 被执行,但我无法确定它是否是故意的。
    【解决方案3】:

    要多加评论,这里是我阅读答案出来的代码。它基本上为您提供了一个“实用程序”文件,您可以在其中将您的 sub/fnc 添加到一个通用库中以进行代码维护。

    A) 例如,这里是 'utility_sub.cmd' 文件:

    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) 下面是如何在“主逻辑”命令文件中调用“SUB_STDOUT_MSG”:

    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
    

    【讨论】:

      【解决方案4】:

      如果提供目标标签作为被调用脚本的第一个参数呢?你需要修改被调用的 dscript。

      file1.bat(主):

      @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(子):

      @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
      

      输出:

      >>> 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"
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-07-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-24
        相关资源
        最近更新 更多