批处理文件中有类似于函数/方法的东西吗?

88

有没有像Java、C#中那样的方法可以模仿?我在批处理文件中有5行命令,这5行命令在批处理文件的不同位置都会被用到。我不能使用goto,因为根据这5行命令创建的errorlevel会有不同的后续操作。我尝试将这5行命令放入一个名为5lines.bat的批处理文件中,但原始的批处理文件original.bat只调用了5lines.bat而并未执行调用5lines.bat之后的命令:

这是我的original.bat文件的样子:

5lines.bat
echo this gets never called, how to make sure this gets called?

5lines.bat文件中没有退出或类似的东西!我该如何确保5lines.bat后面的行被调用?


请查看标记为CALL a subroutine (:label)的部分。 - bobobobo
10个回答

92
你可以使用 call 命令:
call:myDosFunc

然后以这种方式定义函数:

:myDosFunc    - here starts the function
echo.  here the myDosFunc function is executing a group of commands
echo.  it could do a lot of things
goto:eof

来源: 批处理函数


谢谢你,调用命令确实是我缺少的。 - stefan.at.kotlin
9
goto :eofexit /b 是相同的,它们都会在调用 CALL 之后立即返回。 - jeb
如果我理解正确的话,这就像你调用了同一批文件,从:myDosFunc行开始。 - insan-e
@jeb 它没有做它意味着什么的事情吗?不需要创建自己的返回点吗? - Sandburg

53

仅为了完整起见,您也可以向函数传递参数:

函数调用

call :myDosFunc 100 "string val"

函数体

:myDosFunc
echo. Got Param#1 %~1
echo. Got Param#2 %~2
goto :eof

35

将可重复使用的函数放入一个单独的批处理文件中肯定可以模拟一个函数。

关键是您必须使用call命令,以确保在第二个批处理文件执行完毕后控制返回到调用者。

call 5lines.bat
echo this will now get called

3
有趣的是,如果你将5lines.bat重命名为5lines.cmd,你会发现只使用call 5lines就足够了,不需要指定扩展名。 - Noir

24

解决方案:

@ECHO OFF     

call:header Start Some Operation

... put your business logic here
... make sure EXIT below is present
... so you don't run into actual functions without the call

call:header Operation Finished Successfully

EXIT /B %ERRORLEVEL%

:: Functions

:header
ECHO ================================================= 
ECHO %*
ECHO ================================================= 
EXIT /B 0

在每个函数结尾以及函数定义开始之前,都要重要地加上EXIT /B。例如,在我的例子中:

EXIT /B %ERRORLEVEL%

这样做可以使内容更加易懂。


7

作为一个有Java背景的人,我尝试在创建.bat脚本中引入一些熟悉的约定。

下面的脚本演示了定义两个过程的方法。

@ECHO OFF
SET firstInstanceVariable="Hello world!"
SET secondInstanceVariable="Good bye world!"
GOTO:MAIN

:firstMethodName
    SETLOCAL ENABLEDELAYEDEXPANSION
        SET firstArgumentPassedIn=%~1
        SET secondArgumentPassedIn=%~2
        
        ECHO %firstInstanceVariable%
        ECHO "The first argument passed in was %firstArgumentPassedIn%"
        ECHO "The second argument passed in was %secondArgumentPassedIn%"
    ENDLOCAL
EXIT /B 0

:secondMethodName
    SETLOCAL ENABLEDELAYEDEXPANSION
        SET firstArgumentPassedIn=%~1
        SET secondArgumentPassedIn=%~2
        
        ECHO %secondInstanceVariable%
        ECHO "The first argument passed in was %firstArgumentPassedIn%"
        ECHO "The second argument passed in was %secondArgumentPassedIn%"
    ENDLOCAL
EXIT /B 0


:MAIN
call:firstMethodName "The Quick Brown" "Fox Jumps Over"
call:secondMethodName "1 2 3 4" 3.14

