How can I pass arguments to a batch file? - batch-file

I need to pass an ID and a password to a batch file at the time of running rather than hardcoding them into the file.
Here's what the command line looks like:
test.cmd admin P#55w0rd > test-log.txt

Another useful tip is to use %* to mean "all". For example:
echo off
set arg1=%1
set arg2=%2
shift
shift
fake-command /u %arg1% /p %arg2% %*
When you run:
test-command admin password foo bar
The above batch file will run:
fake-command /u admin /p password admin password foo bar
I may have the syntax slightly wrong, but this is the general idea.

Here's how I did it:
#fake-command /u %1 /p %2
Here's what the command looks like:
test.cmd admin P#55w0rd > test-log.txt
The %1 applies to the first parameter the %2 (and here's the tricky part) applies to the second. You can have up to 9 parameters passed in this way.

If you want to intelligently handle missing parameters you can do something like:
IF %1.==. GOTO No1
IF %2.==. GOTO No2
... do stuff...
GOTO End1
:No1
ECHO No param 1
GOTO End1
:No2
ECHO No param 2
GOTO End1
:End1

Accessing batch parameters can be simple with %1, %2, ... %9 or also %*,
but only if the content is simple.
There is no simple way for complex contents like "&"^&, as it's not possible to access %1 without producing an error.
set var=%1
set "var=%1"
set var=%~1
set "var=%~1"
The lines expand to
set var="&"&
set "var="&"&"
set var="&"&
set "var="&"&"
And each line fails, as one of the & is outside of the quotes.
It can be solved with reading from a temporary file a remarked version of the parameter.
#echo off
SETLOCAL DisableDelayedExpansion
SETLOCAL
for %%a in (1) do (
set "prompt="
echo on
for %%b in (1) do rem * #%1#
#echo off
) > param.txt
ENDLOCAL
for /F "delims=" %%L in (param.txt) do (
set "param1=%%L"
)
SETLOCAL EnableDelayedExpansion
set "param1=!param1:*#=!"
set "param1=!param1:~0,-2!"
echo %%1 is '!param1!'
The trick is to enable echo on and expand the %1 after a rem statement (works also with %2 .. %*).
So even "&"& could be echoed without producing an error, as it is remarked.
But to be able to redirect the output of the echo on, you need the two for-loops.
The extra characters * # are used to be safe against contents like /? (would show the help for REM).
Or a caret ^ at the line end could work as a multiline character, even in after a rem.
Then reading the rem parameter output from the file, but carefully.
The FOR /F should work with delayed expansion off, else contents with "!" would be destroyed.
After removing the extra characters in param1, you got it.
And to use param1 in a safe way, enable the delayed expansion.

Yep, and just don't forget to use variables like %%1 when using if and for and the gang.
If you forget the double %, then you will be substituting in (possibly null) command line arguments and you will receive some pretty confusing error messages.

A friend was asking me about this subject recently, so I thought I'd post how I handle command-line arguments in batch files.
This technique has a bit of overhead as you'll see, but it makes my batch files very easy to understand and quick to implement. As well as supporting the following structures:
>template.bat [-f] [--flag] [--namedvalue value] arg1 [arg2][arg3][...]
The jist of it is having the :init, :parse, and :main functions.
Example usage
>template.bat /?
test v1.23
This is a sample batch file template,
providing command-line arguments and flags.
USAGE:
test.bat [flags] "required argument" "optional argument"
/?, --help shows this help
/v, --version shows the version
/e, --verbose shows detailed output
-f, --flag value specifies a named parameter value
>template.bat <- throws missing argument error
(same as /?, plus..)
**** ****
**** MISSING "REQUIRED ARGUMENT" ****
**** ****
>template.bat -v
1.23
>template.bat --version
test v1.23
This is a sample batch file template,
providing command-line arguments and flags.
>template.bat -e arg1
**** DEBUG IS ON
UnNamedArgument: "arg1"
UnNamedOptionalArg: not provided
NamedFlag: not provided
>template.bat --flag "my flag" arg1 arg2
UnNamedArgument: "arg1"
UnNamedOptionalArg: "arg2"
NamedFlag: "my flag"
>template.bat --verbose "argument #1" --flag "my flag" second
**** DEBUG IS ON
UnNamedArgument: "argument #1"
UnNamedOptionalArg: "second"
NamedFlag: "my flag"
template.bat
#::!/dos/rocks
#echo off
goto :init
:header
echo %__NAME% v%__VERSION%
echo This is a sample batch file template,
echo providing command-line arguments and flags.
echo.
goto :eof
:usage
echo USAGE:
echo %__BAT_NAME% [flags] "required argument" "optional argument"
echo.
echo. /?, --help shows this help
echo. /v, --version shows the version
echo. /e, --verbose shows detailed output
echo. -f, --flag value specifies a named parameter value
goto :eof
:version
if "%~1"=="full" call :header & goto :eof
echo %__VERSION%
goto :eof
:missing_argument
call :header
call :usage
echo.
echo **** ****
echo **** MISSING "REQUIRED ARGUMENT" ****
echo **** ****
echo.
goto :eof
:init
set "__NAME=%~n0"
set "__VERSION=1.23"
set "__YEAR=2017"
set "__BAT_FILE=%~0"
set "__BAT_PATH=%~dp0"
set "__BAT_NAME=%~nx0"
set "OptHelp="
set "OptVersion="
set "OptVerbose="
set "UnNamedArgument="
set "UnNamedOptionalArg="
set "NamedFlag="
:parse
if "%~1"=="" goto :validate
if /i "%~1"=="/?" call :header & call :usage "%~2" & goto :end
if /i "%~1"=="-?" call :header & call :usage "%~2" & goto :end
if /i "%~1"=="--help" call :header & call :usage "%~2" & goto :end
if /i "%~1"=="/v" call :version & goto :end
if /i "%~1"=="-v" call :version & goto :end
if /i "%~1"=="--version" call :version full & goto :end
if /i "%~1"=="/e" set "OptVerbose=yes" & shift & goto :parse
if /i "%~1"=="-e" set "OptVerbose=yes" & shift & goto :parse
if /i "%~1"=="--verbose" set "OptVerbose=yes" & shift & goto :parse
if /i "%~1"=="--flag" set "NamedFlag=%~2" & shift & shift & goto :parse
if /i "%~1"=="-f" set "NamedFlag=%~2" & shift & shift & goto :parse
if not defined UnNamedArgument set "UnNamedArgument=%~1" & shift & goto :parse
if not defined UnNamedOptionalArg set "UnNamedOptionalArg=%~1" & shift & goto :parse
shift
goto :parse
:validate
if not defined UnNamedArgument call :missing_argument & goto :end
:main
if defined OptVerbose (
echo **** DEBUG IS ON
)
echo UnNamedArgument: "%UnNamedArgument%"
if defined UnNamedOptionalArg echo UnNamedOptionalArg: "%UnNamedOptionalArg%"
if not defined UnNamedOptionalArg echo UnNamedOptionalArg: not provided
if defined NamedFlag echo NamedFlag: "%NamedFlag%"
if not defined NamedFlag echo NamedFlag: not provided
:end
call :cleanup
exit /B
:cleanup
REM The cleanup function is only really necessary if you
REM are _not_ using SETLOCAL.
set "__NAME="
set "__VERSION="
set "__YEAR="
set "__BAT_FILE="
set "__BAT_PATH="
set "__BAT_NAME="
set "OptHelp="
set "OptVersion="
set "OptVerbose="
set "UnNamedArgument="
set "UnNamedArgument2="
set "NamedFlag="
goto :eof

There is no need to complicate it. It is simply command %1 %2 parameters, for example,
#echo off
xcopy %1 %2 /D /E /C /Q /H /R /K /Y /Z
echo copied %1 to %2
pause
The "pause" displays what the batch file has done and waits for you to hit the ANY key. Save that as xx.bat in the Windows folder.
To use it, type, for example:
xx c:\f\30\*.* f:\sites\30
This batch file takes care of all the necessary parameters, like copying only files, that are newer, etc. I have used it since before Windows. If you like seeing the names of the files, as they are being copied, leave out the Q parameter.

In batch file
set argument1=%1
set argument2=%2
echo %argument1%
echo %argument2%
%1 and %2 return the first and second argument values respectively.
And in command line, pass the argument
Directory> batchFileName admin P#55w0rd
Output will be
admin
P#55w0rd

#ECHO OFF
:Loop
IF "%1"=="" GOTO Continue
SHIFT
GOTO Loop
:Continue
Note: IF "%1"=="" will cause problems if %1 is enclosed in quotes itself.
In that case, use IF [%1]==[] or, in NT 4 (SP6) and later only, IF "%~1"=="" instead.

Everyone has answered with really complex responses, however it is actually really simple. %1 %2 %3 and so on are the arguements parsed to the file. %1 is arguement 1, %2 is arguement 2 and so on.
So, if I have a bat script containing this:
#echo off
echo %1
and when I run the batch script, I type in this:
C:> script.bat Hello
The script will simply output this:
Hello
This can be very useful for certain variables in a script, such as a name and age. So, if I have a script like this:
#echo off
echo Your name is: %1
echo Your age is: %2
When I type in this:
C:> script.bat Oliver 1000
I get the output of this:
Your name is: Oliver
Your age is: 1000

Let's keep this simple.
Here is the .cmd file.
#echo off
rem this file is named echo_3params.cmd
echo %1
echo %2
echo %3
set v1=%1
set v2=%2
set v3=%3
echo v1 equals %v1%
echo v2 equals %v2%
echo v3 equals %v3%
Here are 3 calls from the command line.
C:\Users\joeco>echo_3params 1abc 2 def 3 ghi
1abc
2
def
v1 equals 1abc
v2 equals 2
v3 equals def
C:\Users\joeco>echo_3params 1abc "2 def" "3 ghi"
1abc
"2 def"
"3 ghi"
v1 equals 1abc
v2 equals "2 def"
v3 equals "3 ghi"
C:\Users\joeco>echo_3params 1abc '2 def' "3 ghi"
1abc
'2
def'
v1 equals 1abc
v2 equals '2
v3 equals def'
C:\Users\joeco>

FOR %%A IN (%*) DO (
REM Now your batch file handles %%A instead of %1
REM No need to use SHIFT anymore.
ECHO %%A
)
This loops over the batch parameters (%*) either they are quoted or not, then echos each parameter.

I wrote a simple read_params script that can be called as a function (or external .bat) and will put all variables into the current environment. It won't modify the original parameters because the function is being called with a copy of the original parameters.
For example, given the following command:
myscript.bat some -random=43 extra -greeting="hello world" fluff
myscript.bat would be able to use the variables after calling the function:
call :read_params %*
echo %random%
echo %greeting%
Here's the function:
:read_params
if not %1/==/ (
if not "%__var%"=="" (
if not "%__var:~0,1%"=="-" (
endlocal
goto read_params
)
endlocal & set %__var:~1%=%~1
) else (
setlocal & set __var=%~1
)
shift
goto read_params
)
exit /B
Limitations
Cannot load arguments with no value such as -force. You could use -force=true but I can't think of a way to allow blank values without knowing a list of parameters ahead of time that won't have a value.
Changelog
2/18/2016
No longer requires delayed expansion
Now works with other command line arguments by looking for - before parameters.

Inspired by an answer elsewhere by #Jon, I have crafted a more general algorithm for extracting named parameters, optional values, and switches.
Let us say that we want to implement a utility foobar. It requires an initial command. It has an optional parameter --foo which takes an optional value (which cannot be another parameter, of course); if the value is missing it defaults to default. It also has an optional parameter --bar which takes a required value. Lastly it can take a flag --baz with no value allowed. Oh, and these parameters can come in any order.
In other words, it looks like this:
foobar <command> [--foo [<fooval>]] [--bar <barval>] [--baz]
Here is a solution:
#ECHO OFF
SETLOCAL
REM FooBar parameter demo
REM By Garret Wilson
SET CMD=%~1
IF "%CMD%" == "" (
GOTO usage
)
SET FOO=
SET DEFAULT_FOO=default
SET BAR=
SET BAZ=
SHIFT
:args
SET PARAM=%~1
SET ARG=%~2
IF "%PARAM%" == "--foo" (
SHIFT
IF NOT "%ARG%" == "" (
IF NOT "%ARG:~0,2%" == "--" (
SET FOO=%ARG%
SHIFT
) ELSE (
SET FOO=%DEFAULT_FOO%
)
) ELSE (
SET FOO=%DEFAULT_FOO%
)
) ELSE IF "%PARAM%" == "--bar" (
SHIFT
IF NOT "%ARG%" == "" (
SET BAR=%ARG%
SHIFT
) ELSE (
ECHO Missing bar value. 1>&2
ECHO:
GOTO usage
)
) ELSE IF "%PARAM%" == "--baz" (
SHIFT
SET BAZ=true
) ELSE IF "%PARAM%" == "" (
GOTO endargs
) ELSE (
ECHO Unrecognized option %1. 1>&2
ECHO:
GOTO usage
)
GOTO args
:endargs
ECHO Command: %CMD%
IF NOT "%FOO%" == "" (
ECHO Foo: %FOO%
)
IF NOT "%BAR%" == "" (
ECHO Bar: %BAR%
)
IF "%BAZ%" == "true" (
ECHO Baz
)
REM TODO do something with FOO, BAR, and/or BAZ
GOTO :eof
:usage
ECHO FooBar
ECHO Usage: foobar ^<command^> [--foo [^<fooval^>]] [--bar ^<barval^>] [--baz]
EXIT /B 1
Use SETLOCAL so that the variables don't escape into the calling environment.
Don't forget to initialize the variables SET FOO=, etc. in case someone defined them in the calling environment.
Use %~1 to remove quotes.
Use IF "%ARG%" == "" and not IF [%ARG%] == [] because [ and ] don't play will at all with values ending in a space.
Even if you SHIFT inside an IF block, the current args such as %~1 don't get updated because they are determined when the IF is parsed. You could use %~1 and %~2 inside the IF block, but it would be confusing because you had a SHIFT. You could put the SHIFT at the end of the block for clarity, but that might get lost and/or confuse people as well. So "capturing" %~1 and %~1 outside the block seems best.
You don't want to use a parameter in place of another parameter's optional value, so you have to check IF NOT "%ARG:~0,2%" == "--".
Be careful only to SHIFT when you use one of the parameters.
The duplicate code SET FOO=%DEFAULT_FOO% is regrettable, but the alternative would be to add an IF "%FOO%" == "" SET FOO=%DEFAULT_FOO% outside the IF NOT "%ARG%" == "" block. However because this is still inside the IF "%PARAM%" == "--foo" block, the %FOO% value would have been evaluated and set before you ever entered the block, so you would never detect that both the --foo parameter was present and also that the %FOO% value was missing.
Note that ECHO Missing bar value. 1>&2 sends the error message to stderr.
Want a blank line in a Windows batch file? You gotta use ECHO: or one of the variations.

To refer to a set variable in command line you would need to use %a% so for example:
set a=100
echo %a%
rem output = 100
Note: This works for Windows 7 pro.

For to use looping get all arguments and in pure batch:
Obs: For using without: ?*&|<>
#echo off && setlocal EnableDelayedExpansion
for %%Z in (%*)do set "_arg_=%%Z" && set/a "_cnt+=1+0" && (
call set "_arg_[!_cnt!]=!_arg_!" && for /l %%l in (!_cnt! 1 !_cnt!
)do echo/ The argument n:%%l is: !_arg_[%%l]!
)
goto :eof
Your code is ready to do something with the argument number where it needs, like...
#echo off && setlocal EnableDelayedExpansion
for %%Z in (%*)do set "_arg_=%%Z" && set/a "_cnt+=1+0" && call set "_arg_[!_cnt!]=!_arg_!"
fake-command /u !_arg_[1]! /p !_arg_[2]! > test-log.txt

Simple solution(even though question is old)
Test1.bat
echo off
echo "Batch started"
set arg1=%1
echo "arg1 is %arg1%"
echo on
pause
CallTest1.bat
call "C:\Temp\Test1.bat" pass123
output
YourLocalPath>call "C:\Temp\test.bat" pass123
YourLocalPath>echo off
"Batch started"
"arg1 is pass123"
YourLocalPath>pause
Press any key to continue . . .
Where YourLocalPath is current directory path.
To keep things simple store the command param in variable and use variable for comparison.
Its not just simple to write but its simple to maintain as well so if later some other person or you read your script after long period of time, it will be easy to understand and maintain.
To write code inline : see other answers.

Make a new batch file (example: openclass.bat) and write this line in the file:
java %~n1
Then place the batch file in, let's say, the system32 folder, go to your Java class file, right click, Properties, Open with..., then find your batch file, select it and that's that...
It works for me.
PS: I can't find a way to close the cmd window when I close the Java class. For now...

Paired arguments
If you prefer passing the arguments in a key-value pair you can use something like this:
#echo off
setlocal enableDelayedExpansion
::::: asigning arguments as a key-value pairs:::::::::::::
set counter=0
for %%# in (%*) do (
set /a counter=counter+1
set /a even=counter%%2
if !even! == 0 (
echo setting !prev! to %%#
set "!prev!=%%~#"
)
set "prev=%%~#"
)
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: showing the assignments
echo %one% %two% %three% %four% %five%
endlocal
And an example :
c:>argumentsDemo.bat one 1 "two" 2 three 3 four 4 "five" 5
1 2 3 4 5
Predefined variables
You can also set some environment variables in advance. It can be done by setting them in the console or setting them from my computer:
#echo off
if defined variable1 (
echo %variable1%
)
if defined variable2 (
echo %variable2%
)
and calling it like:
c:\>set variable1=1
c:\>set variable2=2
c:\>argumentsTest.bat
1
2
File with listed values
You can also point to a file where the needed values are preset.
If this is the script:
#echo off
setlocal
::::::::::
set "VALUES_FILE=E:\scripts\values.txt"
:::::::::::
for /f "usebackq eol=: tokens=* delims=" %%# in ("%VALUES_FILE%") do set "%%#"
echo %key1% %key2% %some_other_key%
endlocal
and values file is this:
:::: use EOL=: in the FOR loop to use it as a comment
key1=value1
key2=value2
:::: do not left spaces arround the =
:::: or at the begining of the line
some_other_key=something else
and_one_more=more
the output of calling it will be:
value1 value2 something else
Of course you can combine all approaches. Check also arguments syntax , shift

