如何将批处理文件中的所有函数打包为一个单独的文件?

时间:2013-09-11 13:10:38

标签: windows command-line batch-file

我的问题与this问题有关。我有几堆需要从批处理文件执行的操作,我想将它们建模为函数并从主序列调用。从上面的问题来看,很明显我可以使用调用语法

来做到这一点
call:myDosFunc

我的问题是,我可以将所有这些功能放在一个单独的批处理文件(functions.bat)中,并以某种方式'包含'在主批处理文件中并调用它们吗?另一种选择是利用调用syntaxt从main.bat调用functions.bat的可能性,但我不确定是否可以使用特定函数调用它而不是执行整个批处理文件。

简而言之,我正在寻找类似于C编程世界的东西,我的函数驻留在DLL中,主程序只包含高级逻辑,并从DLL中调用函数。

6 个答案:

答案 0 :(得分:8)

我认为批处理文件开头的路由功能并不那么难看。 您可以在“ libbatch.cmd ”的开头使用类似的内容

    call:%*
    exit/b

:func1
    [do something]
    exit/b

:func2
    [do something else]
    exit/b

现在您可以使用以下命令从另一个批处理中调用func2:

call libbatch.cmd func2 params1 param2 ... paramN

这也保留了由func2“抛出”的错误级别(退出/ b移交当前的错误级别)。 使用第二个调用而不是goto,确保“%1”==“param1”而不是func2。 如果标签不存在,调用将不会终止批处理文件,它只是将errorlevel设置为1并将错误消息放入2(错误输出),这可以重定向到nul。

说明:%*包含所有参数,因此在示例中,第一行转换为:

call:func2 params1 param2 ... paramN

答案 1 :(得分:6)

以下是一个如何完成的简单示例。

调用函数脚本时,函数名称为第一个参数,函数参数为arg2,arg3,...

假设它被正确调用,脚本会移动参数并执行GOTO到原始arg1。然后该函数的参数以新的arg1开头。这意味着您可以执行已编写的例程并将其放入实用程序中,而无需担心调整参数编号。

如果未提供函数参数,或者函数参数与脚本中的有效标签不匹配,则脚本会出错。

@echo off
if "%~1" neq "" (
  2>nul >nul findstr /rc:"^ *:%~1\>" "%~f0" && (
    shift /1
    goto %1
  ) || (
    >&2 echo ERROR: routine %~1 not found
  )
) else >&2 echo ERROR: missing routine
exit /b

:test1
echo executing :test1
echo arg1 = %1
exit /b

:test2
echo executing :test2
echo arg1 = %1
echo arg2 = %2
exit /b

:test3
echo executing :test3
echo arg1 = %1
echo arg2 = %2
echo arg3 = %3
exit /b

我更喜欢上面使用的GOTO方法。另一个选择是使用CALL,正如Thomas在他的回答中所做的那样。

有关使用CALL技术的有用批处理函数库的工作示例,请参阅CHARLIB.BAT,这是一个用于处理批处理文件中的字符和字符串的例程库。显示库开发的线程可用here

几年前我写过CharLib.bat。如果我今天写它,我可能会使用GOTO而不是CALL。

引入CALL的问题在于,在将字符串文字作为参数传递时会产生问题。额外的CALL意味着包含%的字符串文字必须使百分比加倍一倍的额外时间。它还意味着需要将额外时间转义为&|等不带引号的毒药字符。调用者可以解决这两个问题。但真正的问题是,每个CALL都会引用引用的插入符号:"^"变为"^^"。没有一种解决插入符号倍增问题的好方法。

额外CALL的问题不会影响CharLib.bat,因为字符串值是通过引用(变量名称)传递的,而不是字符串文字。

使用带有SHIFT / 1的GOTO的唯一不利方面是你不能使用%0来获取当前正在执行的例程的名称。我可以在没有/ 1的情况下使用SHIFT,但是你不能在例程中使用%~f0来获取执行批处理文件的完整路径。

答案 2 :(得分:3)

您可以使用此格式 - 并按以下方式启动:

call mybat :function4 parameternumber2 parameternumber3

这将是使用库的一种方式

@echo off
goto %1

:function1
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof

:function2
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof

:function3
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof

:function4
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof

答案 3 :(得分:3)

你可以使用一个有趣的技巧来避免其他方法在尝试使库函数可用于主程序时遇到的大多数问题而且速度要快得多。使用这个技巧的唯一要求是:

  • 必须从主文件中的代码块内部调用库函数,并且
  • 在该代码块中,不会调用主文件函数。

技巧包括“切换正在运行的批处理文件的上下文”,其方式是库文件成为正在运行的批处理文件;这样,库文件中的所有函数都可用于主代码块而无需额外处理。当然,在代码块结束之前,必须将正在运行的批处理文件的“上下文”切换回主文件。

“切换上下文”的方法是使用与正在运行的主文件相同的名称重命名库文件(并将主文件重命名为另一个名称)。例如:

