My question is related to this question. I have several bunch of actions that need to be executed from a batch file and I would like to model them as functions and call from a master sequence. From the above question, it is clear that I can do this with the call syntax
call:myDosFunc
My question is that can I place all these functions in a seperate batch file (functions.bat) and somehow 'include' that in the main batch file and call them? Another option would be to utilize the possibility to invoke functions.bat from main.bat with the call syntaxt, but I'm not sure if I can invoke that with a specific function instead of executing the whole batch file.
In short, I'm looking for something similar to the C programming world where my functions reside in a DLL and the main program contains only the high-level logic and calls the functions from the DLL.
I think a routing function in the beginning of a batch file is not that ugly.
You can use something like this at the beginning of a "libbatch.cmd"
call:%*
exit/b
:func1
[do something]
exit/b
:func2
[do something else]
exit/b
Now you can call func2 from another batch with:
call libbatch.cmd func2 params1 param2 ... paramN
this also preserves the errorlevel "thrown" by func2 (exit/b hands over the current errorlevel).
With the second call instead of a goto you ensure that "%1"=="param1" and not func2.
And call will not terminate the batch file if the label does not exist, it simply sets the errorlevel to 1 and puts an error message to 2 (errorout), which could be redirected to nul.
Explanation: %* contains all parameters, so in the example the first line translates to:
call:func2 params1 param2 ... paramN
Here is a simple example of how it might be done.
The function script is called with the name of the function as the first argument, and function arguments as arg2, arg3, ...
Assuming it is called properly, the script shifts the arguments and performs GOTO to the original arg1. Then the function has its arguments starting with the new arg1. This means you can take already written routines and plop them in the utility without having to worry about adjusting the parameter numbers.
The script gives an error if the function argument is not supplied, or if the function argument does not match a valid label within the script.
#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
I prefer the GOTO approach that I used above. Another option is to use CALL instead, as Thomas did in his answer.
For a working example of a usefull library of batch functions that uses the CALL technique, see CHARLIB.BAT, a library of routines for processing characters and strings within a batch file. A thread showing development of the library is available here
I wrote CharLib.bat a few years ago. Were I to write it today, I would probably use GOTO instead of CALL.
The problem with introducing a CALL is that it creates issues when passing string literals as parameters. The extra CALL means that a string literal containing % must have the percents doubled an extra time. It also means unquoted poison characters like & and | would need to be escaped an extra time. Those two issues can be addressed by the caller. But the real problem is that each CALL doubles up quoted carets: "^" becomes "^^". There isn't a good way to work around the caret doubling problem.
The problems with the extra CALL don't impact CharLib.bat because string values are passed by reference (variable name) and not as string literals.
The only down side to using GOTO with SHIFT /1 is that you cannot use %0 to get the name of the currently executing routine. I could have used SHIFT without the /1, but then you wouldn't be able to use %~f0 within a routine to get the full path to the executing batch file.
You can use this format - and launch it like this:
call mybat :function4 parameternumber2 parameternumber3
this would be one way of using a library
#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
You may use an interesting trick that avoids most of the problems that other methods have when they try to make the library functions available to the main program and it is much faster. The only requisites to use this trick are:
The library functions must be called from inside a code block in the main file, and
In that code block no main file functions are called.
The trick consist in "switch the context" of the running Batch file in a way that the library file becomes the running Batch file; this way, all the functions in the library file becomes available to the main code block with no additional processing. Of course, the "context" of the running Batch file must be switched back to the main file before the code block ends.
The way to "switch the context" is renaming the library file with the same name of the running main file (and renaming the main file to another name). For example:
(
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: Working example added
I copied the example below from the screen. Tested in Windows 8, but I also used this method in 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
I'm not sure of the context of the original question, but this might be a case where switching to something like WSH with VBScript or WPS, or any other console-capable scripting other than batch files. I will answer the original question, but first.. a little background and understanding..
The command line/console mode of DOS and Windows is usually either COMMAND.COM or CMD.EXE, which isn't well-geared towards scripting/programming logic. Rather, they're geared towards executing commands and programs, and batch files were added to commonly used sequences of commands to wrapped up in a single typed command. For example, you may have an old DOS game you play that needs the following commands every time, so it's packaged as a batch file:
#EHO OFF
#REM Load the VESA driver fix..
VESAFIX.EXE
#REM Load the joystick driver..
JOYSTICK.COM
#REM Now run the game
RUNGAME.EXE
Many people tend to view the entire batch file as one atomic unit--But it's not. The command interpreter (COMMAND.COM or CMD.EXE) will merely act like you manually typed those lines, one by one, each time you run the batch file. It really has no solid concept of lexica and scoping like a regular programming/scripting language would--that is, it doesn't maintain much extra meta-data like a call stack, et cetera. What little it does maintain is more added like it's an afterthought rather than built-in to batch file from the beginning.
Once you shift the gears in your thinking, however, you can often overcome this limitation using various tricks and techniques to emulate more powerful scripting/programming languages; But you still have to remember that batch files are still going to be limited, regardless.
Anyhow, one technique of using a batch file library is to create a batch file where the first parameter is used to indicate which function is being called:
CALL BATLIB.BAT FunctionName Parameter1 Parameter2 ...
This works well enough when the library is written with this in mind, so it'll know to skip the first argument, et cetera.
Using more modern version of CMD.EXE in Windows' systems allows the use of ":labels" in the CALL syntax, which can be useful if you want to limit the parameter scope (which allows you to use %* for "all arguments", for example), like this:
CALL :LABEL Parameter1 Paramater2 ...
(from within the same batch file or ...)
CALL BATLIB.BAT :LABEL Parameter1 Parameter2 ...
A few notes about that, though.. In the first form, the :LABEL must be already within the current batch file. It will create a new "batch context" within CMD.EXE where %*, %1, %2, et cetera are matched to the parameters. But you'll also have to provide some kind of return/exit logic to return/exit from that context back to the calling context.
In the second form, CMD.EXE does not really recognize that you are passing it a label, so your batch file library will have to expect it and handle it:
#ECHO OFF
CALL %*
This works because the command interpreter replaces the %* before it even attempts to parse the CALL command, so after variable expansion, the CALL command would see the :LABEL as if it were hard-coded. This also creates a situation where CMD.EXE creates yet another batch context, so you'll have to make sure to return/exit from that context twice: Once for the current library context, again to get back to the original CALL.
There are still other ways to do a batch file library, mixing and matching the above techniques, or using even more complex logic, using GOTO's, et cetera. This is actually such a complex topic that there are entire sections of books written on the topic, much more than I want to type in a simple answer here!
And so far, I've mostly ignored other issues you will encounter: What if the CALL label does not exist? How will it be handled? What about environment variable expansion? when does it happen? How can you prevent it from happening too soon? What about using special DOS characters in the arguments/parameters? For example, how does the interpreter see a line like: CALL :ProcessPath %PATH%? (The answer to that is that CMD.EXE replaces the entire %PATH% before it even processes the CALL command. This can create issues if your path has spaces in it which can trip up how CALL processes the entire thing, as many Windows' %PATH% variables do.. C:\Program Files.. for example..)
As you can see, things are getting complicated and messy very quickly.. And you have to stop thinking like a programmer and start thinking like COMMAND.COM/CMD.EXE, which pretty much only sees one single line at a time, not the whole batch file as an atomic unit. In fact, here's an example to help you really grasp the way it works..
Create a folder, C:\testing, and put the following batch, file called "oops.bat", in it:
#ECHO OFF
ECHO Look mom, no brain!
PAUSE
ECHO Bye mom!
Now open a console window and run it, but let it sit there at the PAUSE:
C:\testing>oops.bat
Look mom, no brain!
Press any key to continue . . .
While it's sitting at the PAUSE, open oops.bat in your text editor and change it to:
#ECHO OFF
ECHO Look mom, no brain!?
ECHO Oops!
PAUSE
ECHO Bye mom!
Save it, then switch back to your console window and press any key to continue running the batch file:
'ops!' is not recognized as an internal or external command,
operable program or batch file.
Press any key to continue . . .
Bye mom!
c:\testing>
Whoa.. see that error there? That happened because we edited the batch file while it was still being run by CMD.EXE, but our edits changed where in the batch file CMD.COM thought it was. Internally, CMD.EXE maintains a file pointer indicating the start of the next character to process, which in this case would have been the byte right after the line with the PAUSE (and the CRLF) on it. But when we edited it, it changed the location of the next command in the batch file, but CMD.EXE's pointer was still in the same place. In this case, it was pointing to the byte position right in the middle of the "ECHO Oops!" line, so it tried to process "ops!" as a command after the PAUSE.
I hope this makes it clear that COMMAND.COM/CMD.EXE will always see your batch files as a stream of bytes, not as a logical block, subroutines, et cetera, like a script language or compiler would. This is why batch file libraries are so limited. It makes it impossible to "import" a library in to a currently running batch file.
Oh, and I just had another thought.. In modern Windows' CMD.EXE, you can always create a batch file which creates a temporary batch file on the fly, then calls it:
#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%
This effectively creates a temporary batch file in your temporary directory, named TMP####.BAT (where the #'s are replaced by random numbers; The %RANDOM:~0,1% means take the first digit of the number returned by %RANDOM%--we only wanted one single digit here, not the full number that RANDOM returns..), then ECHO's "Hello, World," followed by it's own full name (the %~dpnx0 part), CALLs the temporary batch file, which in turn ECHO's "Hi Mom!" followed by it's own [random] name, then returns to the original batch file so it can do whatever cleanup is needs, such as deleting the temporary batch file in this case.
Anyhow, as you can see by the length of this post, this topic really is not a simple one. There's dozens or more web pages out on the web with tons of batch file tips, tricks, et cetera, many of which go in to depth about how to work with them, create batch file libraries, what to watch out for, how to pass arguments by reference vs. by value, how to manage when and where variables get expanded, and so on.
Do a quick Google search for "BATCH FILE PROGRAMMING" to find many of them, and you can also check out Wiki and WikiBooks, SS64.com, robvanderwoude.com and even DMOZ's directory with more resources.
Good luck!
Here's a cmd batch script, which imports files or files in folders (recursivelly) into the main script:
#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
To use it:
save it as import.cmd
call it with the /install flag in order to install it (does not require admin)
add a header like this at the begining of your main script that calls subroutines that are stored in other files - files that are going to be imported:
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"
)
To find out how to use it, simply call it with the /? flag.