If you're worried about security/password theft (that led you to design this solution that takes login credentials at execution instead of static hard coding without the need for a database), then you could store the api or half the code of password decryption or decryption key in the program file, so at run time, user would type username/password in console to be hashed/decrypted before passed to program code for execution via set /p, if you're looking at user entering credentials at run time.
If you're running a script to run your program with various user/password, then command line args will suit you.
If you're making a test file to see the output/effects of different logins, then you could store all the logins in an encrypted file, to be passed as arg to test.cmd, unless you wanna sit at command line & type all the logins until finished.
The number of args that can be supplied is limited to total characters on command line. To overcome this limitation, the previous paragraph trick is a workaround without risking exposure of user passwords.

Related

Find value of command line argument in batch file

I am passing a list of arguments while running a batch file and need the value of a particular argument -logdir to set some dir path in my batch file. How can I first check if the -logdir is passed as argument and if it is passed get its value and set it in a variable.
I am running it like:
test.bat -env test1 -logdir C:\Users\...\log -userhome ... -log ...
and many more arguments. These arguments can be passed in different order.
I am able to fetch the list of arguments passed using below.
set argCount=0
for %%x in (%*) do set /A argCount+=1
echo Number of processed arguments: %argCount%
set /a count=0
for /l %%x in (1, 1, %argCount%) do (
set /a count=!count!+1
call set "var=%%!count!"
echo var= %!var!
)
But I am facing issues comparing this args with the value. I tried using if as below :
if -> if "!_var!" equ "-logdir" set /a logdir="%%!count!"
But getting error as syntax not correct.
#echo off
setlocal
call :parseArgs %*
if errorlevel 1 exit /b 1
echo env %env%
echo log %log%
echo logdir %logdir%
echo userhome %userhome%
exit /b
:parseArgs
for %%A in (%*) do (
if defined arg (
call set "%%arg%%=%%~1"
set "arg="
) else (
for %%B in (
env
log
logdir
userhome
) do if "%%~A" == "-%%~B" set "arg=%%~B"
if not defined arg (
>&2 echo %%A is an invalid argument.
exit /b 1
)
)
shift
)
exit /b
After the for %%B in ( line is where the valid keywords
are located. If an argument does not define arg, then it
exits the :parseArgs label in which you can check
errorlevel to exit the script.
Does any order with arguments.
E.g.
myscript -env current -log text.log
which would have variable %env% with value current etc.
If you want default values in case i.e. -log is not passed, then
just e.g. set "log=%~n0.log" before the call :parseArgs line.
You claim there can be "many more arguments". The %x syntax can only use the first nine parameters, so with the available %1 to %9 you can only process four pairs, which might be too few. Gladly, the parameters 10... are not lost (although not directly accessible). You can shift the parameter list to the left.
As the parameter pairs "can be passed in different order", it's best to just assign the even parameters (values) to variables named like the odd parameters (variables). I addes some REM's to the code to explain it:
#echo off & setlocal
:loop
if "%~2" == "" goto :done & REM if no other pair of parameters then done
set "%~1=%~2" & REM set variable
shift & REM remove first...
shift & REM ... two params
goto :loop & REM and repeat
:done
REM show parameters:
set -
Note: no syntax check. Parameters are expected to be repeatedly -param and value
To pass parameters with spaces (or other delimiters), quote them like
test.bat -env test1 -logdir "C:\path with spaces\log"

Calling function from included batch file with a parameter

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
if "%1"=="WAIT" (
call :WAIT_AND_PRINT 5
echo.
)
REM rest...
My define_wait.bat looks like following:
: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
The problem is that if I define the wait function in another batch file it does not work, calling call :WAIT_AND_PRINT 5 does not hand on the parameter correctly (Error: missing operand)... If I copy my code from my define_wait.bat int my main batch file, everything works fine...
How would I make that correctly?
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
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.
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.

How do you iterate through a subset of the parameters passed to a batch file?

Inside a batch file, how do you iterate through a subset of the command line arguments passed to the batch file?
I can find lots of examples, but none seems to deal with this exact scenario.
Here is a demo example using shift command as suggested by Scott C:
#echo off
set "Count=0"
:NextParameter
if "%~1" == "" goto Done
set /A Count+=1
echo Parameter %Count% is: "%~1"
shift
goto NextParameter
:Done
echo %Count% parameters processed.
set "Count="
The absolute minimum loop would be:
#echo off
:NextParameter
if "%~1" == "" goto :EOF
echo Processing parameter: "%~1"
shift
goto NextParameter
In the loop always first parameter is evaluated for being an empty string. The loop exits if this condition becomes true.
Otherwise it can be done whatever should be done with the current parameter using "%~1" or just %1 or whatever is needed, see help output in a command prompt window on executing there call /? or help call.
Command shift shifts all arguments to left by 1 making it possible to access the next parameter again with %1. Help of command shift can be read with shift /? executed in a command prompt window.
The two examples above fail to process all parameters correct if the batch file is called with 1 or more empty parameters.
For example calling minimum example with Arg1 "argument 2" "" "arg 4" results in the output:
Processing parameter: "Arg1"
Processing parameter: "argument 2"
This can be solved with following batch code:
#echo off
:NextParameter
if "%~1" == "" (
if not [%1] == [""] goto :EOF
)
echo Processing parameter: "%~1"
shift
goto NextParameter
This third batch file called with Arg1 "argument 2" "" "arg 4" outputs correct:
Processing parameter: "Arg1"
Processing parameter: "argument 2"
Processing parameter: ""
Processing parameter: "arg 4"
It is of course also possible to skip empty parameter in list, for example with:
#echo off
:NextParameter
if "%~1" == "" (
if [%1] == [""] (
echo Skipping empty parameter.
shift
goto NextParameter
)
goto :EOF
)
echo Processing parameter: "%~1"
shift
goto NextParameter
This last batch file called with Arg1 "argument 2" "" "arg 4" outputs:
Processing parameter: "Arg1"
Processing parameter: "argument 2"
Skipping empty parameter.
Processing parameter: "arg 4"
But batch files are usually not called with empty parameters.
I think the simplest way is to first load the parameters in an array, and then process array elements in any way you wish:
#echo off
setlocal EnableDelayedExpansion
rem Load parameters in "parV" array and "parC" counter
set parC=0
for %%a in (%*) do (
set /A parC+=1
set parV[!parC!]=%%a
)
rem Show parameters from 3 to N
for /L %%i in (3,1,%parC%) do echo %%i- !parV[%%i]!
If the parameters may contain wild-card characters (* or ?), then the parameters must be loaded in the array via the classical shift loop:
rem Load parameters in parV/parC even if they have wild-cards
set parC=0
:nextPar
if "%~1" equ "" goto endPars
set /A parC+=1
set "parV[!parC!]=%~1"
shift
goto nextPar
:endPars
For a further description on array management in Batch files, see this post.

restore the previous echo status

In a DOS Batch File subroutine, how can I turn off echo within the subroutine, but before returning, put it back to what it was before (either on or off)?
For example, if there was a command called echo restore, I would use it like this:
echo on
... do stuff with echoing ...
call :mySub
... continue to do stuff with echoing ...
exit /b
:mySub
#echo off
... do stuff with no echoing ...
echo restore
goto :EOF
My first attempt was an utter failure - thanks jeb for pointing out the errors. For those that are interested, the original answer is available in the edit history.
Aacini has a good solution if you don't mind putting your subroutine in a separate file.
Here is a solution that works without the need of a 2nd batch file. And it actually works this time! :)
(Edit 2 - optimized code as per jeb's suggestion in comment)
:mysub
::Silently get the echo state and turn echo off
#(
setlocal
call :getEchoState echoState
echo off
)
::Do whatever
set return=returnValue
::Restore the echo state, pass the return value across endlocal, and return
(
endlocal
echo %echoState%
set return=%return%
exit /b
)
:getEchoState echoStateVar
#setlocal
#set file=%time%
#set file="%temp%\getEchoState%file::=_%_%random%.tmp"
#(
for %%A in (dummy) do rem
) >%file%
#for %%A in (%file%) do #(
endlocal
if %%~zA equ 0 (set %~1=OFF) else set %~1=ON
del %file%
exit /b
)
If you are willing to put up with the slight risk of two processes simultaneously trying to access the same file, the :getEchoState routine can be simplified without the need of SETLOCAL or a temp variable.
:getEchoState echoStateVar
#(
for %%A in (dummy) do rem
) >"%temp%\getEchoState.tmp"
#for %%A in ("%temp%\getEchoState.tmp") do #(
if %%~zA equ 0 (set %~1=OFF) else set %~1=ON
del "%temp%\getEchoState.tmp"
exit /b
)
The simplest way is to not turn echo off in the first place.
Instead, do what you currently do with the echo off line to the rest of your subroutine - prefix all commands in the subroutine with an # sign. This has the effect of turning off echo for that command, but keeps the echo state for future commands.
If you use commands that execute other commands, like IF or DO, you will also need to prefix the "subcommand" with an # to keep them from being printed when echo is otherwise on.
The easiest way is to extract the subroutine to another .bat file and call it via CMD /C instead of CALL this way:
echo on
... do stuff with echoing ...
cmd /C mySub
... continue to do stuff with echoing ...
exit /b
mySub.bat:
#echo off
... do stuff with no echoing ...
exit /b
This way the echo status will be automatically restored to the value it had when the CMD /C was executed; the only drawback of this method is a slightly slower execution...
Here is a straight forward solution that relies on a single temporary file (using %random% to avoid race conditions). It works and is at least localization resistant, i.e., it works for the two known cases stated by #JoelFan and #jeb.
#set __ME_tempfile=%temp%\%~nx0.echo-state.%random%.%random%.txt
#set __ME_echo=OFF
#echo > "%__ME_tempfile%"
#type "%__ME_tempfile%" | #"%SystemRoot%\System32\findstr" /i /r " [(]*on[)]*\.$" > nul
#if "%ERRORLEVEL%"=="0" (set __ME_echo=ON)
#erase "%__ME_tempfile%" > nul
#::echo __ME_echo=%__ME_echo%
#echo off
...
endlocal & echo %__ME_echo%
#goto :EOF
Add this preliminary code to increase the solution's robustness (although the odd's are high that it's not necessary):
#:: define TEMP path
#if NOT DEFINED temp ( #set "temp=%tmp%" )
#if NOT EXIST "%temp%" ( #set "temp=%tmp%" )
#if NOT EXIST "%temp%" ( #set "temp=%LocalAppData%\Temp" )
#if NOT EXIST "%temp%" ( #exit /b -1 )
:__ME_find_tempfile
#set __ME_tempfile=%temp%\%~nx0.echo-state.%random%.%random%.txt
#if EXIST "%__ME_tempfile%" ( goto :__ME_find_tempfile )
I wasn't really happy with the solution above specially because of the language issue and I found a very simple one just by comparing the result from current echo setting with the result when explicitly set OFF. This is how it works:
:: SaveEchoSetting
:: :::::::::::::::::::::::::::
:: Store current result
#echo> %temp%\SEScur.tmp
:: Store result when explicitly set OFF
#echo off
#echo> %temp%\SESoff.tmp
:: If results do not match, it must have been ON ... else it was already OFF
#for /f "tokens=*" %%r in (%temp%\SEScur.tmp) do (
#find "%%r" %temp%\SESoff.tmp > nul
#if errorlevel 1 (
#echo #echo on > %temp%\SESfix.bat
) else (
#echo #echo off > %temp%\SESfix.bat
)
)
::
:: Other code comes here
:: Do whatever you want with echo setting ...
::
:: Restore echo setting
#call %temp%\SESfix.bat
I was looking for the same solution to the same problem, and after reading your comments I had an idea (which is not the answer to the question, but for my problem is even better).
I wasn't satisfied with the cmd.exe /c mysub.cmd because it makes hard or even impossible to return variables (I didn't check) - (couldn't comment because it's the first time I post here :)
Instead noticed that all we want -in the end- is to suppress stdout:
echo on
rem call "mysub.cmd" >nul
call :mysub >nul
echo %mysub_return_value%
GOTO :eof
:mysub
setlocal
set mysub_return_value="ApplePie"
endlocal & set mysub_return_value=%mysub_return_value%
GOTO :eof
It works fine with labelled subroutines, with subroutines contained in .cmd files, and I suppose it would work fine even with the cmd.exe /c variant (or start).
It also has the plus that we can keep or discard the stderr, replacing >nul with >nul 2>&1
I note that ss64.com scares kids like me stating that with call "Redirection with & | <> also does not work as expected".
This simple test works as expected. He must have been thinking of more complex situations.

