Batch command line argument matching - batch-file

I really can't understand why this refuses to work.
#ECHO OFF
SET CURRDIR=%CD%
if [%1%]==[1] GOTO ONE
if [%1%]==[2] GOTO TWO
if [%1%]==[3] GOTO THREE
:ONE
call "%CURRDIR%\PlanningProduct.bat"
:TWO
call "%CURRDIR%\Organization.bat"
:THREE
call "%CURRDIR%\Measure.bat"
pause
I did the following in the command line
I:\BatchMania>I:\BatchMania\Home.bat 1
and the output I get is funny as follows:
Planning
Organization
Measure
Press any key to continue . . .
This is weird. Hope to never write this kind of code!!!

There are several items that need attention here:
You have implemented "fall-through" scenarios, where THREE or TWO+THREE is executed in 2 distinct cases, or ONE+TWO+THREE in all other cases;
I actually do not think the if statements work as intended: [%1%]==[1] should either be [%1%]==[1%] or [%1]==[1];
Should double backslashes be a problem when this script is run from the root, then consider using %__CD__%;
All if statements can be omitted if you just use goto batch%~1 (or similar) and rename your labels; OR
All number labels can be omitted if you just specify the batch to call in the if statements and/or use if-else constructs.
Here are some alternative implementations:
#ECHO OFF
set CURRDIR=%CD%
goto :BATCH%~1 2>NUL
goto :UHOH
:BATCH1
call "%CURRDIR%\PlanningProduct.bat"
goto :DONE
:BATCH2
call "%CURRDIR%\Organization.bat"
goto :DONE
:BATCH3
call "%CURRDIR%\Measure.bat"
goto :DONE
:UHOH
echo Invalid parameter "%~1"
:DONE
pause
#ECHO OFF
set CURRDIR=%CD%
if "%~1"=="1" (
call "%CURRDIR%\PlanningProduct.bat"
) else if "%~1"=="2" (
call "%CURRDIR%\Organization.bat"
) else if "%~1"=="3" (
call "%CURRDIR%\Measure.bat"
) else (
echo Invalid parameter "%~1"
)
pause
#ECHO OFF
set CURRDIR=%CD%
set BAT=
if "%~1"=="1" set BAT=PlanningProduct.bat
if "%~1"=="2" set BAT=Organization.bat
if "%~1"=="3" set BAT=Measure.bat
call "%CURRDIR%\%BAT%" 2>NUL
pause

Does the below produce what you expect?
:ONE
call "%CURRDIR%\PlanningProduct.bat"
GOTO OUT
:TWO
call "%CURRDIR%\Organization.bat"
GOTO OUT
:THREE
call "%CURRDIR%\Measure.bat"
:OUT
pause

After it junps to ONE and executes the call, it is just going to continue on the next line (TWO). A label does not change the execution sequence, it is still going to parse the file line by line unless you jump somewhere.
Either jump away to a specific point:
...
:ONE
call "%CURRDIR%\PlanningProduct.bat"
GOTO DONE
:TWO
...
:DONE
pause
or end the batch:
:ONE
call "%CURRDIR%\PlanningProduct.bat"
pause
GOTO :EOF

Related

GOTO in IF Statement Batch Scipt

