there is a nice way to build functions in DOS .bat/.cmd script. To modularize some installation scripts, it would be nice to include a file with a library of functions into an .
I came up with a simple solution for using external libraries with batch files, and I would like to ask you guys to test it and find possible bugs.
How to use:
Principle of operation:
How it works:
Advantages:
Code:
Your_batch_file.bat
@echo off & setlocal EnableExtensions
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::Your code starts in :_main
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: v1.5 - 01/08/2015 - by Cyberponk - Fixed returning to original path when using RequestAdminElevation
:: v1.4 - 25/05/2015 - by Cyberponk
:: This module includes funcions from included libraries so that you can call
:: them inside the :_main program
::
:: Options
set "_DeleteOnExit=0" &:: if 1, %_TempFile% will be deleted on exit (set to 0 if using library RequestAdminElevation)
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
(if "%BatchLibraryPath%"=="" set "BatchLibraryPath=.") &set "_ErrorCode=" &set "#include=call :_include"
set _LibPaths="%~dp0";"%BatchLibraryPath%"&set "_TempFile=%TEMP%\_%~nx0"
echo/@echo off ^& CD /D "%~dp0" ^& goto:_main> "%_TempFile%" || (echo/Unable to create "%_TempFile%" &echo/Make sure the %%TEMP%% path has Read/Write access and that a file with the same name doesn't exist already &endlocal &md; 2>nul &goto:eof ) &type "%~dpf0" >> "%_TempFile%" &echo/>>"%_TempFile%" &echo goto:eof>>"%_TempFile%" &call :_IncludeLibraries
(if "%_ErrorCode%"=="" (call "%_TempFile%" %*) else (echo/%_ErrorCode% &pause)) & (if "%_DeleteOnExit%"=="1" (del "%_TempFile%")) & endlocal & (if "%_ErrorCode%" NEQ "" (set "_ErrorCode=" & md; 2>nul)) &goto:eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:_include lib
set "lib=%~1.bat" &set "_included="
(if EXIST "%lib%" ( set "_included=1" &echo/>> "%_TempFile%" &type "%lib%" >> "%_TempFile%" & goto:eof )) & for %%a in (%_LibPaths%) do (if EXIST "%%~a\%lib%" ( set "_included=1" &echo/>> "%_TempFile%" &type "%%~a\%lib%" >> "%_TempFile%" &goto:endfor))
:endfor
(if NOT "%_included%"=="1" ( set "_ErrorCode=%_ErrorCode%Library '%~1.bat' not fount, aborting...&echo/Verify if the environment variable BatchLibraryPath is pointing to the right path - and has no quotes - or add a custom path to line 25&echo/" )) &goto:eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:_IncludeLibraries - Your included libraries go here
::::::::::::::::::::::::::::::::::::::
:: You can add custom paths to this variable:
set _LibPaths=%_LibPaths%; C:\; \\SERVER\folder
:: Add a line for each library you want to include (use quotes for paths with space)
:: Examples:
:: %#include% beep
:: %#include% Network\GetIp
:: %#include% "Files and Folders\GetDirStats"
:: %#include% "c:\Files and Folders\GetDriveSize"
:: %#include% "\\SERVER\batch\SendHello"
%#include% Example
goto:eof
::End _IncludeLibraries
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:_main - Your code goes here
::::::::::::::::::::::::::::::::::::::
echo/Example code:
call :Example "It works!"
echo/____________________
echo/Work folder: %CD%
echo/
echo/This file: %0
echo/
echo/Library paths: %_LibPaths%
echo/____________________
echo/Argument 1 = %1
echo/Argument 2 = %2
echo/All Arguments = %*
echo/____________________
pause
.
Example.bat
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:Example msg
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
setlocal ENABLEEXTENSIONS & set "msg=%1"
echo/%msg%
endlocal & goto :EOF
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
If you want an easy way to set the %BatchLibraryPath%, just put this file inside your Library path and run it before running Your_batch_file.bat. This setting is persistent on reboots, so you run this once only:
SetBatchLibraryPath.bat
setx BatchLibraryPath "%~dp0"
pause
In the given link there's a script for importing subroutines into the main script.
The syntax is something like:
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"
)
<MAIN SCRIPT CODE>
...
Please see this answer.
okay... quick & dirty because I'm a UNIX guy... create your "library" file
1 @ECHO OFF<br>
2 SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION & PUSHD<br>
3 :: -----------------------------------------------<br>
4 :: $Id$<br>
5 :: <br>
6 :: NAME:<br>
7 :: PURPOSE:<br>
8 :: NOTES:<br>
9 :: <br>
10 :: INCLUDES --------------------------------- --<br>
11 :: DEFINES --------------------------------- --<br>
12 :: VARIABLES --------------------------------- --<br>
13 :: MACROS --------------------------------- --<br>
14 <br>
15 GOTO :MAINLINE<br>
16 <br>
17 :: FUNCTIONS --------------------------------- --<br>
18 <br>
19 :HEADER<br>
20 ECHO ^<HTML^><br>
21 ECHO ^<HEAD^><br>
22 ECHO ^<TITLE^>%1^</TITLE^><br>
23 ECHO ^</HEAD^><br>
24 ECHO ^<BODY^><br>
25 GOTO :EOF<br>
26 <br>
27 :TRAILER<br>
28 ECHO ^</BODY^><br>
29 ECHO ^</HTML^><br>
30 GOTO :EOF<br>
31 <br>
32 :: MAINLINE --------------------------------- --<br>
33 :MAINLINE<br>
34 <br>
35 IF /I "%1" == "HEADER" CALL :HEADER %2<br>
36 IF /I "%1" == "TRAILER" CALL :TRAILER<br>
37 <br>
38 ENDLOCAL & POPD<br>
39 :: HISTORY ------------------------------------<br>
40 :: $Log$<br>
41 :: END OF FILE --------------------------------- --<br>
this should be pretty straight-forward for you... at line 15 we make the jump to mainline to begin actual execution. At lines 19 and 27 we create entry points for our routines. At lines 35 and 36 are calls to the internal routines.
now, you build the file that will call the library routines..
1 @ECHO OFF<br>
2 SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION & PUSHD<br>
3 :: -----------------------------------------------<br>
4 :: $Id$<br>
5 :: <br>
6 :: NAME:<br>
7 :: PURPOSE:<br>
8 :: NOTES:<br>
9 :: <br>
10 :: INCLUDES --------------------------------- --<br>
11 <br>
12 SET _LIB_=PATH\TO\LIBRARIES\LIBNAME.BAT<br>
13 <br>
14 :: DEFINES --------------------------------- --<br>
15 :: VARIABLES --------------------------------- --<br>
16 :: MACROS --------------------------------- --<br>
17 <br>
18 GOTO :MAINLINE<br>
19 <br>
20 :: FUNCTIONS --------------------------------- --<br>
21 :: MAINLINE --------------------------------- --<br>
22 :MAINLINE<br>
23 <br>
24 call %_LIB_% header foo<br>
25 call %_LIB_% trailer<br>
26 <br>
27 ENDLOCAL & POPD<br>
28 :: HISTORY ------------------------------------<br>
29 :: $Log$<br>
30 :: END OF FILE --------------------------------- --<br>
<br>
line 12 "imports" the "library"... actually it's just syntactic sugar that makes the subsequent calls easier...
It's possible, and there are some different ways to do it.
1) Copy&Paste the complete "Library" into each of your files Works, but it's not really a library, and it's a horror to change/correct a library function in all files
2) include a library via call-wrapper
call batchLib.bat :length result "abcdef"
and batchLib.bat starts with
call %*
exit /b
...
:length
...
Easy to program, but very slow, as each library call loads the library batch, and possible problems with the parameters.
3) A "self-loading" library BatchLibrary or how to include batch files (cached)
It creates each time a temporary batch file, combined of the own code and the library code.
It do some advanced functions at the library startup like secure parameter access.
But in my opinion it's also easy to use
A user script sample
@echo off
REM 1. Prepare the BatchLibrary for the start command
call BatchLib.bat
REM 2. Start of the Batchlib, acquisition of the command line parameters, activates the code with the base-library
<:%BL.Start%
rem Importing more libraries ...
call :bl.import "bl_DateTime.bat"
call :bl.import "bl_String.bat"
rem Use library functions
call :bl.String.Length result abcdefghij
echo len=%result%
EDIT: Another way is ...
4) A macro library
You could use batch-macros, it's easy to include and to use them.
call MacroLib.bat
set myString=abcdef
%$strLen% result,myString
echo The length of myString is %result%
But it's tricky to build the macros!
More about the macro technic at Batch "macros" with arguments (cached)
MacroLibrary.bat
set LF=^
::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
:::: StrLen pString pResult
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
set "str=A!%%~2!"%\n%
set "len=0"%\n%
for /l %%A in (12,-1,0) do (%\n%
set /a "len|=1<<%%A"%\n%
for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
)%\n%
for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~1=%%v") else echo %%v%\n%
) %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,
Another solution would be to temporary append the library functions to the running batch file.
The original file can be saved in a temporary file before the change and restored when finished.
This has the downside that you need to call the :deimport function at the end to restore the file and remove the temporary file. You also need to be able to write to the batch file and the folder you are currently in.
demo.bat
@ECHO OFF
:: internal import call or external import call via wrapper function
CALL:IMPORT test.bat "C:\path with spaces\lib 2.bat"
:: external import call
::CALL importer.bat "%~f0%" test.bat "C:\path with spaces\lib 2.bat"
CALL:TEST
CALL:LIB2TEST
CALL:DEIMPORT
GOTO:EOF
:: Internal version of the importer
:IMPORT
SETLOCAL
IF NOT EXIST "%~f0.tmp" COPY /Y "%~f0" "%~f0.tmp">NUL
SET "PARAMS=%*"
SET "PARAMS=%PARAMS:.bat =.bat+%"
SET "PARAMS=%PARAMS:.bat" =.bat"+%"
COPY /Y "%~f0"+%PARAMS% "%~f0">NUL
ENDLOCAL
GOTO:EOF
:: wrapper function for external version call
:::IMPORT
::CALL import.bat "%~f0" %*
::GOTO:EOF
:: Internal version of the deimporter
:DEIMPORT
IF EXIST "%~f0.tmp" (
COPY /Y "%~f0.tmp" "%~f0">NUL
DEL "%~f0.tmp">NUL
)
GOTO:EOF
test.bat
:test
ECHO output from test.bat
GOTO:EOF
C:\path with spaces\lib 2.bat
:LIB2TEST
ECHO output from lib 2.bat
GOTO:EOF
Alternatively using the external version. Note this imports the deimport function so make sure you remove it in the demo.bat file.
import.bat
:: External version of the importer
SETLOCAL EnableDelayedExpansion
IF NOT EXIST "%~f1.tmp" COPY /Y "%~f1" "%~f1.tmp">NUL
SET "PARAMS=%*"
SET "PARAMS=!PARAMS:"%~f1" =!"
SET "PARAMS=%PARAMS:.bat =.bat+%"
SET "PARAMS=%PARAMS:.bat" =.bat"+%"
COPY /Y "%~f1"+%PARAMS% "%~f1">NUL
:: external version of the importer - remove the internal one before use!
ECHO :DEIMPORT>>"%~f1"
ECHO IF EXIST ^"%%~f0.tmp^" ^(>>"%~f1"
ECHO. COPY /Y ^"%%~f0.tmp^" ^"%%~f0^"^>NUL>>"%~f1"
ECHO. DEL ^"%%~f0.tmp^"^>NUL>>"%~f1"
ECHO ^)>>"%~f1"
ECHO GOTO:EOF>>"%~f1"
ENDLOCAL
GOTO:EOF
There is an easier way to load the library functions each time the main file is executed. For example:
@echo off
rem If current code was restarted, skip library loading part
if "%_%" == "_" goto restart
rem Copy current code and include any desired library
copy /Y %0.bat+lib1.bat+libN.bat %0.full.bat
rem Set the restart flag
set _=_
rem Restart current code
%0.full %*
:restart
rem Delete the restart flag
set _=
rem Place here the rest of the batch file
rem . . . . .
rem Always end with goto :eof, because the library functions will be loaded
rem after this code!
goto :eof