(
   rem Switch the context to the library file
   ren main.bat orig-main.bat
   ren library.bat main.bat
   rem From this point on, any library function can be called
   . . . .
   rem Switch back the context to the original one
   ren main.bat library.bat
   ren orig-main.bat main.bat
)

编辑已添加工作示例

我从屏幕上复制了下面的例子。在Windows 8中测试过,但我也在Win XP中使用了这个方法:

C:\Users\Antonio\Documents\test
>type main.bat
@echo off
(
   rem Switch the context to the library file
   ren main.bat orig-main.bat
   ren library.bat main.bat
   rem From this point on, any library function can be called, for example:
   echo I am Main, calling libFunc:
   call :libFunc param1
   echo Back in Main
   rem Switch back the context to the original one
   ren main.bat library.bat
   ren orig-main.bat main.bat
)

C:\Users\Antonio\Documents\test
>type library.bat
:libFunc
echo I am libFunc function in library.bat file
echo My parameter: %1
exit /B

C:\Users\Antonio\Documents\test
>main
I am Main, calling libFunc:
I am libFunc function in library.bat file
My parameter: param1
Back in Main

答案 4 :(得分:1)

我不确定原始问题的上下文,但这可能是使用VBScript或WPS切换到WSH之类的东西,或者除了批处理文件之外的任何其他支持控制台的脚本。我会回答原来的问题,但首先......一点背景和理解......

DOS和Windows的命令行/控制台模式通常是COMMAND.COM或CMD.EXE,它们不适合脚本/编程逻辑。相反,它们面向执行命令和程序,并且批处理文件被添加到常用的命令序列中以包装在单个类型的命令中。例如,您可能有一个旧的DOS游戏,每次都需要以下命令,因此它被打包为批处理文件:

@EHO OFF
@REM Load the VESA driver fix..
VESAFIX.EXE
@REM Load the joystick driver..
JOYSTICK.COM
@REM Now run the game
RUNGAME.EXE

许多人倾向于将整个批处理文件视为一个原子单元 - 但事实并非如此。命令解释器(COMMAND.COM或CMD.EXE)的作用就像每次运行批处理文件时一个接一个地手动键入这些行。它确实没有像lexica那样坚实的概念,并且像普通的编程/脚本语言一样具有范围 - 也就是说,它不会像调用堆栈那样维护更多的额外元数据,等等。它所做的一点点维护更多的是它是一个事后的想法,而不是从一开始就内置到批处理文件中。

然而,一旦你改变了想法,你就可以使用各种技巧和技术来克服这种限制,以模拟更强大的脚本/编程语言;但你仍然需要记住批处理文件仍然有限,无论如何。

无论如何,使用批处理文件库的一种技术是创建一个批处理文件,其中第一个参数用于指示调用哪个函数:

CALL BATLIB.BAT FunctionName Parameter1 Parameter2 ...

当编写库时考虑到这一点,这很有效,因此它会知道跳过第一个参数,等等。

在Windows中使用更现代版的CMD.EXE'系统允许使用":标签"在CALL语法中,如果你想限制参数范围(允许你使用%*作为"所有参数",例如),这可能很有用,如下所示:

CALL :LABEL Parameter1 Paramater2 ...

(来自同一个批处理文件或......)

CALL BATLIB.BAT :LABEL Parameter1 Parameter2 ...

关于这一点的一些注意事项..但在第一种形式中,:LABEL必须已经在当前批处理文件中。它将创建一个新的"批处理上下文"在CMD.EXE中,%*,%1,%2等等与参数匹配。但是你还必须提供某种返回/退出逻辑来从该上下文返回/退出回调用上下文。

在第二种形式中,CMD.EXE并没有真正认识到你传递了一个标签,所以你的批处理文件库将不得不期待它并处理它:

@ECHO OFF
CALL %*

这是因为命令解释器在尝试解析CALL命令之前替换了%*,因此在变量扩展之后,CALL命令会看到:LABEL就好像它是硬编码的一样。这也会造成CMD.EXE创建另一个批处理上下文的情况,因此您必须确保两次返回/退出该上下文:对于当前库上下文,再次返回到原​​始CALL。

还有其他方法可以使用GOTO等等来完成批处理文件库,混合和匹配上述技术,或者使用更复杂的逻辑。这实际上是一个复杂的主题,有关于该主题的书籍的整个部分,远远超过我想在这里输入一个简单的答案!