My batch script compares a username to a list of usernames and if the username variable is in the list of usernames, then start notepad.exe. Else, print no. I am doing this using GOTO.
So if the username is in the list, goto match1 and launch notepad. Else, goto match2 and print no. But it seems even when the username is in the list, it always goes to the else part and prints no. Here's my code:-
#ECHO OFF
set user=username1
set list=username3 username2 username1
(FOR %%a IN (%list%) DO (if %user%==%%a (GOTO MATCH1
) else ( GOTO MATCH2
)
))
:MATCH1
notepad.exe
:MATCH2
echo no
pause
However, when I make the list this way, it launches notepad and prints no:-
set list=username1 username2 username3
Please note that when I did this without using GOTO, it worked well. So for example, if there's a match, launch notepad, else, print no. Am I using GOTO in a wrong manner? Or did I misunderstood the functionality of GOTO?
You cannot use GOTO like that, as soon as GOTO is run the loop has broken, (it does not return to the FOR loop). You could use CALL instead.
#ECHO OFF
SET "user=username1"
SET "list=username3 username2 username1"
FOR %%A IN (%list%) DO IF /I "%user%"=="%%a" (CALL :MATCH1) ELSE CALL :MATCH2
PAUSE
EXIT/B
:MATCH1
notepad.exe
GOTO :EOF
:MATCH2
ECHO no
GOTO :EOF
#ECHO OFF
set user=username1
set list=username3 username2 username1
FOR %%a IN (%list%) DO if %user%==%%a GOTO MATCH1
GOTO MATCH2
:MATCH1
notepad.exe
:MATCH2
echo no
You misunderstood for. %%a would acquire each of the list values in turn, and perform the match.
Either the match will be true or it will be false. No other choices. Your code checks for a match on the first string. On a match, goto match1. Otherwise, goto match2. This means you leave the loop, so the for only uses the first value.
with the modified code, the goto will be executed if the first string matches, otherwise it will look a the second, and otherwise at the third. If none match, the for has run out of options, so it terminates and executes the following instruction, which is goto match2.
Note that batch simply executes lines one by one until it reaches a goto call or exit. Consequently, once the instructions in match1 are executed, execution will flow through to the following instruction, so execute match2 as well.

"The syntax of the command (arithm expression) is incorrect" in nested "IF" statement in batch

