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

laximzn5  于 2022-12-30  发布在  Windows
关注(0)|答案(6)|浏览(126)

我的问题与this问题有关。我有几个需要从批处理文件执行的操作,我想将它们建模为函数并从主序列调用。从上面的问题可以清楚地看到,我可以使用调用语法来完成此操作

call:myDosFunc

我的问题是,我能不能把所有这些函数放在一个单独的批处理文件(functions.bat)中,然后以某种方式“包含”到主批处理文件中并调用它们?另一种选择是利用调用语法从main.bat调用functions.bat的可能性,但我不确定我是否能用一个特定的函数来调用它,而不是执行整个批处理文件。
简而言之,我正在寻找类似于C编程世界的东西,在C编程世界中,我的函数驻留在DLL中,主程序只包含高级逻辑,并从DLL调用函数。

gkn4icbw

gkn4icbw1#

我认为在批处理文件的开头使用路由函数并不是那么难看。您可以在"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 "抛出"的错误级别(exit/b移交当前错误级别)。使用第二个调用而不是goto,您可以确保"%1"=="param1"而不是func2。如果标签不存在,调用不会终止批处理文件,它只是将错误级别设置为1,并将错误消息放置为2(errorout),这可能会被重定向为nul。
说明:%* 包含所有参数,因此在示例中,第一行转换为:

call:func2 params1 param2 ... paramN
jfewjypa

jfewjypa2#

这里有一个简单的例子来说明如何做到这一点。
调用函数脚本时,函数名作为第一个参数,函数参数为arg 2、arg 3...
假设函数被正确调用,脚本会将参数移位并执行后藤操作到原始的arg 1。然后函数的参数会从新的arg 1开始。这意味着您可以使用已经编写好的例程并将它们放入实用程序中,而不必担心调整参数编号。
如果未提供函数参数,或者函数参数与脚本中的有效标签不匹配,则脚本将给出错误。

@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

我更喜欢上面使用的后藤方法,另一种选择是使用CALL,就像托马斯在他的回答中所做的那样。
有关使用CALL技术的实用批处理函数库的工作示例,请参见CHARLIB.BAT,这是一个用于处理批处理文件中的字符和字符串的例程库。here中提供了一个显示库开发的线程
我几年前写过CharLib.bat,如果我今天写它,我可能会用后藤而不是CALL。
引入CALL的问题是,当将字符串常量作为参数传递时,它会产生问题。额外的CALL意味着包含%的字符串常量必须额外地将百分比加倍。它还意味着需要额外地转义未加引号的有害字符,如&|。这两个问题可以由调用者解决。但真实的的问题是,每个CALL都会使引号中的插入符号加倍:"^"变为"^^"。没有解决插入符号加倍问题的好方法。
额外CALL的问题不会影响CharLib.bat,因为字符串值是通过引用(变量名)传递的,而不是作为字符串常量。
使用带有SHIFT /1的后藤的唯一缺点是不能使用%0来获取当前正在执行的例程的名称。我可以使用不带/1的SHIFT,但是这样就不能在例程中使用%~f0来获取正在执行的批处理文件的完整路径。

pxq42qpu

pxq42qpu3#

您可以使用此格式-并像这样启动它:

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
omqzjyyz

omqzjyyz4#

你可以使用一个有趣的技巧来避免其他方法在试图使库函数对主程序可用时所遇到的大多数问题,而且它要快得多。使用这个技巧的唯一必要条件是:

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

诀窍在于 “切换上下文” 的运行批处理文件的方式,库文件成为运行批处理文件;这样,库文件中的所有函数都可以在主代码块中使用,而不需要额外的处理。2当然,运行批处理文件的“上下文”必须在代码块结束之前切换回主文件。
“切换上下文”的方法是将库文件重命名为与正在运行的主文件相同的名称(并将主文件重命名为另一个名称)。例如:

(
   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
)

EDIT:* 添加了工作示例 *

我从屏幕上复制了下面的例子。在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
t30tvxxf

t30tvxxf5#