到目前为止,我大多忽略了您将遇到的其他问题:如果CALL标签不存在怎么办?如何处理?环境变量扩展怎么样?什么时候发生?你怎么能防止它太快发生?如何在参数/参数中使用特殊的DOS字符?例如,解释器如何看到如下行:CALL:ProcessPath%PATH%? (答案是CMD.EXE _ 取代 整个%PATH% _ 之前它甚至处理CALL命令。如果你的路径中有空格可以解决CALL如何处理整个事情,这可能会产生问题,因为很多Windows'%PATH%变量都可以... C:\ Program Files ..例如.. )

正如你所看到的,事情变得越来越复杂和混乱。你必须停止像程序员一样思考并开始思考COMMAND.COM/CMD.EXE,它几乎只能看到一行一行,而不是整个批处理文件作为原子单位。事实上,这是一个帮助你真正掌握其运作方式的例子。

创建一个文件夹C:\ testing,并将以下批处理文件名为" oops.bat",其中:

@ECHO OFF
ECHO Look mom, no brain!
PAUSE
ECHO Bye mom!

现在打开一个控制台窗口并运行它,但让它坐在PAUSE:

C:\testing>oops.bat
Look mom, no brain!
Press any key to continue . . .

当它坐在PAUSE中时,在文本编辑器中打开oops.bat并将其更改为:

@ECHO OFF
ECHO Look mom, no brain!?
ECHO Oops!
PAUSE
ECHO Bye mom!

保存,然后切换回控制台窗口并按任意键继续运行批处理文件:

'ops!' is not recognized as an internal or external command,
operable program or batch file.
Press any key to continue . . .
Bye mom!
c:\testing>

哇..看到那个错误?之所以发生这种情况,是因为我们在CMD.EXE仍在运行时编辑了批处理文件,但我们的编辑更改了CMD.COM认为的批处理文件中的位置。在内部,CMD.EXE维护一个文件指针,指示要处理的下一个字符的开始,在这种情况下,它就是在PAUSE(和CRLF)的行之后的字节。但是当我们编辑它时,它改变了批处理文件中下一个命令的位置,但是CMD.EXE的指针仍然在同一个地方。在这种情况下,它指向正好位于" ECHO中间的字节位置!"线,所以它试图处理"操作!"作为PAUSE之后的命令。

我希望这清楚地表明COMMAND.COM/CMD.EXE将总是将批处理文件视为字节流,而不是逻辑块,子例程等等,就像脚本一样语言或编译器会。这就是批处理文件库如此有限的原因。这使得进口"一个库到当前正在运行的批处理文件。

哦,我还有另一个想法..在现代Windows' CMD.EXE,您始终可以创建一个批处理文件,该文件可以动态创建临时批处理文件,然后调用它:

@ECHO OFF
SET TEMPBAT=%TEMP%\TMP%RANDOM:~0,1%%RANDOM:~0,1%%RANDOM:~0,1%%RANDOM:~0,1%.BAT
ECHO @ECHO OFF > %TEMPBAT%
ECHO ECHO Hi Mom! I'm %TEMPBAT%! >> %TEMPBAT%
ECHO Hello, world, I'm %~dpnx0!
CALL %TEMPBAT%
DEL %TEMPBAT%

这有效地在临时目录中创建一个临时批处理文件,名为TMP ####。BAT(其中#'被随机数替换;%RANDOM:~0.1%表示取%RANDOM%返回的数字的第一个数字 - 我们这里只需要一个数字,而不是RANDOM返回的完整数字..),然后是ECHO" Hello,World,"然后是它自己的全名(%~dpnx0部分),调用临时批处理文件,而这又是ECHO"嗨妈妈!"然后是它自己的[随机]名称,然后返回到原始批处理文件,以便它可以执行任何需要的清理,例如在这种情况下删除临时批处理文件。

无论如何,正如你可以看到这篇文章的篇幅,这个话题真的不是一个简单的话题。网上有数十个或更多的网页,有大量的批处理文件提示,技巧等等,其中许多都深入探讨了如何使用它们,创建批处理文件库,需要注意什么,如何通过引用和值传递参数,如何管理变量扩展的时间和位置,等等。

快速谷歌搜索"批量文件编程"找到它们中的许多,你还可以查看Wiki和WikiBooks,SS64.com,robvanderwoude.com甚至DMOZ的http://www.dmoz.org/Computers/Software/Operating_Systems/x86/DOS/Programming/Languages/Batch/目录以及更多资源。

祝你好运!

答案 5 :(得分:1)

这是我编写的cmd批处理脚本,导入 文件文件夹中的文件(递归)到主脚本中< / strong>:

@echo off
REM IMPORT - a .cmd utility for importing subroutines into the main script
REM Author: Ioan Marin

REM !!! IN ORDER TO FUNCTION CORRECTLY:                                                       !!!
REM !!! IMPORT MUST BE CALLED INSIDE A DISABLED DELAYED EXPANSION BLOCK/ENVIRONMENT (DEFAULT) !!!


    rem \\// Define import file mask here:
    rem If mask is not defined outside "import.cmd":
    if not defined mask (
        set "mask=*.cmd; *.bat"
    )
    rem //\\ Define import file mask here:

    rem Detect if script was started from command line:
    call :DetectCommandLine _not_started_from_command_line

    if "%~1" == "/install" (
        set "import_path=%~dp0"
        call :EscapePathString import_path import_path_escaped
    )

    if not "%~1" == "" (
        if /i not "%~1" == "end" (
            if "%~1" == "/?" (
                call :DisplayHelp
            ) else (
                if "%~1" == "/install" (
                    echo Installing
                    set "_first_time="

                    rem This should get into the Autorun registry key: path %path%;"...\import.cmd"
                    rem     If you want, other commands can be added to the left or to the right of this command, unified as a block with a "&" command operator
                    REG ADD "HKCU\Software\Microsoft\Command Processor" /v AutoRun /t REG_SZ /d "path %%path%%;"""%import_path_escaped%""||(
                        echo ERROR: Cannot install import: cannot write to the registry^!
                        echo You can try to manually add the "import.cmd" path in the "PATH" variable or use pushd ^(see help^).
                        if not "%_not_started_from_command_line%" == "0" (
                            call :PressAnyKey Press any key to exit...
                            echo.
                        )
                        exit /b 1
                    )

                    echo.
                    echo Done. The install directory was set to: 
                    echo "%import_path%"
                    echo and was added to the PATH environment variable. You can add other desired programs into this directory.
                    echo.
                    echo Please note that the console needs to be closed and reopened in order for the changes to take effect^!
                    echo.
                ) else (
                    if not defined _first_time (
                        set _first_time=defined
                        set /a count=0
                        if "%_DISPLAY_WARNING%" == "true" (
                            echo.
                            echo WARNING: CMD_LIBRARY was reset to "", because previously it gave an error^!
                            echo.
                        )
                        echo Loading list to import...
                    )
                    REM build import files list 

                    set /a count+=1
                    call set "_import_list_%%count%%=%%~1"
                )
            )
        )
    ) else (
        call :DisplayHelp
    )

    if /i "%~1" == "end" (

        set "_first_time="

        echo Done.
        echo Analyzing...

        rem set "_main_program=%~dpnx2"

        if not exist "%~dpnx2" (
            echo ERROR: Second parameter, after "import end", must be a valid file path - see help^!>>&2

            rem Clean up
            call :CleanUp

            if not "%_not_started_from_command_line%" == "0" (
                call :PressAnyKey Press any key to exit...
                echo.
            )
            exit /b 1
        )
    )

    if /i "%~1" == "end" (
        set "_main_batch_script=%~dpnx2"

        rem \\// Define output filename here:
        rem set "_output_filename=tmp0001_%~n2.cmd"
        set "_output_filename=tmp0001.cmd"
        rem //\\ Define output filename here:
    )

    if /i "%~1" == "end" (
        rem Check all paths not to be UNC:
        setlocal EnableDelayedExpansion
            set "_error=false"

            call :TestIfPathIsUNC _main_batch_script _result
            if "!_result!" == "true" (
                set "_error=true"
                echo. 
                echo ERROR: UNC paths are not allowed: Second parameter, after "import end", must not be a UNC path^^^! Currently it is: "!_main_batch_script!">>&2
            )

            set "_CMD_LIBRARY_error=false"
            call :TestIfPathIsUNC CMD_LIBRARY _result
            if "!_result!" == "true" (
                set "_error=true"
                set "_CMD_LIBRARY_error=true"
                echo.
                echo ERROR: UNC paths are not allowed: CMD_LIBRARY variable must not contain a UNC path^^^! Currently, it is set to: "!CMD_LIBRARY!".>>&2
            )

            for /l %%i in (1,1,!count!) do (
                call :TestIfPathIsUNC _import_list_%%i _result
                if "!_result!" == "true" (
                    set "_error=true"
                    echo.
                    echo ERROR: UNC paths are not allowed: The import path: "!_import_list_%%i!" is a UNC path^^^!>>&2
                )
            )

            if "!_error!" == "true" (
                echo.
                echo Errors were ecountered^^^!

                if "!_CMD_LIBRARY_error!" == "true" (
                    endlocal
                    set "_CMD_LIBRARY_error=true"
                ) else (
                    endlocal
                    set "_CMD_LIBRARY_error=false"
                )

                rem Clean up
                call :CleanUp

                if not "%_not_started_from_command_line%" == "0" (
                    call :PressAnyKey Press any key to exit...
                    echo.
                )
                exit /b 1
            ) else (
                endlocal
                set "_CMD_LIBRARY_error=false"
            )
    )

    if /i "%~1" == "end" (
        rem Check all paths not to contain "*" and "?" wildcards:
        set "_asterisk=*"
        set "_qm=?"

        setlocal EnableDelayedExpansion
            set "_error=false"

            call :TestIfStringContains _main_batch_script _asterisk _result1
            call :TestIfStringContains _main_batch_script _qm _result2
            if "!_result1!" == "true" (
                set "_error=true"
            )
            if "!_result2!" == "true" (
                set "_error=true"
            )
            if "!_error!" == "true" (
                echo. 
                echo ERROR: The use of "*" or "?" wildcards is not supported by import: Second parameter, after "import end", must not contain "*" or "?" wildcards^^^! Currently it is: "!_main_batch_script!". Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
            )

            set "_CMD_LIBRARY_error=false"
            call :TestIfStringContains CMD_LIBRARY _asterisk _result1
            call :TestIfStringContains CMD_LIBRARY _qm _result2
            if "!_result1!" == "true" (
                set "_error=true"
            )
            if "!_result2!" == "true" (
                set "_error=true"
            )
            if "!_error!" == "true" (
                set "_error=true"
                set "_CMD_LIBRARY_error=true"
                echo.
                echo ERROR: The use of "*" or "?" wildcards is not supported by import: CMD_LIBRARY variable must not contain "*" or "?" wildcards^^^! Currently, it is set to: "!CMD_LIBRARY!". Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
            )

            for /l %%i in (1,1,!count!) do (
                call :TestIfStringContains _import_list_%%i _asterisk _result1
                call :TestIfStringContains _import_list_%%i _qm _result2
                if "!_result1!" == "true" (
                    set "_error=true"
                )
                if "!_result2!" == "true" (
                    set "_error=true"
                )
                if "!_error!" == "true" (
                    set "_error=true"
                    echo.
                    echo ERROR: The use of "*" or "?" wildcards is not supported by import: The import path: "!_import_list_%%i!" must not contain "*" or "?" wildcards^^^! Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
                )
            )

            if "!_error!" == "true" (
                echo.
                echo Errors were ecountered^^^!

                if "!_CMD_LIBRARY_error!" == "true" (
                    endlocal
                    set "_CMD_LIBRARY_error=true"
                ) else (
                    endlocal
                    set "_CMD_LIBRARY_error=false"
                )

                rem Clean up
                call :CleanUp

                if not "%_not_started_from_command_line%" == "0" (
                    call :PressAnyKey Press any key to exit...
                    echo.
                )
                exit /b 1
            ) else (
                endlocal
                set "_CMD_LIBRARY_error=false"
            )
    )

    if /i "%~1" == "end" (
        pushd "%~dp2"
            call set "_output_dir=%%CD%%"
        popd
    )

    if /i "%~1" == "end" (

        if not defined CMD_LIBRARY (

            set CMD_LIBRARY_CASE=IMPORT.CMD

            set "BASE=%~dpnx0\.."

            pushd "%~dpnx0"\..\

                REM \\// Define CMD LIBRARY here ("." is relative to "import.cmd" parent directory):
                REM if CMD_LIBRARY is not defined outside import.cmd, "." (used here) is related to import.cmd parent directory:
                set "CMD_LIBRARY=."
                REM //\\ Define CMD LIBRARY here ("." is relative to "import.cmd" parent directory):

        ) else (

            set CMD_LIBRARY_CASE=MAIN.CMD

            set "BASE=%~dpnx2\.."

            REM if CMD_LIBRARY is defined outside the "import.cmd" script, "." (used in CMD_LIBRARY) is related to "main program" parent directory
            pushd "%~dpnx2"\..

        )
    )

    if /i "%~1" == "end" (

        call :DeQuoteOnce CMD_LIBRARY CMD_LIBRARY
        call set "CMD_LIBRARY_ORIGINAL=%%CMD_LIBRARY%%"

        call :TestIfPathIsUNC CMD_LIBRARY_ORIGINAL _result
        setlocal EnableDelayedExpansion
            if "!_result!" == "true" (
                set "_error=true"

                echo.
                echo ERROR: UNC paths are not allowed: CMD_LIBRARY variable must not contain a UNC path^^^! Currently, it is set to: "!CMD_LIBRARY_ORIGINAL!".>>&2

                echo.
                echo Errors were ecountered^^^!

                endlocal
                set "_CMD_LIBRARY_error=true"

                rem Clean up
                call :CleanUp

                if not "%_not_started_from_command_line%" == "0" (
                    call :PressAnyKey Press any key to exit...
                    echo.
                )
                exit /b 1
            ) else (
                endlocal
                set "_CMD_LIBRARY_error=false"
            )

        call pushd "%%CMD_LIBRARY%%" >nul 2>nul&&(
            call set "CMD_LIBRARY=%%CD%%"
        )||(
            call echo ERROR: Could not access directory CMD_LIBRARY=^"%%CMD_LIBRARY%%^"^!>>&2

            call :CleanUp

            if not "%_not_started_from_command_line%" == "0" (
                call :PressAnyKey Press any key to exit...
                echo.
            )
            popd
            exit /b 1
        )

    )

    if /i "%~1" == "end" (
        setlocal EnableDelayedExpansion
            set _error=false
            pushd "!BASE!"
            echo.
            if "!CMD_LIBRARY_CASE!" == "IMPORT.CMD" (
                echo CMD_LIBRARY was defined as: "!CMD_LIBRARY_ORIGINAL!" in the file "import.cmd" and was expanded to: "!CMD_LIBRARY!"
            ) else (
                if "!CMD_LIBRARY_CASE!" == "MAIN.CMD" (
                    echo CMD_LIBRARY was defined as: "!CMD_LIBRARY_ORIGINAL!" outside "import.cmd" file "%~nx2" and was expanded to: "!CMD_LIBRARY!"
                ) 
            )
                for /l %%i in (1,1,!count!) do (
                    if not exist "!_import_list_%%i!" (
                        if not exist "!CMD_LIBRARY!\!_import_list_%%i!" (
                            rem if first time:
                            if not "!_error!" == "true" (
                                echo.
                                echo Directory of "!CMD_LIBRARY!":
                            )

                            echo.
                            echo ERROR: element "!_import_list_%%i!" does not exist or is not accessible as a standalone file/dir or as a file/dir in the directory contained by "CMD_LIBRARY" variable^^^!>>&2
                            set _error=true
                        )
                    )
                )
            popd
            if "!_error!" == "true" (
                endlocal

                rem Clean up
                call :CleanUp

                if not "%_not_started_from_command_line%" == "0" (
                    call :PressAnyKey Press any key to exit...
                    echo.
                )
                exit /b 1
            ) else (
                endlocal
            )
        echo OK
        echo.

    )

    set "_error=false"
    if /i "%~1" == "end" (

        echo Output file is: "%_output_dir%\%_output_filename%"
        echo.
        echo Importing...
        echo.
        (
            type nul>"%_output_dir%\%_output_filename%"
        ) 2>nul||(
            echo ERROR: Could not write to file: "%_output_dir%\%_output_filename%"^!>>&2

            rem Clean up
            call :CleanUp

            if not "%_not_started_from_command_line%" == "0" (
                call :PressAnyKey Press any key to exit...
                echo.
            )
            exit /b 1
        )

        echo Importing main script "%_main_batch_script%"
        (
            echo @set _import=defined
            echo @REM Timestamp %date% %time%

            echo.
        )>>"%_output_dir%\%_output_filename%"
        (
            (
                type "%_main_batch_script%"
            )>>"%_output_dir%\%_output_filename%"
        ) 2>nul||(echo  ERROR: Could not read file^!&set "_error=true">>&2)
        (
            echo.
            echo.
        )>>"%_output_dir%\%_output_filename%"
        echo.

        echo Directory of "%CMD_LIBRARY%":
        if not "%CMD_LIBRARY_CASE%" == "MAIN.CMD" (
            pushd "%BASE%"
        )
        if not defined mask (
            rem If mask is not defined, import all file types:
            set "mask=*"
        )
        for /l %%i in (1,1,%count%) do (
            call set "_import_list_i=%%_import_list_%%i%%"
            call :ProcedureImportCurrentFile
        )
        if not "%CMD_LIBRARY_CASE%" == "MAIN.CMD" (
            popd
        )
    )

    if "%~1" == "end" (
        if "%_error%" == "true" (
            echo.
            echo Errors were ecountered^!

            rem Clean up
            call :CleanUp

            if not "%_not_started_from_command_line%" == "0" (
                call :PressAnyKey Press any key to exit...
                echo.
            )
            exit /b 1
        ) else (
            echo Done^!
        )

        call popd
        popd

        rem Clean up
        call :CleanUp

        rem Detect if script was started from command line:
        call :DetectCommandLine _not_started_from_command_line
    )
    if "%~1" == "end" (
        if "%_not_started_from_command_line%" == "0" (
            set "_import="
        ) else (
            echo.
            echo Starting program...
            echo.
            rem Start "resulting" program:
            "%_output_dir%\%_output_filename%"
        )
    )

goto :eof

REM \\\/// Next subroutines use jeb's syntax for working with delayed expansion: \\\///

:CleanUp
(   
    setlocal EnableDelayedExpansion
)
(
    endlocal

    if "%_CMD_LIBRARY_error%" == "true" (
        set "CMD_LIBRARY="
        set "_DISPLAY_WARNING=true"
    ) else (
        set "_DISPLAY_WARNING=false"
    )
    set "_first_time="
    for /l %%i in (1,1,%count%) do (
        set "_import_list_%%i="
    )
    rem optional:
    set "count="
    set "import_path="
    rem set "_output_dir="
    set "_error="
    set "_main_batch_script="
    rem set "_output_filename="
    rem set "_import="
    set "mask="

    exit /b
)

:GetStrLen - by jeb - adaptation
(   
    setlocal EnableDelayedExpansion
        set "s=!%~1!#"
        set "len=0"
        for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
            if "!s:~%%P,1!" NEQ "" ( 
                set /a "len+=%%P"
                set "s=!s:~%%P!"
            )
        )
)
( 
    endlocal
    set "%~2=%len%"
    exit /b
)