注意,需要显式的 GOTO:MAIN 命令来跳过过程定义。 这是因为在决定读取之前必须跳过过程。否则,过程将被执行。

下面的代码演示了一个接近上述 .bat 脚本的 Java 等效版本。

public class MyObject {
    private String firstInstanceVariable = "Hello world!";
    private String secondInstanceVariable = "Good bye world!";
    public void firstMethodName(Object... arguments) {
        String firstArgumentPassedIn = arguments[0].toString();
        String secondArgumentPassedIn = arguments[1].toString();
        System.out.println(firstInstanceVariable);
        System.out.format("The first argument passed in was %s", firstArgumentPassedIn);
        System.out.format("The second argument passed in was %s", secondArgumentPassedIn);
    }

    public void secondMethodName(Object... arguments) {
        String firstArgumentPassedIn = arguments[0].toString();
        String secondArgumentPassedIn = arguments[1].toString();
        System.out.println(secondInstanceVariable);
        System.out.format("The first argument passed in was %s", firstArgumentPassedIn);
        System.out.format("The second argument passed in was %s", secondArgumentPassedIn);
    }

    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        myObject.firstMethodName("The Quick Brown", "Fox Jumps Over");
        myObject.secondMethodName(new Integer[]{1,2,3,4}, 3.14);
    }
}

7
你可以尝试使用 DOS 批处理函数教程 上列出的示例。
或者,你可以将常用行放入另一个批处理文件中,然后从主文件中调用该文件。

1
很棒的链接!(尽管最终我只是使用了call 5lines.bat) - stefan.at.kotlin
链接确实很棒,我下次找的时候加了更明显的文本;-) - Wolf

5

这里有一个“黑科技”,可以让你在批处理文件中使用"匿名"函数:

@echo off
setlocal 
set "anonymous=/?"

:: calling the anonymous function
call :%%anonymous%% a b c 3>&1 >nul

:: here the anonymous function is defined
if "%0" == ":%anonymous%" (
  echo(
  echo Anonymous call:
  echo %%1=%1 %%2=%2 %%3=%3
  exit /b 0
)>&3
::end of the anonymous function

匿名函数块应该放在调用语句后面,并且必须以退出语句结束。
技巧在于,CALL 内部使用 GOTO 然后返回到执行 CALL 的行。通过双重扩展,GOTO 帮助消息被触发(带有 %%/?%% 参数),然后继续脚本。但是,在完成后它会返回到 CALL - 这就是为什么需要 if 语句的原因。

3

我不确定其他回答是否已经明确,但为了表述清楚我发表这个答案。在编写下面的代码时,我发现其他答案很有帮助。

echo what
rem the third param gives info to which label it should comeback to
call :myDosFunc 100 "string val" ComeBack

:ComeBack
echo what what
goto :eof

:myDosFunc
echo. Got Param#1 %~1
echo. Got Param#2 %~2
set returnto=%~3
goto :%returnto%

3
为什么要使用“goto :%returnto%”?“goto :eof”或者只是“文件结束”会自动返回到跟在“call…”后面的那一行(这就是“call”的目的),并且不会在栈上留下孤立的JumpBackAddress。过多的这些会导致脚本出现错误提示而崩溃。 - Stephan

3

2
以下可能看起来像一个函数。
call :myFunc %para1% %para2%

:myFunc <para1> <para2>
    echo %1
    echo %2
    EXIT /B

例子

@echo off
echo PROGRAM_NAME:%~nx0 Start
echo.
================================

SET debugMode=%1
call :myFunc1 %debugMode%
call :myFunc2 para1 "para2 hello"

================================
echo PROGRAM_NAME:%~nx0 End & pause>nul
EXIT /B

::  define the function under below
:myFunc1 <isDebug>
    :: So that you can know the %1 means: isDebug.
    if "%1" == "1" (
        echo debug mode
    )
    EXIT /B

:myFunc2 <para1> <para2>
    :: output: para1
    echo %1

    :: output: "para2 hello"
    echo %2
    EXIT /B

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