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.
Related
I have two .bat files, both use delayed expansion, so that I can set variables within for loops.
The example below is greatly simplified just to show the problem
Script one.bat
#echo off
setlocal enableextensions
setlocal enabledelayedexpansion
set j=0
for /L %%i in (1,1,2) do (
set j=%%i
set /A j=!j! + 1
echo %%i !j!
two.bat
echo %%i !j!
)
Script two.bat
#echo off
setlocal enableextensions
setlocal enabledelayedexpansion
echo Hello World
exit /B 0
On return from two.bat variable !j! is lost and echo is turned back on.
J:\>one
1 2
Hello World
1 !j!
J:\>(
set j=2
set /A j=!j! + 1
echo 2 !j!
two.bat
echo 2 !j!
)
Missing operator.
2 !j!
Hello World
2 !j!
Yes I could make two.bat a sub-routine in one.bat but its hundred of lines long and I dont want to have to maintain two copies of the same logic
What am I missing here ?
Your assumption is wrong that execution returns from file two.bat, because that is only the case when you are using the call command1.
The batch file one.bat runs two.bat within a parenthesised block of code, which is already in a command stack2, so the block is (kind of) finished before terminating execution.
Your output perfectly illustrates what happens (therefore I commented it here):
J:\>one
1 2 /* first loop iteration, the first `echo %%i !j!` in the block is
executed as expected; */
Hello World /* `two.bat` is run; execution does NOT return to `one.bat`,
because there is NO `call`, which would put a return point onto
the stack! the only thing remembered and thus accomplished is
the current command line or parenthesised block; */
1 !j! /* the second `echo %%i !j!` in the block is executed as it is
still buffered, but `two.bat` is already quit, hence implicit
`endlocal` commands have been executed, so all the nested
`setlocal` commands in your scripts are cancelled and delayed
expansion is disabled (default state); moreover, variable `j` is
no longer defined here; */
/* at this point, the parenthesised block, which is the loop body,
has been executed, hence batch file context is left and Command
Prompt context applies, so `#echo off` from `one.bat` does no
longer apply here; */
J:\>( // this block is nothing but the echo of the second loop iteration,
set j=2 // which is still buffered;
set /A j=!j! + 1
echo 2 !j!
two.bat
echo 2 !j!
)
Missing operator. /* this error message is caused by the attempt to execute
`set /A j=!j! + 1` (remember that delayed expansion is no longer
enabled and `j` is undefined); */
2 !j! // first `echo %%i !j!` in the buffered second loop iteration;
Hello World /* `two.bat` is run again; afterwards, batch file context is left
again and Command Prompt context applies; */
2 !j! // second `echo %%i !j!` in the buffered second loop iteration;
To prove whether execution happens under batch file or Command Prompt context, just place set /A j in the loop body in one.bat as the last command, so you will get an additional output 0 after the output 1 !j! and the second 2 !j!, because set /A does not return anything in batch file context, but it outputs the (last) result (without a trailing line-break) in Command Prompt context; the value of 0 shows that j is no longer set.
1) There are a few exceptions: the called batch file is involved in a pipe, or it is run and parsed by a for /F loop, because the batch file runs in a new cmd.exe instance in such cases.
2) The same would be true if the called batch file was involved in a line with concatenated commands, hence something like two.bat & echo Fine would echo Fine upon execution of two.bat.
I'm busy writing a Windows batch script and I'm having some problem with arguments.
My batch script is the following
#echo off
setlocal EnableDelayedExpansion
:: RETRIEVE ARGS WITH SPACES
set VAR01=%~1
set VAR02=%~2
set VAR03=%~3
:: CONFIRM IT WORKED
echo %VAR01%
echo %VAR02%
echo %VAR03%
endlocal
exit /b
And I am trying to pass it arguments that include spaces.
run_batch.bat "arg var 01" "arg var 02" "arg var 03"
But when it runs I am getting the output
arg var 01
ECHO is off
ECHO is off
Why is it only working correctly for the first argument and how can it be fixed?
NOTE
There was never anything wrong with the code, it seems there were invisible special characters that there causing issues. Must have been the text editor or something along those lines. How do I remove this question?
This should work:
#echo off
setlocal EnableDelayedExpansion
:: RETRIEVE ARGS WITH SPACES
set VAR01=%~1
set VAR02=%~2
set VAR03=%~3
:: CONFIRM IT WORKED
echo %VAR01%
echo %VAR02%
echo %VAR03%
endlocal
exit /b
The wrong part was:
set VAR01=%~1
set VAR01=%~2
set VAR01=%~3
but should be:
set VAR01=%~1
set VAR02=%~2
set VAR03=%~3
A small example script for your perusal.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
REM EXIT IF NO ARGS RECEIVED
IF "%~1"=="" EXIT
REM SET MINIMUM NUMBER OF ARGS
SET "i=1"
REM RETRIEVE ALL ARGS
:ARGS
SET "VAR0%i%=%~1"
SHIFT
IF NOT "%~1"=="" (
SET/A i+=1
GOTO :ARGS
)
REM INFORM NUMBER OF ARGS
CLS
ECHO=THERE WERE %i% ARGS
REM CONFIRM IT WORKED
ECHO=
SET VAR0
REM ALTERNATIVE CONFIRMATION
ECHO=
FOR /L %%A IN (1,1,%i%) DO IF DEFINED VAR0%%A ECHO=!VAR0%%A!
TIMEOUT -1
ENDLOCAL
EXIT
I think there was no problem aside from a typo. A quite tricky way to show the args and put them to numbered vars I learned from DosTips.org (but can't find the exact link) is:
:: Expand-Args.cmd
#Echo off
Set Args=,1=%~1,2=%~2,3=%~3,4=%~4,5=%~5,6=%~6,7=%~7,8=%~8,9=%~9
Set "Args=%*%Args:,="&Set "Arg%"
Set Arg
The batch will have this output run with:
Expand-Args one "2 2" three 4 five 6 "7 7" 8 9
Arg1=one
Arg2=2 2
Arg3=three
Arg4=4
Arg5=five
Arg6=6
Arg7=7 7
Arg8=8
Arg9=9
Args=one "2 2" three 4 five 6 "7 7" 8 9
Edit To not interfere with content in double quotes the delimiter comma might be replaced with an uncommon char like ALT+0127
Basically what I'm trying to create is a script that only will run if the time is LEQ than 19:00 (7 PM), what I did so far is:
set myTime=%time%
set myFlag=false
if %myTime% LEQ 19:00 set myFlag=true
if myFlag=true (
*my script*
)
It returns this error message: "86 was not expected at this moment", (86 being the last numbers of the variable 'myTime' [14:36:11,86]. It just won't work.
I've also tried:
set myTime=%time%
set myFlag=false
if %myTime% LEQ 19:00:00,00 set myFlag=true
if myFlag=true (
*my script*
)
Both ways i get the same error message double-digit number not expected. Any thoughts on how to solve this? It's even possible do a time comparision on a windows batch file?
Syntax: Escape Characters, Delimiters and Quotes
Delimiters
Delimiters separate one parameter from the next - they split the
command line up into words.
Parameters are most often separated by spaces, but any of the
following are also valid delimiters:
Comma (,)
Semicolon (;)
Equals (=)
Space ( )
Tab ( )
Notice that although / and - are commonly used to separate command
options, they are absent from the list above. This is because batch
file parameters are passed to CMD.exe which can accept it's own
parameters (which are invoked using / and - )
Next code snippet should work (see set /? and if /?):
set "myTime=%time%"
set "myFlag=false"
if "%myTime%" LEQ "19:00:00,00" set "myFlag=true"
if "%myFlag%"=="true" (
echo *my script*
)
Note that above code snippet is locale dependent. You can try next locale independent solution similar to this answer to another question:
SETLOCAL EnableExtensions
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "myTime=%%a"
set "myTime=%myTime:~8,6%"
set "myFlag=false"
if "%myTime%" LEQ "190000" set "myFlag=true"
if "%myFlag%"=="true" (
echo *my script*
)
Read
WMIC.exe: Windows Management Instrumentation Command
localdatetime: Win32_OperatingSystem class property in CIM_DATETIME format
set "myTime=%myTime:~8,6%": Extract part of a variable (substring)
Set Ag=WScript.Arguments
If CDate(Ag(0)) > CDate(Ag(1)) then
Wscript.echo "Param 1 greater than Param 2"
wscript.quit 0
else
Wscript.echo "Param 1 NOT greater than Param 2"
wscript.quit 1
End If
To use in batch (use any valid time/date string)
scriptname.vbs 7pm 8pm
If errorlevel 0 if not errorlevel 1 echo Param 1 greater than param 2
If errorlevel 1 if not errorlevel 2 echo Param 2 greater than param 1
Or
scriptname.vbs 19:00:00 20:00:00
If errorlevel 0 if not errorlevel 1 echo Param 1 greater than param 2
If errorlevel 1 if not errorlevel 2 echo Param 2 greater than param 1
Is there a way to write an IF OR IF conditional statement in a windows batch-file?
For example:
IF [%var%] == [1] OR IF [%var%] == [2] ECHO TRUE
The zmbq solution is good, but cannot be used in all situations, such as inside a block of code like a FOR DO(...) loop.
An alternative is to use an indicator variable. Initialize it to be undefined, and then define it only if any one of the OR conditions is true. Then use IF DEFINED as a final test - no need to use delayed expansion.
FOR ..... DO (
set "TRUE="
IF cond1 set TRUE=1
IF cond2 set TRUE=1
IF defined TRUE (
...
) else (
...
)
)
You could add the ELSE IF logic that arasmussen uses on the grounds that it might perform a wee bit faster if the 1st condition is true, but I never bother.
Addendum - This is a duplicate question with nearly identical answers to Using an OR in an IF statement WinXP Batch Script
Final addendum - I almost forgot my favorite technique to test if a variable is any one of a list of case insensitive values. Initialize a test variable containing a delimitted list of acceptable values, and then use search and replace to test if your variable is within the list. This is very fast and uses minimal code for an arbitrarily long list. It does require delayed expansion (or else the CALL %%VAR%% trick). Also the test is CASE INSENSITIVE.
set "TEST=;val1;val2;val3;val4;val5;"
if "!TEST:;%VAR%;=!" neq "!TEST!" (echo true) else (echo false)
The above can fail if VAR contains =, so the test is not fool-proof.
If doing the test within a block where delayed expansion is needed to access current value of VAR then
for ... do (
set "TEST=;val1;val2;val3;val4;val5;"
for /f %%A in (";!VAR!;") do if "!TEST:%%A=!" neq "!TEST!" (echo true) else (echo false)
)
FOR options like "delims=" might be needed depending on expected values within VAR
The above strategy can be made reliable even with = in VAR by adding a bit more code.
set "TEST=;val1;val2;val3;val4;val5;"
if "!TEST:;%VAR%;=!" neq "!TEST!" if "!TEST:;%VAR%;=;%VAR%;"=="!TEST!" echo true
But now we have lost the ability of providing an ELSE clause unless we add an indicator variable. The code has begun to look a bit "ugly", but I think it is the best performing reliable method for testing if VAR is any one of an arbitrary number of case-insensitive options.
Finally there is a simpler version that I think is slightly slower because it must perform one IF for each value. Aacini provided this solution in a comment to the accepted answer in the before mentioned link
for %%A in ("val1" "val2" "val3" "val4" "val5") do if "%VAR%"==%%A echo true
The list of values cannot include the * or ? characters, and the values and %VAR% should not contain quotes. Quotes lead to problems if the %VAR% also contains spaces or special characters like ^, & etc. One other limitation with this solution is it does not provide the option for an ELSE clause unless you add an indicator variable. Advantages are it can be case sensitive or insensitive depending on presence or absence of IF /I option.
I don't think so. Just use two IFs and GOTO the same label:
IF cond1 GOTO foundit
IF cond2 GOTO foundit
ECHO Didn't find it
GOTO end
:foundit
ECHO Found it!
:end
A simple "FOR" can be used in a single line to use an "or" condition:
FOR %%a in (item1 item2 ...) DO IF {condition_involving_%%a} {execute_command}
Applied to your case:
FOR %%a in (1 2) DO IF %var%==%%a ECHO TRUE
Suppress executing twice
A comment pointed out that {execute_command} may be encountered twice. To avoid this, you can use a goto after the first encounter.
FOR %%a in (1 2) DO IF %var%==%%a (
ECHO TRUE
goto :continue
)
:continue
If you think there's a possibility that {execute_command} might be executed twice and you don't want that, you can just add && goto :eof:
FOR %%a in (1 2) DO IF %var%==%%a ECHO TRUE && goto :eof
Much simpler, and still on a single line.
Thanks for this post, it helped me a lot.
Dunno if it can help but I had the issue and thanks to you I found what I think is another way to solve it based on this boolean equivalence:
"A or B" is the same as "not(not A and not B)"
Thus:
IF [%var%] == [1] OR IF [%var%] == [2] ECHO TRUE
Becomes:
IF not [%var%] == [1] IF not [%var%] == [2] ECHO FALSE
Even if this question is a little older:
If you want to use if cond1 or cond 2 - you should not use complicated loops or stuff like that.
Simple provide both ifs after each other combined with goto - that's an implicit or.
//thats an implicit IF cond1 OR cond2 OR cond3
if cond1 GOTO doit
if cond2 GOTO doit
if cond3 GOTO doit
//thats our else.
GOTO end
:doit
echo "doing it"
:end
Without goto but an "inplace" action, you might execute the action 3 times, if ALL conditions are matching.
There is no IF <arg> OR or ELIF or ELSE IF in Batch, however...
Try nesting the other IF's inside the ELSE of the previous IF.
IF <arg> (
....
) ELSE (
IF <arg> (
......
) ELSE (
IF <arg> (
....
) ELSE (
)
)
The goal can be achieved by using IFs indirectly.
Below is an example of a complex expression that can be written quite concisely and logically in a CMD batch, without incoherent labels and GOTOs.
Code blocks between () brackets are handled by CMD as a (pathetic) kind of subshell. Whatever exit code comes out of a block will be used to determine the true/false value the block plays in a larger boolean expression. Arbitrarily large boolean expressions can be built with these code blocks.
Simple example
Each block is resolved to true (i.e. ERRORLEVEL = 0 after the last statement in the block has executed) / false, until the value of the whole expression has been determined or control jumps out (e.g. via GOTO):
((DIR c:\xsgdde /w) || (DIR c:\ /w)) && (ECHO -=BINGO=-)
Complex example
This solves the problem raised initially. Multiple statements are possible in each block but in the || || || expression it's preferable to be concise so that it's as readable as possible. ^ is an escape char in CMD batches and when placed at the end of a line it will escape the EOL and instruct CMD to continue reading the current batch of statements on the next line.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
(
(CALL :ProcedureType1 a b) ^
|| (CALL :ProcedureType2 sgd) ^
|| (CALL :ProcedureType1 c c)
) ^
&& (
ECHO -=BINGO=-
GOTO :EOF
)
ECHO -=no bingo for you=-
GOTO :EOF
:ProcedureType1
IF "%~1" == "%~2" (EXIT /B 0) ELSE (EXIT /B 1)
GOTO :EOF (this line is decorative as it's never reached)
:ProcedureType2
ECHO :ax:xa:xx:aa:|FINDSTR /I /L /C:":%~1:">nul
GOTO :EOF
It's possible to use a function, which evaluates the OR logic and returns a single value.
#echo off
set var1=3
set var2=5
call :logic_or orResult "'%var1%'=='4'" "'%var2%'=='5'"
if %orResult%==1 (
echo At least one expression is true
) ELSE echo All expressions are false
exit /b
:logic_or <resultVar> expression1 [[expr2] ... expr-n]
SETLOCAL
set "logic_or.result=0"
set "logic_or.resultVar=%~1"
:logic_or_loop
if "%~2"=="" goto :logic_or_end
if %~2 set "logic_or.result=1"
SHIFT
goto :logic_or_loop
:logic_or_end
(
ENDLOCAL
set "%logic_or.resultVar%=%logic_or.result%"
exit /b
)
If %x%==1 (
If %y%==1 (
:: both are equal to 1.
)
)
That's for checking if multiple variables equal value. Here's for either variable.
If %x%==1 (
:: true
)
If %x%==0 (
If %y%==1 (
:: true
)
)
If %x%==0 (
If %y%==0 (
:: False
)
)
I just thought of that off the top if my head. I could compact it more.
I realize this question is old, but I wanted to post an alternate solution in case anyone else (like myself) found this thread while having the same question. I was able to work around the lack of an OR operator by echoing the variable and using findstr to validate.
for /f %%v in ('echo %var% ^| findstr /x /c:"1" /c:"2"') do (
if %errorlevel% equ 0 echo true
)
While dbenham's answer is pretty good, relying on IF DEFINED can get you in loads of trouble if the variable you're checking isn't an environment variable. Script variables don't get this special treatment.
While this might seem like some ludicrous undocumented BS, doing a simple shell query of IF with IF /? reveals that,
The DEFINED conditional works just like EXIST except it takes an
environment variable name and returns true if the environment variable
is defined.
In regards to answering this question, is there a reason to not just use a simple flag after a series of evaluations? That seems the most flexible OR check to me, both in regards to underlying logic and readability. For example:
Set Evaluated_True=false
IF %condition_1%==true (Set Evaluated_True=true)
IF %some_string%=="desired result" (Set Evaluated_True=true)
IF %set_numerical_variable% EQ %desired_numerical_value% (Set Evaluated_True=true)
IF %Evaluated_True%==true (echo This is where you do your passing logic) ELSE (echo This is where you do your failing logic)
Obviously, they can be any sort of conditional evaluation, but I'm just sharing a few examples.
If you wanted to have it all on one line, written-wise, you could just chain them together with && like:
Set Evaluated_True=false
IF %condition_1%==true (Set Evaluated_True=true) && IF %some_string%=="desired result" (Set Evaluated_True=true) && IF %set_numerical_variable% EQ %desired_numerical_value% (Set Evaluated_True=true)
IF %Evaluated_True%==true (echo This is where you do your passing logic) ELSE (echo This is where you do your failing logic)
Never got exist to work.
I use
if not exist g:xyz/what goto h:
Else xcopy c:current/files g:bu/current
There are modifiers /a etc. Not sure which ones. Laptop in shop. And computer in office. I am not there.
Never got batch files to work above Windows XP
A much faster alternative I usually use is as follows, as I can "or" an arbitrary number of conditions that can fit in variable space
#(
Echo off
Set "_Match= 1 2 3 "
)
Set /a "var=3"
Echo:%_Match%|Find " %var% ">nul || (
REM Code for a false condition goes here
) && (
REM code for a true condition goes here.
)
it's quite simple, just use below
IF %var% == 1 (
ECHO TRUE)
IF %var% == 2 (
ECHO TRUE)
Another option is to display the current environment variables and exploit the default behaviour of FINDSTR:
FINDSTR "hello there" x.y searches for "hello" or "there" in file x.y.
So
SET | FINDSTR /I /X "var=1 var=2" >NUL
IF %ERRORLEVEL% EQU 0 (
ECHO TRUE
) ELSE (
ECHO FALSE
)
Where
/I Specifies that the search is not to be case-sensitive.
/X Prints lines that match exactly.
If regular expressions are preferred, use FINDSTR /R /I "^var=1$ ^var=2$" >NUL instead.
Edit: FINDSTR /R should be used if the variable string includes a space, e.g., FINDSTR /R /I "^var=1 a$ ^var=2 b$" >NUL.
Edit: If the variable string includes spaces, a literal search string should be used. E.g., FINDSTR /I /X /C:"var=1 a" /C:"var=2 b" >NUL.
There is no OR operator but you can write (the pseudocode)
IF [%var%] == [1] OR IF [%var%] == [2] ECHO TRUE
like
IF "%var%" == "1" SET "match=y"
IF "%var%" == "2" SET "match=y"
IF DEFINED match ECHO TRUE
Note that the double quotes prevents a syntax error from being triggered if var is undefined.
I took bogdan's solution to the next level by building an extern function that is a callable and clean abstraction of IF, so it can be used in inline blocks. Don't look further, if you build a batch library anyways.
lib.cmd
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS
SHIFT & GOTO:%1
: Common batch extension library.
:::
: Performs conditional processing in batch programs. Is callable for inline use.
: Arguments:
: %1 - /I for case-insensitive comparison on strings, can be skipped for case-sensitive comparison.
: %2 - NOT to negate the result, can be skipped.
: %3 - EXIST for file checks or first string to compare with, also supports "string1"=="string2" (full condition).
: %4 - If EXIST is specified, path to the directory or file.
: Otherwise if %3 is not a full condition, this argument has to be one of:
: - == Compares both strings to be equal using lstrcmpW or lstrcmpiW (case-insensitive).
: - EQU Converts both strings to numbers and checks if they are equal.
: - NEQ Converts both strings to numbers and checks if they are not equal.
: - LSS Converts both strings to numbers and checks if the first is lesser than the second.
: - LEQ Converts both strings to numbers and checks if the first is lesser or equal than the second.
: - GTR Converts both strings to numbers and checks if the first is greater than the second.
: - GEQ Converts both strings to numbers and checks if the first is greater or equal than the second.
: If a string cannot be parsed to a number, its numeric representation will be used.
: This argument can be skipped, so == will be used.
: %5 - If %3 is not a full condition, this argument has to be the second string to compare.
: Outputs:
: Nothing
: Returns:
: 0, if the condition is met, 1 otherwise
:::
:test-if
IF "%~1"=="/I" SET "I=/I " & SHIFT
IF "%~1"=="/i" SET "I=/I " & SHIFT
IF "%~1"=="NOT" SET "NOT=NOT " & SHIFT
IF "%~1"=="not" SET "NOT=NOT " & SHIFT
IF "%~1"=="EXIST" SET "EXIST=EXIST " & SHIFT
IF "%~1"=="exist" SET "EXIST=EXIST " & SHIFT
SET "string1=%~1%"
IF "%~3"=="" (
SET "comp=^=^="
SET "string2=%~2"
) ELSE (
SET "comp=%~2"
SET "string2=%~3"
)
IF %I%%NOT%%EXIST% "%string1%" %comp% "%string2%" (
ENDLOCAL & EXIT /B 0
)
ENDLOCAL & EXIT /B 1
Usage
( ( CALL lib test-if "%1" == "foo" ) || ( CALL lib test-if "%1" == "bar" ) ) && (
ECHO "Argument is foo or bar"
)
lib is the path to the lib.cmd, the suffix .cmd is not mandatory for cmd-files
Any IF syntax is compatible with this abstraction, so you can also do things like test-if EXIST "path" or test-if not 300 LSS 200
The == in test-if "%1" == "foo" will be stripped away by batch and I address this fact in my case, but this causes test-if "%1" "foo" to be valid as well, it's not the standard though.
Realizing this is a bit of an old question, the responses helped me come up with a solution to testing command line arguments to a batch file; so I wanted to post my solution as well in case anyone else was looking for a similar solution.
First thing that I should point out is that I was having trouble getting IF ... ELSE statements to work inside of a FOR ... DO clause. Turns out (thanks to dbenham for inadvertently pointing this out in his examples) the ELSE statement cannot be on a separate line from the closing parens.
So instead of this:
FOR ... DO (
IF ... (
)
ELSE (
)
)
Which is my preference for readability and aesthetic reasons, you have to do this:
FOR ... DO (
IF ... (
) ELSE (
)
)
Now the ELSE statement doesn't return as an unrecognized command.
Finally, here's what I was attempting to do - I wanted to be able to pass several arguments to a batch file in any order, ignoring case, and reporting/failing on undefined arguments passed in. So here's my solution...
#ECHO OFF
SET ARG1=FALSE
SET ARG2=FALSE
SET ARG3=FALSE
SET ARG4=FALSE
SET ARGS=(arg1 Arg1 ARG1 arg2 Arg2 ARG2 arg3 Arg3 ARG3)
SET ARG=
FOR %%A IN (%*) DO (
SET TRUE=
FOR %%B in %ARGS% DO (
IF [%%A] == [%%B] SET TRUE=1
)
IF DEFINED TRUE (
SET %%A=TRUE
) ELSE (
SET ARG=%%A
GOTO UNDEFINED
)
)
ECHO %ARG1%
ECHO %ARG2%
ECHO %ARG3%
ECHO %ARG4%
GOTO END
:UNDEFINED
ECHO "%ARG%" is not an acceptable argument.
GOTO END
:END
Note, this will only report on the first failed argument. So if the user passes in more than one unacceptable argument, they will only be told about the first until it's corrected, then the second, etc.
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