:EscapePathString

(   
    setlocal EnableDelayedExpansion
        set "string=!%~1!"
        call :GetStrLen string string_len
        set /a string_len-=1

        for /l %%i in (0,1,!string_len!) do (
            rem escape "^", "(", ")", "!", "&"
            if "!string:~%%i,1!" == "^" (
                set "result=!result!^^^^"
            )  else (
                if "!string:~%%i,1!" == "(" (
                    set "result=!result!^^^("
                ) else (
                    if "!string:~%%i,1!" == ")" (
                        set "result=!result!^^^)"
                    ) else (
                        if "!string:~%%i,1!" == "^!" (
                            set "result=!result!^^^!"
                        ) else (
                            if "!string:~%%i,1!" == "&" (
                                set "result=!result!^^^&"
                            ) else (
                                if "!string:~%%i,1!" == "%%" (
                                    set "result=!result!%%"
                                ) else (
                                    set "result=!result!!string:~%%i,1!"
                                )
                            )
                        )
                    )
                )
            )
        )
)
(
    endlocal
    set "%~2=%result%"
    exit /b
)

:PressAnyKey
    set /p=%*<nul
    pause>nul
goto :eof

:DeQuoteOnce
(
    setlocal EnableDelayedExpansion
        set "string=!%~1!"
        if "!string!" == """" (
            endlocal
            set "%~2=%string%"
            exit /b
        )
        rem In order to work with " we replace it with a special character like < > | that is not allowed in file paths:
        set "string=!string:"=^<!"

        if "!string:~0,1!" == "<" (
            if "!string:~-1,1!" == "<" (
                set "string=!string:~1,-1!"
            )
        )
        rem restore " in string (replace < with "):
        set "string=!string:<="!"
)
(
    endlocal
    set "%~2=%string%"
    exit /b
)

:TestIfPathIsUNC

(
    setlocal EnableDelayedExpansion
        set "_current_path=!%~1!"
        set "_is_unc_path=true"
        if defined _current_path (
            if "!_current_path:\\=!" == "!_current_path!" (
                set "_is_unc_path=false"
            )
        ) else (
            set "_is_unc_path=false"
        )
)
(
    endlocal
    set "%~2=%_is_unc_path%"
    exit /b
)

:TestIfStringContains

(
    setlocal EnableDelayedExpansion
        echo "!%~1!"|find "!%~2!">nul 2>nul
        set "_error_code=!ERRORLEVEL!"
)
(
    endlocal
    if "%_error_code%" == "0" (
        set "%~3=true"
    ) else (
        set "%~3=false"
    )
    exit /b
)

REM ///\\\ The subroutines above use jeb's syntax for working with delayed expansion: ///\\\

:DetectCommandLine

setlocal
    rem Windows: XP, 7
    for /f "tokens=*" %%c in ('echo "%CMDCMDLINE%"^|find "cmd /c """ /c') do (
        set "_not_started_from_command_line=%%~c"
    )
    if "%_not_started_from_command_line%" == "0" (
        rem Windows: 10
        for /f "tokens=*" %%c in ('echo "%CMDCMDLINE%"^|find "cmd.exe /c """ /c') do (
            set "_not_started_from_command_line=%%~c"
        )
    )
