I know I can get first argument with %0, second with %1, and so on.. And I can also get all arguments with %*.
Can I get all arguments from the second arguments? For example, if I run
foo.bat bar1 bar2 bar3 bar4
How can I get only bar2 bar3 bar4?
#ECHO OFF
SETLOCAL
SET allargs=%*
IF NOT DEFINED allargs echo no args provided&GOTO :EOF
SET arg1=%1
CALL SET someargs=%%allargs:*%1=%%
ECHO allargs %allargs%
ECHO arg1 %arg1%
ECHO someargs %someargs%
This will leave SOMEARGS with at least one leading separator (if it is set)
With SHIFT command. But with every shift you'll lose the first.
This will not change the %* but you'll be able to get all argument ,but the first:
#echo off
shift
set "arg_line= "
:parse_args
if "%~1" NEQ "" (
arg_line=%argline% "%~1"
goto :parse_args
)
now you'll have all arguments but the first stored in %arg_line%
You need to use SHIFT. It moves the apparent position of the parameters, then %* will get all parameters from the position shifted to. You should get the first parameters before using SHIFT.
More information on SHIFT.
Related
Basically, the script below can be used in three different ways:
call foo
.bat boot_up "path"
.bat halt "path"
.bat ssh "path" "command"
I cannot guarantee that the path or the command does not have spaces.
When I use foo.bat for executing my subroutine ssh, then everything works. Instead, when I try to call my subroutines boot_up or halt, an error apprears:
( was unexpected at this time.
But, if I add a third argument to boot_up or halt, then everything works again.
So my question is, how do I manage call of batch files with variable lenght of arguments?
:main
echo Argument 1: (%1)
echo Argument 2: (%2)
echo Argument 3: (%3)
call :set_cygwin_env || exit /b 1
if not "%1"=="" if not %2=="" (
if "%1"=="boot_up" (
call :boot_up %2
) else if "%1"=="halt" (
call :halt %2
) else if "%1"=="ssh" if not %3=="" (
call :ssh %2 %3
) else (
call :show_help || exit /b 1
)
) else (
call :show_help || exit /b 1
)
:exit
The source of your error is ) else if "%1"=="ssh" if not %3=="" ( - When you don't pass a 3rd argument, your code expands to ) else if "halt"=="ssh" if not =="" (, which is invalid syntax. The entire compound statement must have valid syntax, even the branches that don't fire. You must make sure the left side of the comparison has at least one character. Enclosing quotes are typically used, because they protect against poison characters like & and |, and token delimiters like space, comma, equal, tab.
In general, you should use if "%~1"=="someValue" ... when doing comparisons with command line arguments. The ~ removes any existing enclosing quotes, and then you explicitly add your own. It is important to first remove the quotes because you cannot anticipate whether the user added their own quotes or not. A value like "this&that" could be passed in, so "%1" would expand to ""this&that"", and the & would no longer be quoted. "%~1" expands to the desired "this&that". This strategy is not fool proof, but it is about as good as it gets without doing crazy batch programming that you probably don't want to get into.
So your fixed code should look like
:main
echo Argument 1: (%1)
echo Argument 2: (%2)
echo Argument 3: (%3)
call :set_cygwin_env || exit /b 1
if not "%~1"=="" if not "%~2"=="" (
if "%~1"=="boot_up" (
call :boot_up %2
) else if "%~1"=="halt" (
call :halt %2
) else if "%~1"=="ssh" if not "%~3"=="" (
call :ssh %2 %3
) else (
call :show_help || exit /b 1
)
) else (
call :show_help || exit /b 1
)
:exit
you could pre-process your arguments to remove passed quotes as follows (I simplified your code to create an independent test)
#echo off
:main
set ARG1=%1
set ARG2=%2
set ARG3=%3
if x%ARG1%==x goto skarg1
if ^%ARG1:~0,1%==^" set ARG1=%ARG1:~1,-1%
:skarg1
if x%ARG2%==x goto skarg2
if ^%ARG2:~0,1%==^" set ARG2=%ARG2:~1,-1%
:skarg2
if x%ARG3%==x goto skarg3
if ^%ARG3:~0,1%==^" set ARG3=%ARG3:~1,-1%
:skarg3
echo Argument 1: (%ARG1%)
echo Argument 2: (%ARG2%)
echo Argument 3: (%ARG3%)
if "%ARG1%"=="boot_up" if not "%ARG2%"=="" (
echo bootup "%ARG2%"
)
:exit
if argument is empty, just leave it as-is
if argument starts with quotes, just remove the first & last char
use %ARGx% (quoted) from now on instead of %1,%2
quick test using only argument 2:
L:\>args boot_up
Argument 1: (boot_up)
Argument 2: ()
Argument 3: ()
L:\>args boot_up arg2
Argument 1: (boot_up)
Argument 2: (arg2)
Argument 3: ()
bootup "arg2"
L:\>args boot_up "arg2 space"
Argument 1: (boot_up)
Argument 2: (arg2 space)
Argument 3: ()
bootup "arg2 space"
L:\>args boot_up "arg2"
Argument 1: (boot_up)
Argument 2: (arg2)
Argument 3: ()
bootup "arg2"
I have smth like %SystemRoot%\blahblahblah in a variable (for example, variable a).
But echo !a! will return %SystemRoot%\blahblahblah (without expanding %SystemRoot%). How can I expand it?
You can use a trick of calling a subroutine that will cause the variable to be expanded twice:
#Echo Off
set a=%%SystemRoot%%\blahblahblah
call :reparse set b=%a%
echo value of a: %a%
echo value of b: %b%
goto :EOF
:reparse
%*
goto :EOF
The :reparse subroutine just executes all its parameters. The first expansion occurs when executing the call and the second occurs when %* is interpreted as set b=%SystemRoot%\blahblahblah.
I would like to know how to call more than 9 argument within a batch script when calling a label. For example, the following shows that I have 12 arguments assigned along with attempting to echo all of them.
CALL:LABEL "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten" "eleven" "twelve"
PAUSE
GOTO:EOF
:LABEL
echo %1
echo %2
echo %3
echo %4
echo %5
echo %6
echo %7
echo %8
echo %9
echo %10
echo %11
echo %12
The output for %10 %11 and %12 ends up being one0 one1 one2. I've tried using curly brackets, brackets, quotations, single quotes around the numbers without any luck.
Use the shift command if you want to work with more than 9 parameters.
(actually more than 10 parameters if you count the %0 parameter)
You can [...] use the shift command to create a batch file that can accept more than 10 batch parameters. If you specify more than 10 parameters on the command line, those that appear after the tenth (%9) will be shifted one at a time into %9.
You can either use a loop, store the variables before shifting, or do it quick like this:
#echo off
CALL:LABEL "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten" "eleven" "twelve"
PAUSE
GOTO:EOF
:LABEL
:: print arguments 1-9
echo %1
echo %2
echo %3
echo %4
echo %5
echo %6
echo %7
echo %8
echo %9
:: print arguments 10-11
shift
shift
echo %8
echo %9
:: print argument 13
shift
echo %9
You can replace the shift commands with a loop in case you have many arguments. The following for loop executes shift nine times, so that %1 will be the tenth argument.
#for /L %%i in (0,1,8) do shift
This is another way to use the shift command.
Note in this case you can use a variable number of parameters.
#ECHO OFF
CALL:LABEL "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten" "eleven" "twelve"
PAUSE
GOTO:EOF
:LABEL
echo %1
shift
if not [%1]==[] GOTO LABEL
Just throwing my hat in the ring if it helps anyone
Set a=%1 & Set b=%2 & Set c=%3 & Set d=%4 & Set e=%5 & Set f=%6 & Set g=%7 & Set h=%8 & Set i=%9
Shift & Shift & Shift & Shift & Shift & Shift & Shift & Shift & Shift
Set j=%1 & Set k=%2 & Set l=%3 & Set m=%4 & Set n=%5 & Set o=%6 & Set p=%7 & Set q=%8 & Set r=%9
Shift & Shift & Shift & Shift & Shift & Shift & Shift & Shift & Shift
Set s=%1 & Set t=%2 & Set u=%3 & Set v=%4 & Set w=%5 & Set x=%6 & Set y=%7 & Set z=%8
I just put this at the top of the .bat or subroutine that will use >9 args, then you can use %a% through %z% instead of %1 to the non-existent %26.
It's pretty ugly, originally I had it all one one line à la
Set a=%1 & Shift & Set b=%1 & Shift &...
That way it's only on one line and gets hidden behind the edge of the editor pretty swiftly, but apparently Shift won't take effect until the next true line; all the variables were set to the first parameter.
If you're wondering why do this instead of the loops, unless someone shows me you can create a sort of map or array, I needed to use the variables in a one-line command, not echo (or do whatever) with them one at a time:
::example; concatenate a bunch of files
Set output=%1
Shift
<insert above behemoth>
type %a% %b% %c% %d% %e% %f% 2>NUL > %output%
Damn this was so much easier in bash
cat ${#:2} > "$1"
Assuming we're using a recent-ish version of CMD here, I'm pretty shocked to find no one has posted the following, which allows an arbitrary number of arguments to be processed easily without ever using the clunky shift command:
rem test.bat
call :subr %*
exit /b
:subr
for %%A in (%*) do (
echo %%A
)
exit /b
You can also do this same technique right in "main" as well.
This way, you don't eat up your arguments as you process them, and there is no need for shift, meaning you can consistently loop through the argument-list more than once if you have complicated calling options and it happens to make your script's logic simpler.
Obviously, if do loop through more than once, it increases computational complexity in exchange for legibility, but neither Bash nor CMD were really built for great speed (as far as I've ever heard) so there's little point in trying to optimize by doing setup all-in-one-go assuming n is any less than 100 or so.
Some sample output:
C:\tmp>test.bat one two three four five six seven eight nine ten eleven
one
two
three
four
five
six
seven
eight
nine
ten
eleven
edit - On the off chance anyone is processing arguments against another list and part of the work is nested, it's important that an intermediary CALL be used to allow the current argument being processed to be transferred into the inner loop; simply doing set intermediary=%%OUTER appears to simply set intermediary to a an empty string, so it's next simplest to do this:
setlocal enabledelayedexpansion
rem ^ in my experience, it's always a good idea to set this if for-loops are involved
for /f %%L in (file.txt) do (
call :inner %%L
)
exit /b
:inner
for %%A in (%*) do (
if %%A EQU %~1 call dosomething.bat %~1
)
exit /b
edit 2 - Also, if for whatever reason you want to add the shift approach into the mix here – perhaps by trying to pass all arguments other than the 1st to a subroutine by using a shift and then using call :subroutine %* – note that it won't work because %* actually gives you all of the original arguments in order, ignorant of any shifting you have done. It's a shame because there's no native syntax (that I know of) that can group all arguments after a certain one, as one might expect say %3* to do.
You cannot have more than 10 (0 through 9) accessible arguments (%0 being the batchfile itself) in a batch file. However, using the shift command will allow you to "left-shift" your arguments, and access those arguments beyond the 9th one. If you do it three times, you should end up with %7, %8 and %9 containing "ten", "eleven" and "twelve".
I know this old but I figured I'd update for others google searching.
For anyone that is curious, I created a nice dynamic and compact method of doing this, so you can have a variable amount of parameters.
SETLOCAL ENABLEDELAYEDEXPANSION
:ARG
set /A Count+=1
echo !Count!
if "%~1"=="" (
GOTO ENDARGS
) else (
<Do Something, call variables with !VarName! instead of %VarName%>
)
shift
GOTO ARG
:ENDARGS
ENDLOCAL
kkk's answer can work as well
Please let me know if anyone has any improvements. I attempted using the following also accepted method. But for people using this method and trying to do something as insane as I am like having more than 27 parameters, this doesn't work. It stops after 27. Or at least that's what I experienced, let me know if you've experienced otherwise.
for %%a in (%*) do (
<Do Something>
)
Just converting some shell scripts into batch files and there is one thing I can't seem to find...and that is a simple count of the number of command line arguments.
eg. if you have:
myapp foo bar
In Shell:
$# -> 2
$* -> foo bar
$0 -> myapp
$1 -> foo
$2 -> bar
In batch
?? -> 2 <---- what command?!
%* -> foo bar
%0 -> myapp
%1 -> foo
%2 -> bar
So I've looked around, and either I'm looking in the wrong spot or I'm blind, but I can't seem to find a way to get a count of number of command line arguments passed in.
Is there a command similar to shell's "$#" for batch files?
ps. the closest i've found is to iterate through the %1s and use 'shift', but I need to refernece %1,%2 etc later in the script so that's no good.
Googling a bit gives you the following result from wikibooks:
set argC=0
for %%x in (%*) do Set /A argC+=1
echo %argC%
Seems like cmd.exe has evolved a bit from the old DOS days :)
You tend to handle number of arguments with this sort of logic:
IF "%1"=="" GOTO HAVE_0
IF "%2"=="" GOTO HAVE_1
IF "%3"=="" GOTO HAVE_2
etc.
If you have more than 9 arguments then you are screwed with this approach though. There are various hacks for creating counters which you can find here, but be warned these are not for the faint hearted.
The function :getargc below may be what you're looking for.
#echo off
setlocal enableextensions enabledelayedexpansion
call :getargc argc %*
echo Count is %argc%
echo Args are %*
endlocal
goto :eof
:getargc
set getargc_v0=%1
set /a "%getargc_v0% = 0"
:getargc_l0
if not x%2x==xx (
shift
set /a "%getargc_v0% = %getargc_v0% + 1"
goto :getargc_l0
)
set getargc_v0=
goto :eof
It basically iterates once over the list (which is local to the function so the shifts won't affect the list back in the main program), counting them until it runs out.
It also uses a nifty trick, passing the name of the return variable to be set by the function.
The main program just illustrates how to call it and echos the arguments afterwards to ensure that they're untouched:
C:\Here> xx.cmd 1 2 3 4 5
Count is 5
Args are 1 2 3 4 5
C:\Here> xx.cmd 1 2 3 4 5 6 7 8 9 10 11
Count is 11
Args are 1 2 3 4 5 6 7 8 9 10 11
C:\Here> xx.cmd 1
Count is 1
Args are 1
C:\Here> xx.cmd
Count is 0
Args are
C:\Here> xx.cmd 1 2 "3 4 5"
Count is 3
Args are 1 2 "3 4 5"
Try this:
SET /A ARGS_COUNT=0
FOR %%A in (%*) DO SET /A ARGS_COUNT+=1
ECHO %ARGS_COUNT%
If the number of arguments should be an exact number (less or equal to 9), then this is a simple way to check it:
if "%2" == "" goto args_count_wrong
if "%3" == "" goto args_count_ok
:args_count_wrong
echo I need exactly two command line arguments
exit /b 1
:args_count_ok
Avoids using either shift or a for cycle at the cost of size and readability.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set /a arg_idx=1
set "curr_arg_value="
:loop1
if !arg_idx! GTR 9 goto :done
set curr_arg_label=%%!arg_idx!
call :get_value curr_arg_value !curr_arg_label!
if defined curr_arg_value (
echo/!curr_arg_label!: !curr_arg_value!
set /a arg_idx+=1
goto :loop1
)
:done
set /a cnt=!arg_idx!-1
echo/argument count: !cnt!
endlocal
goto :eof
:get_value
(
set %1=%2
)
Output:
count_cmdline_args.bat testing more_testing arg3 another_arg
%1: testing
%2: more_testing
%3: arg3
%4: another_arg
argument count: 4
EDIT: The "trick" used here involves:
Constructing a string that represents a currently evaluated command-line argument variable (i.e. "%1", "%2" etc.) using a string that contains a percent character (%%) and a counter variable arg_idx on each loop iteration.
Storing that string into a variable curr_arg_label.
Passing both that string (!curr_arg_label!) and a return variable's name (curr_arg_value) to a primitive subprogram get_value.
In the subprogram its first argument's (%1) value is used on the left side of assignment (set) and its second argument's (%2) value on the right. However, when the second subprogram's argument is passed it is resolved into value of the main program's command-line argument by the command interpreter. That is, what is passed is not, for example, "%4" but whatever value the fourth command-line argument variable holds ("another_arg" in the sample usage).
Then the variable given to the subprogram as return variable (curr_arg_value) is tested for being undefined, which would happen if currently evaluated command-line argument is absent. Initially this was a comparison of the return variable's value wrapped in square brackets to empty square brackets (which is the only way I know of testing program or subprogram arguments which may contain quotes and was an overlooked leftover from trial-and-error phase) but was since fixed to how it is now.
A reasonably robust solution is to delegate counting to a subroutine invoked with call; the subroutine uses goto statements to emulate a loop in which shift is used to consume the (subroutine-only) arguments iteratively:
#echo off
setlocal
:: Call the argument-counting subroutine with all arguments received,
:: without interfering with the ability to reference the arguments
:: with %1, ... later.
:: NOTE: See comments re /? below.
call :count_args %* >NUL || (echo Usage: ... & exit /b 0)
:: Print the result.
echo %ReturnValue% argument(s) received.
:: Exit the batch file.
exit /b
:: Subroutine that counts the arguments given.
:: Returns the count in %ReturnValue%
:count_args
set /a ReturnValue = 0
:count_args_for
if %1.==. goto :eof
set /a ReturnValue += 1
shift
goto count_args_for
Limitations:
/? among the arguments - except if passed as "/?" - is not supported, because call invariably interprets it as a request to show its command-line help.
The code above detects this case (curiously, call signals failure via its exit code after showing its help) and interprets this case as wanting to show the current batch file's help instead of counting arguments.
Arguments with metacharacters (special characters) are supported, if enclosed in "..." (as is typical), but character-individual ^-escaping in an unquoted argument does not work (because the ^ is already stripped by the time the batch file sees the arguments, and expanding %* then causes the metacharacters to act as such):
:: OK - arguments with special chars. are "..."-enclosed.
:: -> "3 argument(s) received."
test.cmd hi "you & me" "a<b"
:: !! FAILS
:: The last argument uses character-individual ^-escaping.
:: -> error "The system cannot find the file specified."
test.cmd hi "you & me" a^<b
A syntactically invalid argument list is not supported (which is arguably not a problem):
:: !! FAILS
:: Syntactically invalid argument list
:: -> error "The syntax of the command is incorrect."
test.cmd a"
The last answer was two years ago now, but I needed a version for more than nine command line arguments. May be another one also does...
#echo off
setlocal
set argc_=1
set arg0_=%0
set argv_=
:_LOOP
set arg_=%1
if defined arg_ (
set arg%argc_%_=%1
set argv_=%argv_% %1
set /a argc_+=1
shift
goto _LOOP
)
::dont count arg0
set /a argc_-=1
echo %argc_% arg(s)
for /L %%i in (0,1,%argc_%) do (
call :_SHOW_ARG arg%%i_ %%arg%%i_%%
)
echo converted to local args
call :_LIST_ARGS %argv_%
exit /b
:_LIST_ARGS
setlocal
set argc_=0
echo arg0=%0
:_LOOP_LIST_ARGS
set arg_=%1
if not defined arg_ exit /b
set /a argc_+=1
call :_SHOW_ARG arg%argc_% %1
shift
goto _LOOP_LIST_ARGS
:_SHOW_ARG
echo %1=%2
exit /b
The solution is the first 19 lines and converts all arguments to variables in a c-like style. All other stuff just probes the result and shows conversion to local args. You can reference arguments by index in any function.
Further to How to Pass Command Line Parameters in batch file how does one get the rest of the parameters with specifying them exactly? I don't want to use SHIFT because I don't know how many parameters there might be and would like to avoid counting them, if I can.
For example, given this batch file:
#echo off
set par1=%1
set par2=%2
set par3=%3
set therest=%???
echo the script is %0
echo Parameter 1 is %par1%
echo Parameter 2 is %par2%
echo Parameter 3 is %par3%
echo and the rest are %therest%
Running mybatch opt1 opt2 opt3 opt4 opt5 ...opt20 would yield:
the script is mybatch
Parameter 1 is opt1
Parameter 2 is opt2
Parameter 3 is opt3
and the rest are opt4 opt5 ...opt20
I know %* gives all the parameters, but I don't wan't the first three (for example).
Here's how you can do it without using SHIFT:
#echo off
for /f "tokens=1-3*" %%a in ("%*") do (
set par1=%%a
set par2=%%b
set par3=%%c
set therest=%%d
)
echo the script is %0
echo Parameter 1 is %par1%
echo Parameter 2 is %par2%
echo Parameter 3 is %par3%
echo and the rest are %therest%
#echo off
setlocal enabledelayedexpansion
set therest=;;;;;%*
set therest=!therest:;;;;;%1 %2 %3 =!
echo the script is %0
echo Parameter 1 is %1
echo Parameter 2 is %2
echo Parameter 3 is %3
echo and the rest are: %therest%
This works with quoted arguments and with arguments that have equal signs or commas, as long as first three arguments didn't have these special delimiter chars.
Sample output:
test_args.bat "1 1 1" 2 3 --a=b "x y z"
Parameter 1 is "1 1 1"
Parameter 2 is 2
Parameter 3 is 3
and the rest are: --a=b "x y z"
This works by replacing %1 %2 %3 in original command line %*. The first five semicolons are there just to make sure that only the first occurrence of these %1 %2 %3 is replaced.
The following code uses shift, but it avoids to parse the command line using for and lets the command line interpreter do this job (regard that for does not parse double-quotes properly, for instance argument set A B" "C is interpreted as 3 arguments A, B", "C by for, but as 2 arguments A, B" "C by the interpreter; this behaviour prevents quoted path arguments like "C:\Program Files\" from being handled correctly):
#echo off
set "par1=%1" & shift /1
set "par2=%1" & shift /1
set "par3=%1" & shift /1
set therest=
set delim=
:REPEAT
if "%1"=="" goto :UNTIL
set "therest=%therest%%delim%%1"
set "delim= "
shift /1
goto :REPEAT
:UNTIL
echo the script is "%0"
echo Parameter 1 is "%par1%"
echo Parameter 2 is "%par2%"
echo Parameter 3 is "%par3%"
echo and the rest are "%therest%"
rem.the additional double-quotes in the above echoes^
are intended to visualise potential whitespaces
The remaining arguments in %therest% might not look like the way they were originally concerning the delimiters (remember the command line interpreter also treats TABs, ,, ;, = as delimiters as well as all combinations), because all delimiters are replaced by a single space here. However, when passing %therest% to some other command or batch file, it will be parsed correctly.
The only limitation I encountered so far applies to arguments containing the caret character ^. Other limitations (related to <, >, |, &, ") apply to the command line interpreter itself.
#ECHO OFF
SET REST=
::# Guess you want 3rd and on.
CALL :SUBPUSH 3 %*
::# ':~1' here is merely to drop leading space.
ECHO REST=%REST:~1%
GOTO :EOF
:SUBPUSH
SET /A LAST=%1-1
SHIFT
::# Let's throw the first two away.
FOR /L %%z in (1,1,%LAST%) do (
SHIFT
)
:aloop
SET PAR=%~1
IF "x%PAR%" == "x" (
GOTO :EOF
)
ECHO PAR=%PAR%
SET REST=%REST% "%PAR%"
SHIFT
GOTO aloop
GOTO :EOF
I like to use subroutines instead of EnableDelayedExpansion. Above is extract from my dir/file pattern processing batch. Don't say this cannot handle arguments with =, but at least can do quoted path with spaces, and wildcards.
One more alternative :-)
some of the other answers did not deal with quotes, or with extra spaces between parameters. Disclaimer: I have not tested this extremely well.
This is all just a workaround for %* not being updated after a shift ugg!
I implemented it as a POP starting from the technique by #Pavel-P.
It relies on the batch parser to remove extra spaces
It depends on a global variable 'Params'
~ removes quotes -- leaving it up to you to re-quote as desired. Remove the ~ if you don't want that.
:PopFirstParam
#set varname=%~1
#set %varname%=%~2
#rem #echo %varname% = '!%varname%!'
#if '%3' equ '' set Params=&goto :eof
#call :popone %Params%
#goto :eof
:popone
#set Params=;;;%*
#set Params=!Params:;;;%1 =!
#goto :eof
Usage:
#setlocal enabledelayedexpansion
#set Params=%*
#call :PopFirstParam P1 %Params%
#call :PopFirstParam P2 %Params%
#call :PopFirstParam P3 %Params%
#call :PopFirstParam P4 %Params%
#rem etc . . .
Specifically I use it to optionally run commands asynchronously:
#setlocal enabledelayedexpansion
#rem set variables that decide what to be run . . . .
#call :RunIfDefined doNum1 "Title 1" mycmd1.exe some "params" here
#call :RunIfDefined doNum2 "MyTitle 2" mylongcmd2.exe some "params" here
#call :RunIfDefined doNum3 "Long Title 3" mycmd3.exe some "params" here
#goto :eof
:RunIfDefined
#set Params=%*
#call :PopFirstParam DefineCheck %Params%
#if not defined %DefineCheck% goto :eof
#call :PopFirstParam WindowTitle %Params%
#call :PopFirstParam Command %Params%
#rem %Params% should now be the remaining params (I don't care about spaces here)
#echo start "%WindowTitle%" "%Command%" %Params%
#start "%WindowTitle%" "%Command%" %Params%
#goto :eof
C:\test> set doNum1=yes
C:\test> set doNum3=yes
C:\test> call dothings.bat
start "Title 1" "mycmd1.exe" some "params" here
start "Long Title 3" "mycmd3.exe" some "params" here
too bad DOS doesn't have a #include