从另一个批处理文件中调用批处理子程序

7

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,但是没有得到正确的结果。

请问我该如何实现呢?

4个回答

8

带有子程序的文件应该长这样:

@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的答案吗?我觉得你喜欢这种技巧,或者你已经知道了吗? - jeb
这对我来说看起来更干净。 - Tomer W
2
我更喜欢使用GOTO与SHIFT而不是CALL,因为CALL会使%加倍、毒字符转义和脱字符加倍的问题变得更加复杂。 - dbenham

7
您可以编写函数文件(在此示例中为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 

在这种情况下,标签需要在两个文件中使用相同的名称进行定义。
直接调用“library”批处理文件将替换 call:label 的上下文,当被调用的批处理文件被读取时,内部执行一个 goto:label 并继续在指定的标签内执行代码。当调用的批处理文件结束时,上下文被释放,并且在 call:label 之后的代码继续执行。
正如Jeb在评论中提到的那样,这种方法存在缺点。在调用的批处理文件中运行的代码无法使用%0 检索被调用的函数的名称,它将返回批处理文件的名称。但是,如果需要,呼叫者可以按示例代码执行此操作。
回答dbenham,我无法确定是否是编码错误还是故意特性,但这就是进程如何工作。
处理批处理文件中的行时,它们被视为内部的BatLoop函数,创建批处理的“上下文”。此函数作为其参数之一接收导致创建“上下文”的命令的指针。
在此函数内部,对批处理文件中的命令进行迭代。迭代命令的循环在每次迭代中进行测试:如果启用扩展名,则它是批处理文件中的第一行,并且启动上下文的命令的参数以冒号(标签)开头,则生成goto以跳转到标签。
至此,我必须假设这是处理 call:label 语法的故意行为:创建一个新的“上下文”,加载文件,跳转到标签。
但是,接收到的命令参数从未更改,使用不同的变量来跟踪批处理文件中命令的执行。如果将新的批处理文件加载到/覆盖当前批处理“上下文”中(我们没有使用 call 命令),则在加载新的批处理代码后,BatLoop 会重置行计数器(我们从已加载文件的第一行开始),并且条件在循环开始时(启用扩展名,第一行,冒号)再次成立(指向的输入命令未更改),并且会生成新的 goto

2
我喜欢这个技巧,只有一个小的“缺点”,在调用次要批处理函数时,%0 中包含的不是函数名,而是批处理文件名。但由于几乎没有人使用 %0 来检索当前函数名,所以这只是一个非常小的区别。 - jeb
@jeb,当然你是对的。考虑到有人可能需要函数名称,所以发布的示例代码包括一个echo来检索函数名称,然后将其余工作委托给“库”。现在已经包含在答案中了,谢谢。 - MC ND
2
隐式GOTO可以被任意深度地链接。library.cmd中的函数可以执行library2.cmd(无需CALL),而library2.cmd也将执行GOTO操作。我想知道这个特性是否是有意为之的?我无法想象为什么每次启动新脚本时GOTO会被缓存并执行,除非设计者想要这个特性。不管怎样,这个功能非常酷。 - dbenham
@dbenham,答案已更新以解释为什么执行了goto,但我无法确定它是否是有意的。 - MC ND

0

把目标标签作为被调用脚本的第一个参数提供怎么样?不过这样你需要修改被调用的脚本。

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"

0
稍微多说一点,这是我在阅读答案后编写的代码。它基本上为您提供了一个“实用程序”文件,在其中可以将您的子程序/函数添加到通用库中以进行代码维护。
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接