【问题标题】:Batch file how to call another batch file at a specified label or call and immediately goto a certain label?批处理文件如何在指定标签处调用另一个批处理文件或调用并立即转到某个标签?
【发布时间】:2012-07-19 13:09:45
【问题描述】:

我试图弄清楚 file1.bat 如何在指定的标签处调用 file2.bat。

我想我可以这样做:

File1.bat

:config
@echo off
:setvars
set labelmarker=labelmarker
call file2.bat
pause > nul
:EOF

File2.bat

if %labelmarker%==labelmarker goto label4
:label1
echo it won't work...
goto EOF
:label2
echo it must work!
goto EOF
:label3
echo it didn't work...
goto EOF
:label4
echo it works!
goto EOF
:EOF

这行得通。但我想从 file1.bat 中调用一个蝙蝠和标签。 是否可以使用控制字符或ascii代码或任何东西? 就像我试过的那样

call file2.bat | goto label4 - doesn't work
call file2.bat > goto label4 - doesn't work
call file2.bat @label4 - doesn't work

任何帮助将不胜感激。

即使涉及到将特定标签和内容提取到新文件中也可以。

【问题讨论】:

  • 我认为它可以与call file2.bat:label4 或类似的东西一起使用。不幸的是,似乎没有这种可能性(除了破解file2.bat,但这有时可能是不可能的)。

标签: label call batch-file


【解决方案1】:

您可以将要转到的标签作为参数传递

示例脚本

First.bat

@echo off
set label=GOHERE
call Second.bat %label%
pause >nul

Second.bat

@echo off
goto %1
echo This line should be skipped
:GOHERE
echo Jumped here