我不确定原始问题的上下文,但这可能是一个切换到类似WSH与VBScript或WPS,或任何其他控制台能力的脚本而不是批处理文件的情况。我将回答原始问题,但首先..一点背景知识和理解..
DOS和Windows的命令行/控制台模式通常是COMMAND.COM或CMD.EXE,这并不适合脚本/编程逻辑。相反,它们适合执行命令和程序,批处理文件被添加到常用的命令序列中,以 Package 在一个单一类型的命令中。例如,您可能有一个旧的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)的作用就像是每次运行批处理文件时一行一行地手动键入这些行。它确实没有像常规编程/脚本语言那样的词汇和作用域的坚实概念--也就是说,它没有像调用堆栈那样维护很多额外的元数据,它所维护的一点东西更多的是像是事后的想法而不是从一开始就内置到批处理文件中。
然而,一旦您改变了思路,您通常可以使用各种技巧和技术来模拟更强大的脚本/编程语言,从而克服这个限制;但是您仍然必须记住,无论如何,批处理文件仍然是有限的。
无论如何,使用批处理文件库的一种技术是创建一个批处理文件,其中第一个参数用于指示正在调用哪个函数:

CALL BATLIB.BAT FunctionName Parameter1 Parameter2 ...

如果在编写库时考虑到这一点,那么它就能很好地工作,因此它会知道跳过第一个参数,等等。
在Windows系统中使用更现代版本的CMD.EXE允许在CALL语法中使用“:labels”,如果您想限制参数范围(例如,允许您使用%* 表示“所有参数”),这会很有用,如下所示:

CALL :LABEL Parameter1 Paramater2 ...

(from在同一批处理文件中或...)

CALL BATLIB.BAT :LABEL Parameter1 Parameter2 ...

在第一种形式中,:LABEL必须已经在当前批处理文件中。它将在CMD.EXE中创建一个新的“批处理上下文”,其中%*、%1、%2等与参数匹配。但是您还必须提供某种返回/退出逻辑,以便从该上下文返回/退出到调用上下文。
在第二种形式中,CMD.EXE并不能真正识别您正在传递给它的标签,因此您的批处理文件库必须预期并处理它:

@ECHO OFF
CALL %*

这是因为命令解释器在解析CALL命令之前就替换了%,所以在变量扩展之后,CALL命令会看到:LABEL,就好像它是硬编码的一样。这也会造成CMD.EXE创建另一个批处理上下文的情况,所以你必须确保从那个上下文返回/退出两次:一次用于当前的库上下文,再次用于返回到原始CALL。
还有其他的方法来做一个批处理文件库,混合和匹配上述技术,或者使用更复杂的逻辑,使用后藤的,等等.这实际上是一个如此复杂的主题,有整个章节的书籍写的主题,远远超过我想在这里输入一个简单的答案!
到目前为止,我基本上忽略了您将遇到的其他问题:如果CALL标签不存在怎么办?如何处理?环境变量扩展怎么办?什么时候发生?如何防止它过早发生?在参数/参数中使用特殊DOS字符怎么办?例如,解释器如何看到这样的行:CALL:ProcessPath %PATH%?(答案是CMD.EXE
*处理CALL命令之前替换整个%PATH%**。如果您的路径中有空格,这可能会导致问题,这可能会影响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 Oops!”行中间的字节位置,因此它尝试在PAUSE之后将“ops!”作为命令处理。
我希望这能清楚地表明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”,后面跟着它自己的全名(%~ dpnx 0部分),CALL临时批处理文件,然后依次是ECHO的“Hi Mom!”,后面跟着它自己的[random]名称,然后返回到原始批处理文件,以便它可以执行任何需要的清理操作,例如在本例中删除临时批处理文件。
无论如何,从这篇文章的长度可以看出,这个主题并不简单,网上有很多网页提供了大量的批处理文件技巧、窍门等等,其中很多都深入到如何使用它们,创建批处理文件库,注意什么,如何通过引用与通过值传递参数,如何管理变量展开的时间和位置等等。
在谷歌上快速搜索“批处理文件编程”,找到其中的许多,你也可以查看Wiki和WikiBook,SS64.comrobvanderwoude.com,甚至DMOZ's directory,了解更多的资源。
祝你好运!

0x6upsns

0x6upsns6#

下面是一个cmd批处理脚本,它将文件文件夹中的文件**(递归) 导入主脚本

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

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

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

相关问题