endlocal & (
    set "%~1=%_not_started_from_command_line%"
)
goto :eof

:ProcedureImportCurrentFile

setlocal
    set "cc="

    if not exist "%_import_list_i%" (
        set "_not_a_dir=false"
        pushd "%CMD_LIBRARY%\%_import_list_i%\" 1>nul 2>&1||set "_not_a_dir=true"
        call :GetStrLen CD _CD_len
    )
    if "%_not_a_dir%" == "false" (
        setlocal EnableDelayedExpansion
            if not "!CD:~-1,1!" == "\" (
                endlocal
                set /a _CD_len+=1
            ) else (
                endlocal
            )
        popd
    )

    if not exist "%_import_list_i%" (
        if "%_not_a_dir%" == "true" (
            echo Importing file "%CMD_LIBRARY%\%_import_list_i%"
            (
                type "%CMD_LIBRARY%\%_import_list_i%">>"%_output_dir%\%_output_filename%"
            ) 2>nul||(
                echo  ERROR:   Could not read file^!>>&2
                set "_error=true"
            )
            (
                if not "%%i" == "%count%" (
                    echo.
                    echo.
                ) else (
                    echo.
                )
            )>>"%_output_dir%\%_output_filename%"
        ) else (
            echo Importing dir "%_import_list_i%"
            rem
            pushd "%CMD_LIBRARY%\%_import_list_i%\"

                set /a cc=0
                for /r %%f in (%mask%); do (
                    set "_current_file=%%~dpnxf"
                    call set "r=%%_current_file:~%_CD_len%%%"
                    call echo   Importing subfile "%%_import_list_i%%\%%r%%"
                    (
                        (
                            call type "%%_current_file%%"
                        )>>"%_output_dir%\%_output_filename%"
                    ) 2>nul||(
                        echo     ERROR: Could not read file^!>>&2
                        set "_error=true"
                    )
                    (
                        echo. 
                        echo. 
                    )>>"%_output_dir%\%_output_filename%"
                    set /a cc+=1
                )
                popd
        )
    ) else (
        set "_not_a_dir=false"
        pushd "%_import_list_i%\" 1>nul 2>&1||set "_not_a_dir=true"
        call :GetStrLen CD _CD_len
    )
    if "%_not_a_dir%" == "false" (
        setlocal EnableDelayedExpansion
            if not "!CD:~-1,1!" == "\" (
                endlocal
                set /a _CD_len+=1
            ) else (
                endlocal
            )
        popd
    )

    if exist "%_import_list_i%" (
        if "%_not_a_dir%" == "true" (
            echo Importing file "%_import_list_i%"
            (
                type "%_import_list_i%">>"%_output_dir%\%_output_filename%"
            ) 2>nul||(
                echo    ERROR: Could not read file^!>>&2
                set "_error=true"
            )
            (
                if not "%%i" == "%count%" (
                    echo.
                    echo.
                ) else (
                    echo.
                )
            )>>"%_output_dir%\%_output_filename%"
        ) else (
            rem
            echo Importing dir "%_import_list_i%"
            pushd "%_import_list_i%\"

            set /a cc=0
            for /r %%f in (%mask%); do (
                set "_current_file=%%~dpnxf"
                call set "r=%%_current_file:~%_CD_len%%%"
                call echo   Importing subfile "%%_import_list_i%%\%%r%%"
                (
                    (
                        call type "%%_current_file%%"
                    )>>"%_output_dir%\%_output_filename%"
                ) 2>nul||(
                    echo     ERROR: Could not read file^!>>&2
                    set "_error=true"
                )
                (
                    echo. 
                    echo. 
                )>>"%_output_dir%\%_output_filename%"
                set /a cc+=1
            )
            popd
        )
    )
    if "%cc%" == "0" (
        echo   No match^!
    )