【讨论】:

    【解决方案2】:

    你可以使用一个奇怪的技巧
    您可以在第二批中转到标签,而无需在第二批中调用它!

    First.bat

    @echo off
    call :label
    echo returned
    exit /b
    
    :label
    second.bat
    exit /b
    

    Second.bat

    @echo off
    echo Main of second.bat
    exit /b
    
    :label
    echo This is second.bat at LABEL
    exit /b
    

    输出

    This is second.bat at LABEL
    returned
    

    似乎没有原因为什么调用标签,也没有为什么控件应该返回到 first.bat,因为第二批 调用 没有 打电话
    第一点的原因似乎是goto命令的内部代码。
    第二点可以解释,因为在第一个批处理文件中的 dummy 标签之前有一个调用。
    second.bat 中的exit /b 直接返回到对first.bat 的调用(第3 行),而不是在第7 行调用second.bat

    编辑:如何禁用奇怪的行为

    如果您在second.bat 处附加命令,它将不再隐式跳转到 second.bat 中的标签。

    second.bat & rem 将输出更改为 输出

    Main of second.bat
    returned
    

    【讨论】:

    • +1,我完全忘记了那种奇怪的行为。好记性。
    • @dbenham,我找到了一种禁用它的方法,使用 & REM
    • 有趣。如果“呼叫”括在括号中,它也会被禁用 - (second.bat)
    • 我可以添加一个我已经使用了很长时间的技巧:second.bat %* 在 second.bat 之后添加 %* 并且参数的圣杯将向您鞠躬。如果你不这样做,参数将不会被传递。两者都有相同的行为。但是,您可以通过这种方式剥离参数(“清理”调用,对循环调用很有用)
    【解决方案3】:

    有几种方法或技巧可以做到这一点。在这个解决方案中,技巧在于将正在运行的程序的上下文更改为第二个批处理文件。在此更改之后,第二个批处理文件中的所有标签都成为正在运行的程序的本地标签,因此可以通过通常的call :label 命令直接调用它们,该命令还可以以完全标准的方式包含参数。运行程序的上下文必须在程序终止前恢复。

    更改上下文组成的方法只是重命名第二个批处理文件与第一个批处理文件同名(并将第一个批处理文件重命名为任何其他名称)所以当批处理文件处理器看起来对于一个标签,它真的会搜索第二个文件的内容!在程序终止之前,必须将文件重命名回其原始名称。使该方法起作用的关键在于,两组重命名命令(以及它们之间的所有代码)都必须括在括号中;这样,代码首先被解析,然后从内存中执行,所以磁盘上文件的重命名不影响它。当然,这意味着对代码中所有变量的值的访问必须通过延迟!扩展! (或使用call %%variable%% 技巧)。

    First.bat

    @echo off
    
    rem Change the running context to the second/library batch file:
    (
    ren first.bat temporary.bat
    ren second.bat first.bat
    
    rem From this point on, you may call any label in second.bat like if it was a local label
    
    call :label2
    echo Returned from :label2
    call :label1
    echo Returned from :label1
    
    rem Recover the running context to the original batch file
    ren first.bat second.bat
    ren temporary.bat first.bat
    )
    
    echo This is first.bat ending...
    pause > nul
    

    Second.bat

    @echo off
    
    :label1
    echo I am label1 subroutine at second.bat
    goto :EOF
    
    :label2
    echo This is the second subroutine at second.bat
    goto :EOF
    

    输出

    This is the second subroutine at second.bat
    Returned from :label2
    I am label1 subroutine at second.bat
    Returned from :label1
    This is first.bat ending...
    

    您应该注意,此方法允许以通常的方式开发第一个文件中的子例程(无需更改上下文),然后只需将完成的子例程移动到第二个/库文件,因为在这两种情况下,调用它们的方式完全相同。这种方法不需要对第二个/库文件进行任何更改,也不需要对子程序的调用方式进行任何更改;它只需要在第一个文件中插入两个小块的重命名命令。


    编辑发生运行时错误时恢复文件

    如注释中所述,如果在重命名文件后发生运行时错误,则不会将文件重命名回其原始名称;但是,通过 second.bat 文件的存在很容易检测到这种情况,如果这样的文件不存在,则恢复文件。这个测试可以在一个独立的 cmd.exe 上下文中运行原始代码的主管部分中完成,因此即使原始代码因错误而取消,该部分也将始终正确完成。

    First.bat

    @echo off
    
    if "%~1" equ "Original" goto Original
    
    rem This supervisor section run the original code in a separate cmd.exe context
    rem and restore the files if an error happened in the original code
    (
    cmd /C "%~F0" Original
    if not exist second.bat (
       echo Error detected!  Restoring files
       ren first.bat second.bat & ren temporary.bat first.bat
    )
    goto :EOF
    )
    
    
    :Original
    
    ( ren first.bat temporary.bat & ren second.bat first.bat
    
    call :label1
    echo Returned from :label1
    call :label2
    echo Returned from :label2
    
    ren first.bat second.bat & ren temporary.bat first.bat )
    
    echo This is first.bat ending...
    

    Second.bat

    @echo off
    
    :label1
    echo I am label1 subroutine at second.bat
    exit /B
    
    :label2
    set "var="
    if %var% equ X echo This line cause a run-time error!
    exit /B
    

    输出

    I am label1 subroutine at second.bat
    Returned from :label1
    No se esperaba X en este momento.
    Error detected!  Restoring files
    

    【讨论】:

    • 奇怪的事情......但如果你仔细想想的话,这是有道理的。也许关于需要延迟扩展(在块内)的说明会有所帮助。
    • @Stephan:好点。我添加了它。点赞怎么办? :)
    • 我赞成它,因为这个概念很酷。但是当发生错误时,您的解决方案有缺点,因为那时文件将无法恢复
    【解决方案4】:

    我刚刚创建了一个可能对您有用的脚本。我认为它的优势在于您不必以任何方式修改被调用的脚本。我敢肯定,只要花一点时间,您就可以完善它并使其符合您的目的。我还可以认为,当调用的脚本很大时,它对于生产使用的速度不够快。此方法包括将我的脚本放置到您自己的脚本文件夹中,然后像这样使用它:

    编辑

    现在这个脚本可以在任何目录下工作,并且可以使用被调用脚本的完整或相对路径输入,带或不带文件扩展名(C:\scripts\script、C:\scripts\script.bat、.\scripts \script.cmd 等)

    用法:

    给定一个示例脚本script.bat

    :LABEL_1
    ECHO This is label number one!
    GOTO :EOF
    
    :LABEL_2
    ECHO This is label number two!
    GOTO :EOF
    

    以下输入:

    CALLAT script :LABEL_2
    

    应该输出:

    This is label number two!
    

    CALLAT.bat的源代码

    @ECHO OFF
    SETLOCAL EnableDelayedExpansion
    @ECHO OFF
    
    rem Create a folder in the temporary files directory
    MKDIR "%TEMP%\CALLAT\"> NUL
    
    rem Chose a name for a temporal script
    SET TEMP_SCRIPT="%TEMP%\CALLAT\temp-script.bat"
    
    rem Get the full path to the called script
    FOR /F "delims=" %%F IN ('DIR /B /S "%1*" 2^>NUL') DO SET ORIGINAL_SCRIPT=%%F
    
    rem Add this lines to go directly to the desired label in the temp script
    ECHO @ECHO OFF> %TEMP_SCRIPT%
    ECHO GOTO %2>> %TEMP_SCRIPT%
    
    rem Concatenate the called script to the end of the temp script
    COPY /B %TEMP_SCRIPT%+%ORIGINAL_SCRIPT% %TEMP_SCRIPT%>NUL
    
    rem Call the modified script
    CALL %TEMP_SCRIPT%
    
    rem Clean up
    RD "%TEMP%\CALLAT\" /S /Q>NUL
    
    ENDLOCAL
    

    【讨论】:

    • 我接受您的回答,但有条件和改进。条件是第二个 IF 语句不正确,因为它重复了第一个。改进之处在于,正如所写,当您传入完整的文件名(扩展名和所有)时,脚本会失败,它不会自行清理,并且可以用连接副本替换循环。请参阅我的下一个“答案”了解更多信息。
    【解决方案5】:

    正如我在附上我的答案的评论中所说,我授予了赏金,因为@NicoBerrogorry 花时间设计了一个出色的解决方案,并提供了完整的文章。

    尽管如此,他忽略了一个语法错误,可能是因为他不使用 CMD 文件,而我几乎完全放弃了 BAT 文件。以下 CALLAT 脚本包含一项更正和四项增强功能。

    1. 它处理完全指定的命令,即指定文件名带有扩展名的命令。
    2. 它更正了导致它忽略 CMD 文件的原始疏忽。
    3. 当根本找不到目标文件时,它会显示一条错误消息。
    4. 每次追加一行文件的 for 循环被替换为绝对老式的 COPY 命令,该命令将目标脚本文件追加到两行使整个事情起作用的序言。如果您忘记或不知道它,复制命令末尾的 > NUL: 会丢弃“已复制 1 个文件”消息。
    5. 虽然原版完成了它的使命,但它并没有自行清理;此版本通过删除临时脚本文件和创建它的临时目录来实现这一点。

    @ECHO OFF
    SETLOCAL EnableDelayedExpansion
    
    rem Create a folder in the temporary files directory.
    IF NOT EXIST "%TEMP%\CALLAT\" MKDIR "%TEMP%\CALLAT\"
    
    rem Chose a name for the modified script.
    SET MODIFIED_SCRIPT="%TEMP%\CALLAT\modified_%1.bat"
    
    rem Convert the called script name to the corresponding .bat
    rem or .cmd full path to the called script.
    
    rem Then load the script. - 2017/05/08 - DAG - The second if exist test duplicated the first. Instead, it must evaluate for the .CMD extension.
    
    SET "COMMAND_FILE=%~dp0%1"
    IF EXIST "%COMMAND_FILE%.bat" (
        SET "COMMAND_FILE=%COMMAND_FILE%.bat"
    ) ELSE IF EXIST "%COMMAND_FILE%.cmd" (
        SET "COMMAND_FILE=%COMMAND_FILE%.cmd"
    ) else if exist "%COMMAND_FILE%" (
    	echo INFO: Using "%COMMAND_FILE%"
    ) else (
    	echo ERROR: Cannot find script file %COMMAND_FILE%.bat
    	echo                             or %COMMAND_FILE%.cmd
    )
    
    rem Add some lines to go directly to the desired label.
    ECHO @ECHO OFF> %MODIFIED_SCRIPT%
    ECHO GOTO %2>> %MODIFIED_SCRIPT%
    
    rem Append the called script.
    copy %MODIFIED_SCRIPT%+%COMMAND_FILE% %MODIFIED_SCRIPT% > NUL:
    
    rem Call the modified script.
    CALL %MODIFIED_SCRIPT%
    
    rem Pack out your trash. When a script ends, you get an ENDLOCAL for free.
    del %MODIFIED_SCRIPT%
    rd "%TEMP%\CALLAT\"

    最后,这个版本的 CALLAT 消除了多余的 ECHO OFF 和结尾的 ENDLOCAL,我早就发现这是不必要的,因为退出一个脚本意味着 ENDLOCAL

    由于实际上我的所有生产脚本都位于一个可通过 PATH 目录列表访问的目录中,因此两个脚本位于同一目录中的要求并不是一个重大障碍。此外,只要多做一点工作,几乎肯定可以放松。我将把它作为练习留给感兴趣的读者,但我会给你一个线索;它可能涉及调用一个内部子程序,该子程序可以通过利用[命令行参数]中描述的技术来解析输入文件的名称。1

    上面显示的脚本即将投入生产。

    【讨论】:

    • 嗨,谢谢你,我很高兴能帮上忙。我只是在您的更正后发布我的编辑。我以不同的方式解决了“查找脚本的完整路径”部分,但我以相同的方式完成了连接部分。无论如何,我都会发布我的编辑,以表明我已尝试纳入您的更正。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-24
    • 1970-01-01
    相关资源
    最近更新 更多