I'm writing a batch file right now and encountered a problem. I need to make "OR" statement in "AND" statement in batch script.
Here's my code:
#echo off
set /p a="a="
set /p b="b="
if "%a%" == "5" (
if "%b%" == "8" (
set /a "c=%a%*%b%"
goto :win
)
if "%b%" == "2"(
set /a "c=%a%+%b%"
goto :win
)
)
goto :fail
:win
echo %c%
goto :exit
:fail
echo U'r not a magician!
:exit
pause
The problem is that it only works fine without second nested "IF" expression but I need a possibility of getting both results.
Your code works perfectly fine when you insert a SPACE between "2" and (.
The line that is causing problem is:
if "%b%" == "2"(
To fix, change the code to:
if "%b%" == "2" (
Now that you've accepted your answer, I'd just like to mention that your code seems OTT, because you already know the accepted parameters and what you're going to do with them. (There isn't really a need to perform the arithmetic operations). This structure would act in the same manner:
#Echo Off
Set/P "a=a="
Set/P "b=b="
Set "c="
If "%a%][%b%"=="5][8" Set "c=40"
If "%a%][%b%"=="5][2" Set "c=7"
If Defined c (Echo=%c%) Else (Echo=U'r not a magician!)
Pause

Why CALL prints the GOTO help message in this script?And why command after that are executed twice?

Here's one interesting thread. And I tried to play with the two things discussed there.
You can access labels with special symbols with double expansion.
Labels that contain /? cannot be used because GOTO and CALL prints their help messages instead of execution.
And here's the result:
#echo off
setlocal enableDelayedExpansion
set "label=/?"
call :%%label%%
echo == Test message to check if the CALL or GOTO ( or neither ) command is executed ==
exit /b 0
:/?
echo == CALL or GOTO has been executed ==
exit /b 0
And the output:
Directs cmd.exe to a labeled line in a batch program.
GOTO label
label Specifies a text string used in the batch program as a label.
You type a label on a line by itself, beginning with a colon.
If Command Extensions are enabled GOTO changes as follows:
GOTO command now accepts a target label of :EOF which transfers control
to the end of the current batch script file. This is an easy way to
exit a batch script file without defining a label. Type CALL /? for a
description of extensions to the CALL command that make this feature
useful.
== Test message to check if the CALL or GOTO ( or neither ) command is executed ==
== Test message to check if the CALL or GOTO ( or neither ) command is executed ==
And the code after the CALL is executed twice??
EDIT
This is even more unexplainable to me:
#echo off
setlocal enableDelayedExpansion
set "label=/?"
set /a x=1
call :%%label%% >nul
set /a x=x+1
echo ---
echo -%x%-
echo ---
exit /b 0
:/?
echo == CALL or GOTO has been executed ==
echo == first argument : %1 ==
exit /b 0
The output is:
---
-3-
---
The code after the call of the CALL for sure is executed twice, but the output of the first run can be redirected in the same line?
I'm always surprised, that you still found things that never came to my mind to test.
Contrary to Aacini, I don't believe that :/? acts here as a valid label.
Else this should find such a label.
I suppose that the CALL command is internally composed of a stack pusher function and then just use GOTO to jump to the label.
And as you are using late expansion the /? isn't detected by the CALL command itself, but by the GOTO command.
The goto shows only the help info and finished, but as the call has already pushed the fileposition to the stack it works like calling this filepostion and later return to the same location!
set "help=^ /?"
call :myLabel%%help%%
This shows also the help, like a GOTO :myLabel /? would do.
But this one don't
set "help=/?"
call :myLabel %%help%%
I suppose, the GOTO gets only the label parsed by the CALL, the other parameters are moved to %1, %2, ...
And this could explain why only the label can use delayed expansion two times.
#echo off
setlocal EnableDelayedExpansion
set "label=myLabel"
set "pointer=^!label^!"
call :!pointer!
exit /b
:myLabel
echo it works
Interesting! The first code may be reduced to:
#echo off
setlocal
set "label=/?"
call :%%label%%
echo == Test message to check if the CALL or GOTO ( or neither ) command is executed ==
and still show the same output, that is, it behaves the same way than this code:
#echo off
setlocal
call :/?
echo == Test message to check if the CALL or GOTO ( or neither ) command is executed ==
where the call :/? is both a call command and a valid label, AND considering :/? a valid label. Wow!
Why the GOTO help is displayed? No idea!!!
EDIT: Additional tests
This code:
#echo off
setlocal
set "label=/?"
set i=0
call :%%label%% & echo Command in same line: i=%i%
set /A i+=10
echo == Test message == i=%i%
show the GOTO help screen first, and then:
== Test message == i=10
Command in same line: i=0
== Test message == i=20
but if EnableDelayedExpansion is added and %i% is changed by !i! in the call line, then it shows:
== Test message == i=10
Command in same line: i=10
== Test message == i=20
as "expected"...
EDIT #2
The test below show that the call :%%label%% command does NOT report the ERRORLEVEL=1 standard on "label not found" error:
#echo off
setlocal
set "label=/?"
call :notFound
echo Label not found: ERRROLEVEL = %ERRORLEVEL%
ver > NUL
echo Reset errorlevel: ERRROLEVEL = %ERRORLEVEL%
call :%%label%%
echo == Test message == ERRROLEVEL = %ERRORLEVEL%

For loop reading empty variable

I have a subroutine that runs in my batch file, during which I output to a textfile the success of each operation. An example is this...
set Tasks=One Two Three
set LogFile=Log.txt
for %%T in (%Tasks%) do call :Operation %%T
:Operation
set LogEntry=%1
echo %LogEntry%>> %LogFile%
goto :EOF
Using this I can get one, two and three written into the text file but I also get a final entry with an empty variable.
Can anyone see what the issue is?
:operation is just a label. When the for command ends its work, the batch file continues its execution, enters the code after the label and the code inside it gets executed, but this time without any passed parameter.
Place a goto :eof or a exit /b after the for command to avoid it
set Tasks=One Two Three
set LogFile=Log.txt
for %%T in (%Tasks%) do call :Operation %%T
goto :eof
:Operation
set LogEntry=%1
echo %LogEntry%>> %LogFile%
goto :EOF

How can I exit a batch file from within a function?

I have a simple function written to check for directories:
:direxist
if not exist %~1 (
echo %~1 could not be found, check to make sure your location is correct.
goto:end
) else (
echo %~1 is a real directory
goto:eof
)
:end is written as
:end
endlocal
I don't understand why the program would not stop after goto:end has been called. I have another function that uses the same method to stop the program and it work fine.
:PRINT_USAGE
echo Usage:
echo ------
echo <file usage information>
goto:end
In this instance, the program is stopped after calling :end; why would this not work in :direxist? Thank you for your help!
I suppose you are mixing call and goto statements here.
A label in a batch file can be used with a call or a goto, but the behaviour is different.
If you call such a function it will return when the function reached the end of the file or an explicit exit /b or goto :eof (like your goto :end).
Therefore you can't cancel your batch if you use a label as a function.
However, goto to a label, will not return to the caller.
Using a synatx error:
But there is also a way to exit the batch from a function.
You can create a syntax error, this forces the batch to stop.
But it has the side effect, that the local (setlocal) variables will not be removed.
#echo off
call :label hello
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" goto :halt
exit /b
:halt
call :haltHelper 2> nul
:haltHelper
()
exit /b
Using CTRL-C:
Creating an errorcode similar to the CTRL-C errorcode stops also the batch processing.
After the exit, the setlocal state is clean!
See #dbenham's answer Exit batch script from inside a function
Using advanced exception handling:
This is the most powerful solutions, as it's able to remove an arbitrary amount of stack levels, it can be used to exit only the current batch file and also to show the stack trace.
It uses the fact, that (goto), without arguments, removes one element from the stack.
See Does Windows batch support exception handling?
jeb's solution works great. But it may not be appropriate in all circumstances. It has 2 potential drawbacks:
1) The syntax error will halt all batch processing. So if a batch script called your script, and your script is halted with the syntax error, then control is not returned to the caller. That might be bad.
2) Normally there is an implicit ENDLOCAL for every SETLOCAL when batch processing terminates. But the fatal syntax error terminates batch processing without the implicit ENDLOCAL! This can have nasty consequences :-( See my DosTips post SETLOCAL continues after batch termination! for more information.
Update 2015-03-20 See https://stackoverflow.com/a/25474648/1012053 for a clean way to immediately terminate all batch processing.
The other way to halt a batch file within a function is to use the EXIT command, which will exit the command shell entirely. But a little creative use of CMD can make it useful for solving the problem.
#echo off
if "%~1" equ "_GO_" goto :main
cmd /c ^""%~f0" _GO_ %*^"
exit /b
:main
call :label hello
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" exit
exit /b
I've got both my version named "daveExit.bat" and jeb's version named "jebExit.bat" on my PC.
I then test them using this batch script
#echo off
echo before calling %1
call %1
echo returned from %1
And here are the results
>test jebExit
before calling jebExit
hello
stop
>test daveExit
before calling daveExit
hello
stop
returned from daveExit
>
One potential disadvantage of the EXIT solution is that changes to the environment are not preserved. That can be partially solved by writing the environent to a temporary file before exiting, and then reading it back in.
#echo off
if "%~1" equ "_GO_" goto :main
cmd /c ^""%~f0" _GO_ %*^"
for /f "eol== delims=" %%A in (env.tmp) do set %%A
del env.tmp
exit /b
:main
call :label hello
set junk=saved
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" goto :saveEnvAndExit
exit /b
:saveEnvAndExit
set >env.tmp
exit
But variables with newline character (0x0A) in the value will not be preserved properly.
If you use exit /b X to exit from the function then it will set ERRORLEVEL to the value of X. You can then use the || conditional processing symbol to execute a command if ERRORLEVEL is non zero.
#echo off
setlocal
call :myfunction PASS || goto :eof
call :myfunction FAIL || goto :eof
echo Execution never gets here
goto :eof
:myfunction
if "%1"=="FAIL" (
echo myfunction: got a FAIL. Will exit.
exit /b 1
)
echo myfunction: Everything is good.
exit /b 0
Output from this script is:
myfunction: Everything is good.
myfunction: got a FAIL. Will exit.
Here's my solution that will support nested routines if all are checked for errorlevel
I add the test for errolevel at all my calls (internal or external)
#echo off
call :error message&if errorlevel 1 exit /b %errorlevel%<
#echo continuing
exit /b 0
:error
#echo in %0
#echo message: %1
set yes=
set /p yes=[no]^|yes to continue
if /i "%yes%" == "yes" exit /b 0
exit /b 1

Resources