Parsing of cmdline arguments containing =

After using batch files for many years I was surprised to discover that the equals sign '=' is considered an argument separator.
Given this test script:
echo arg1: %1
echo arg2: %2
echo arg3: %3
and an invocation:
test.bat a=b c
the output is:
arg1: a
arg2: b
arg3: c
Why is that and how can it be avoided? I don't want the user of the script to account for this quirk and quote "a=b", which is counter-intuitive.
This batch script was run on Windows 7.
===== EDIT =====
A little more background: I encountered this problem when writing a bat file to start a Java application. I wanted to consume some args in the bat file and then pass the rest to the java application. So my first attempt was to do shift and then rebuild the args list (since %* is not affected by shift). It looked something like this, and that's when I discovered the issue:
rem Rebuild the args, %* does not work after shift
:args
if not "%1" == "" (
set ARGS=!ARGS! %1
shift
goto args
)
The next step was to not use shift anymore, but rather implement shift by hand by removing one character at a time from %* until a space is encountered:
rem Remove the 1st arg if it was the profile
set ARGS=%*
if not "%FIRST_ARG%" == "%KNOA_PROFILE%" goto remove_first_done
:remove_first
if not defined ARGS goto remove_first_done
if "%ARGS:~0,1%" == " " goto remove_first_done
set ARGS=%ARGS:~1%
goto remove_first
:remove_first_done
But this is ugly and might still fail in some cases I haven't considered. So finally I decided to write a Java program to deal with the argument parsing! In my case this is fine, since I am launching a server and the penalty of an extra java invocation is minimal. It's mind-boggling what you end up doing sometimes.
You might wonder why didn't I take care of the args in the Java application itself? The answer is that I want to be able to pass JVM options like -Xmx which must be processed before invoking java.
I'm guessing it does this so that /param=data is the same as /param data
I don't know any tricks to fix that stupid (probably by design) parsing issue but I was able to come up with a super ugly workaround:
#echo off
setlocal ENABLEEXTENSIONS
set param=1
:fixnextparam
set p=
((echo "%~1"|find " ")>nul)||(
call :fixparam %param% "%~1" "%~2" %* 2>nul
)
if "%p%"=="" (set "p%param%=%1") else shift
shift&set /A param=%param% + 1
if not "%~1"=="" goto fixnextparam
echo.1=%p1%
echo.2=%p2%
echo.3=%p3%
echo.4=%p4%
echo.5=%p5%
goto:EOF
:fixparam
set p%1=
for /F "tokens=4" %%A in ("%*") do (
if "%%~A"=="%~2=%~3" set p=!&set "p%1=%%A"
)
goto:EOF
When I execute test.cmd foo=bar baz "fizz buzz" w00t I get:
1=foo=bar
2=baz
3="fizz buzz"
4=w00t
5=
The problem with this is of course that you cannot do %~dp1 style variable expansion.
It is not possible to do call :mylabel %* and then use %1 either because call :batchlabel has the same parameter parsing problem!
If you really need %~dp1 handling you could use the WSH/batch hybrid hack:
#if (1==1) #if(1==0) #ELSE
#echo off
#SETLOCAL ENABLEEXTENSIONS
if "%SPECIALPARSE%"=="*%~f0" (
echo.1=%~1
echo.2=%~2
echo.3=%~3
echo.4=%~4
echo.5=%~5
) else (
set "SPECIALPARSE=*%~f0"
cscript //E:JScript //nologo "%~f0" %*
)
#goto :EOF
#end #ELSE
w=WScript,wa=w.Arguments,al=wa.length,Sh=w.CreateObject("WScript.Shell"),p="";
for(i=0;i<al;++i)p+="\""+wa.Item(i)+"\" ";
function PipeStream(i,o){for(;!i.AtEndOfStream;)o.Write(i.Read(1))}
function Exec(cmd,e){
try{
e=Sh.Exec(cmd);
while(e.Status==0){
w.Sleep(99);
PipeStream(e.StdOut,w.StdOut);
PipeStream(e.StdErr,w.StdErr);
}
return e.ExitCode;
}catch(e){return e.number;}
}
w.Quit(Exec("\""+WScript.ScriptFullName+"\" "+p));
#end
I am only answering this: >> Why is that and how can it be avoided?
My suggestion: To use a better language. No i am not joking. batch has too many quirks/nuances such as this plus a lot of other limitations, its just not worth the time coming up with ugly/inefficient workarounds. If you are running Windows 7 and higher, why not try using vbscript or even powershell. These tools/language will greatly help in your daily programming/admin tasks. As an example of how vbscript can propely take care of such an issue:
For i=0 To WScript.Arguments.Count-1
WScript.Echo WScript.Arguments(i)
Next
Output:
C:\test>cscript //nologo myscript.vbs a=b c
a=b
c
Note that it properly takes care of the arguments.
#echo off
setlocal enabledelayedexpansion
if %1 neq ~ (
set "n=%*"
for %%a in ("!n: =" "!") do (
set "s=%%a"
if !s:~0^,2!==^"^" (
set "s=!s:" "= !"
set "s=!s:""="!"
)
set "m=!m! !s!"
)
%0 ~ !m!
)
endlocal
shift
echo arg1: %~1
echo arg1: %~2
echo arg1: %~dp3
exit /b
c:> untested.bat a=b c "%USERPROFILE%"
#jeb: here another method
untested.cmd
#echo off
setlocal enabledelayedexpansion
set "param=%*"
set param="%param: =" "%"
for /f "delims=" %%a in ('echo %param: =^&echo.%') do (
set "str=%%a"
if !str:~0^,2!==^"^" (
set "str=!str:^&echo.=!"
set "str=!str:"^"= !"
set str="!str:~1,-1!"
)
set "m=!m! !str!"
)
Call :sub %m%
endlocal & goto :eof
:sub
echo arg1: %~1
echo arg2: %~2
echo arg3: %~3
echo arg4: %~4
goto :eof
Again, I can't explain the unexpected behavior, but there is a very simple solution. Just change your argument references, then quote the argument when invoking as such:
test.bat script:
echo arg1: %~1
echo arg2: %~2
echo arg3: %~3
Invoke:
test.bat "a=b" c
Output:
arg1: a=b
arg2: c
arg3:
Note that referencing the arguments with a tilde (~) removes the leading/trailing quotes from the argument.

Resources