endlocal & (
    set "_error=%_error%"
)
goto :eof

:DisplayHelp
    echo IMPORT - a .cmd utility for importing subroutines into the main script
    echo.
    echo NOTES: 1. This utility assumes that command extensions are enabled (default) and that delayed expansion can be enabled;
    echo           ALSO IMPORT MUST BE CALLED INSIDE A DISABLED DELAYED EXPANSION BLOCK/ENVIRONMENT (DEFAULT);
    echo           These are necessary in order for it to function correctly.
    echo        2. The use of UNC paths is not supported by import. As a workarround, you can mount a UNC path to a temporary drive using "pushd".
    echo           The use of "*" or "?" wildcards is not supported by import. Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat"
    echo           When the "mask" variable is set, only the filenames having the extensions contained by it are matched at import.
    echo.
    echo Description:
    echo    import organizes your batch programs on common libraries of subroutines, that you can use in the future for other programs that you build; it also makes code editing and debugging easier. 
    echo.
    echo Usage [1]:
    echo    import [flags]
    echo.
    echo    [flags] can be:
    echo            /install - installs import into the registry, in the Command Processor AutoRun registry key ^(adds the current location of import into the PATH variable^).
    echo            /? - displays help ^(how to use import^)
    echo.
    echo Usage [2]:
    echo    What it does:
    echo            Concatenates ^(appends^) files content containing subroutines to the main program content using the following SYNTAX:
    echo            REM \\//Place this in the upper part of your script ^(main program)^ \\//:
    echo.
    echo @echo off
    echo.
    echo            if not defined _import ^(
    echo                            rem OPTIONAL ^(before the "import" calls^):
    echo                            set "CMD_LIBRARY=^<library_directory_path^>"
    echo.
    echo                    import "[FILE_PATH1]filename1" / "DIR_PATH1"
    echo                    ...
    echo                    import "[FILE_PATHn]filenamen" / "DIR_PATHn"
    echo                    import end "%%~0"
    echo            ^)
    echo.
    echo            REM //\\Place this in the upper part of your script ^(main program)^ //\\:
    echo.
    echo            "filename1" .. "filenamen" represent the filenames that contain the subroutines that the user wants to import in the current ^(main^) program. The paths of these files are relative to the directory contained in the CMD_LIBRARY variable.
    echo.
    echo            "FILE_PATH1" .. "FILE_PATHn" represent the paths of these files.
    echo.
    echo            "DIR_PATH1" .. "DIR_PATHn" represent directories paths in which to recursivelly search and import all the files of the type defined in the variable "mask"
    echo.
    echo            CMD_LIBRARY is a variable that contains the directory path where your library of files ^(containing subroutines^) is found.
    echo.
    echo            We denote the script that calls "import" as "the main script".
    echo.
    echo            By default, if not modified in outside the import.cmd script, in the import.cmd script - CMD_LIBRARY is set to "." directory and is relative to the "import.cmd" parent directory.
    echo            If CMD_LIBRARY directory is modified outside the import.cmd script, CMD_LIBRARY is relative to the main script parent directory.
    echo.
    echo            Note that only the last value of "CMD_LIBRARY" encountered before `import end "%%~0"` is taken into consideration.
    echo.
    echo            import end "%%~0" - marks the ending of importing files and the start of building of the new batch file ^(named by default tmp0001.cmd, and located in the directory in which the main script resides^).
    echo.
    echo            "%%~0" represents the full path of the main script.
    echo.
    echo    Author: Ioan Marin
goto :eof

要使用它:

  • 另存为import.cmd

  • 使用/install标志对其进行调用以进行安装(不需要管理员)

  • 在您的主脚本的开头添加这样的标头,该标头调用存储在其他文件(将要导入的文件)中的子例程:

     if not defined _import (
             rem OPTIONAL (before the "import" calls):
             set "CMD_LIBRARY=<library_directory_path>"
    
         import "[FILE_PATH1]filename1" / "DIR_PATH1"
         ...
         import "[FILE_PATHn]filenamen" / "DIR_PATHn"
         import end "%~0"
     )
    

要了解如何使用它,只需使用/?标志对其进行调用。