In my main batch file I include another batch file and want to call a function defined in there, code looks like following:
@echo off
call define_wait.bat
i
Although there are several ways to call a function that reside in a separate library file, all methods require to change the way to call the library functions in the calling program, and/or insert additional code at beginning of the library file in order to identify the called function.
There is an interesting trick that allows to avoid all these details, so both the main and the library files contain the original code, and just 2 lines needs to be added to the main file. The method consist in switch the context of the running main Batch file to the library file; after that, all functions in the library file are available to the running code. The way to do that is renaming the library file with the same name of the main file. After that, when a call :function
command is executed, the :function
label will be search in the library file! Of course, the files must be renamed back to the original names before the program ends. Ah! I almost forget the key point of this method: both the initial and final renames must be executed in a code block in the main file. A simple example:
main.bat
@echo off
echo Calling :test and :hello functions in the library.bat file:
rem Switch the context to the library file
(ren "%~NX0" temp.bat & ren library.bat "%~NX0"
call :test
echo Back from library.bat :test function
call :hello
echo Back from library.bat :hello function
rem Switch the context back to the main file
ren "%~NX0" library.bat & ren temp.bat "%~NX0")
echo Continue in main file
library.bat
:test
echo I am :test function in library.bat file
exit /B
:hello
echo I am :hello function in library.bat file
exit /B
A drawback of this method is that if a run-time error happens when the files are renamed, the files remains renamed, but this may be fixed in a very simple way. For example, a check.bat file may check if the library.bat file exists, and do the rename back if it was not found.
I wasn't aware of this until jeb commented it, but here's a quick demonstration of the call
bug he mentioned, using some utility functions I had lying around.
functions.bat:
:length <"string">
rem // sets errorlevel to the string length (not including quotation marks)
setlocal disabledelayedexpansion
if "%~1"=="" (endlocal & exit /b 0) else set ret=1
set "tmpstr=%~1"
for %%I in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
setlocal enabledelayedexpansion
if not "!tmpstr:~%%I,1!"=="" (
for %%x in ("!tmpstr:~%%I!") do endlocal & (
set /a ret += %%I
set "tmpstr=%%~x"
)
) else endlocal
)
endlocal & exit /b %ret%
:password <return_var>
rem // prompts user for password, masks input, and sets return_var to entered value
setlocal disabledelayedexpansion
<NUL set /P "=Password? "
set "psCommand=powershell -noprofile "$p=read-host -AsSecureString;^
$m=[Runtime.InteropServices.Marshal];$m::PtrToStringAuto($m::SecureStringToBSTR($p))""
for /f "usebackq delims=" %%p in (`%psCommand%`) do endlocal & set "%~1=%%p"
goto :EOF
main.bat:
@echo off & setlocal
rem // demo return value
call :password pass
setlocal enabledelayedexpansion
echo You entered !pass!
rem // demo bubbling up of %ERRORLEVEL%
call :length "!pass!"
echo Password length is %ERRORLEVEL%
endlocal
goto :EOF
rem // ====== FUNCTION DECLARATIONS =======
:length <"string">
:password <return_var>
functions.bat %*
Output:
Password? *********
You entered something
Password length is 9
This web page offers an explanation:
If you execute a second batch file without using CALL you may run into some buggy behaviour: if both batch files contain a label with the same name and you have previously used CALL to jump to that label in the first script, you will find execution of the second script starts at the same label. Even if the second label does not exist this will still raise an error "cannot find the batch label". This bug can be avoided by always using CALL.
If you've ever done any coding in C++, it helps to think of the labels in main.bat as function declarations in a .h file, while the labels in functions.bat would correspond to function definitions in a .cpp file. Or in .NET, the main.bat labels would be like DllImport("functions.bat")
so to speak.
Working function bat that forwards it's parameters to it's subfunction:
@echo off
call %*
goto :EOF
:WAIT_AND_PRINT
set /a time=%1
for /l %%x in (1, 1, %time%) do (
ping -n 1 -w 1000 1.0.0.0 > null
echo|set /p=.
)
goto :EOF
:WAIT
set /a time="%1 * 1000"
ping -n 1 -w %time% 1.0.0.0 > null
goto :EOF
In the main bat I now don't include the batch file anymore but call it directly like following:
call define_wait.bat :WAIT_AND_PRINT 5