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.
Related
This code is part of a chat program that I am currently working on. The 'else' part of my program is the one that doesn't work. The program quits instead of going to :home
:join
cls
if not exist "C:/Users/Public/room.cmd" (
echo No room has been found.
echo.
set /p choiceretry=Do you want to retry? y/n
if "%choiceretry%"=="y" goto join
if "%choiceretry%"=="n" goto home
) else (
cls
"C:/Users/Public/room.cmd"
echo A room has been found.
pause >nul
echo Joining
set roomjoined=1
echo %roomjoined%
goto home
)
:home
echo this finally works
pause
I have tried changing the code several times starting from 'echo Joining'
Anyone know why cmd quits?...
:) :) :)
Thanks in advance
The problem is the way you run room.cmd; you must use call to return from it:
call "C:/Users/Public/room.cmd"
Otherwise, execution will not return from room.cmd to the original batch file that ran it.
Hint: Consider to use choice instead of set /P for Y/N decisions.
Firstly, please don't left justify your code blocks. It's much easier to read code that's properly indented.
Secondly, when retrieving values within a code block, you need delayed expansion. See setlocal /? in a cmd prompt for more information. This is the reason for the unexpected behavior. Your variables retrieved within the same parenthetical code block in which they were set won't contain the values you expect unless you retrieve them with delayed expansion syntax. As an alternative, you could use the choice command and if errorlevel, which would result in a bit nicer user experience I think.
Thirdly, when testing user input, you should use the /i switch in your if statements for case-insensitivity. This isn't relevant if using choice / if errorlevel though.
Fourthly, Windows paths use backslashes, not forward slashes.
I'd fix it this way:
#echo off
setlocal
:join
cls
if errorlevel 1 set /P "=Retrying... "<NUL
if not exist "C:\Users\Public\room.cmd" (
echo No room has been found.
echo.
choice /c yn /n /m "Do you want to retry? [y/n] "
if errorlevel 2 goto home
goto join
) else (
"C:\Users\Public\room.cmd"
echo A room has been found.
pause >nul
echo Joining
set roomjoined=1
)
:home
echo this finally works
pause
Is there any code system like below :
#echo off
set /p location=Type Folder Location
copy "%location%\file.txt" "c:\Folder"
if copy is done goto ok
if not goto failed
:ok
echo File is copyed succesfully
:failed
echo File is not copyed
echo.
pause
exit
#echo off
set /p location=Type Folder Location
copy "%location%\file.txt" "c:\Folder"
if errorlevel 1 goto failed
:ok
echo File is copied succesfully
goto done
:failed
echo File is not copied
:done
echo.
pause
exit
Normally, when a command succeeds, the "magic" variable errorlevel is set to zero; if it fails, to non-zero.
The syntax if errorlevel n will be true if errorlevel is n or greater than n (this last point is important - if errorlevel 0 will always be true (in normal circumstances).
Unlike many languages, batch has no concept of the end of a "procedure" - it simply continues execution line-by-line until it reaches the end-of-file. Consequently, you need to goto :eof after completing the mainline, otherwise execution will continue through the subroutine code. :EOF is a predefined label understood by CMD to mean end of file. The colon is required.
(in this case, the goto done skips over the 'failed' message - often you want to terminate the batch under certain circumstances. there you'd use goto :eof)
Like #Magoo saids in your case you just need 1 condition to go forward.
But to answer your question: How to mention If else condition in batch file
#echo off
set /a $var=1
if %$var%==1 (goto:ok) else (goto:nok)
:ok
echo OK
exit/b
:nok
echo NOK
You can change the value of $var to check it
I have made a batch game where users can log in / register. But there is no point in having passwords if a person standing nearby can peep at the password. Normal password fields mask the characters with asterisks (*).
How can mask characters on a batch file?
I've seen this done on cmd before but I have no clue how.
You can use XCOPY for a hidden input, it can handle nearly all characters and you can also implement a backspace logic.
#echo off
setlocal EnableDelayedExpansion
call :input
echo(
echo '!input!'
if "!input!"=="password" echo OK
exit /b
:input
for /F "tokens=1 delims=# " %%a in ('"prompt #$H# & echo on & for %%b in (1) do rem"') do (
set "\b=%%a"
)
set "input="
:keyLoop
call :GetKey
if not defined key exit /b
if "!key!"=="!\b!" (
if defined input (
set "input=!input:~0,-1!"
<nul set /p ".=!\b! !\b!"
)
) ELSE (
<nul set /p ".=*"
set "input=!input!!key!"
)
goto :keyLoop
:GetKey
setlocal DisableDelayedExpansion
set "key="
for /F "usebackq delims=" %%L in (`xcopy /L /w "%~f0" "%~f0" 2^>NUL`) do (
if not defined key set "key=%%L"
)
(
endlocal
set "key=^%key:~-1%" !
exit /b
)
This code should be able to handle all characters, like ^!&%<>.
It's also possible to use backspace to delete the last entered character.
The line set "key=^%key:~-1%" ! seems odd, but it's used to escape the ! character with set "key=^!" ! in the delayed expansion context.
And to avoid problems for all other characters the last ! removes the caret, like in set "key=^A" ! will be evaluated to ``set "key=A"`
Ok, this is a bit different to what you may have had in mind, but that's you're fault for choosing batch for game dev.
The way I see it is you have 3 options:
Use an external program you self made in C#, C++, Python, [etc.]
Howver this requires an application to already do this for you (Which there probably is) or for you to have a knowledge in one of these languages
Use the choice command, to continuously take one key input and wait for the user to hit space to signify the end of the password
However this limits the password characters choice, and makes the program look ugly
Use 2 Batch threads, one that masks and tallies input while the other stores it to a variable.
This may be a bit dodgey at times, at would be a bit complicated but may be the only choice you have.
Now, as I was typing this an idea stuck my head on how to achieve this. Since it might take some time to test I thought I'd post the idea (as it seems to be a soloution to this problem, which has been around for a while).
Logic
One Batch Thread will simply use set /p to store all the input into a variable and upon completion will communicate to the other batch thread through the use of waitfor or a simple directory file.
Another Batch Thread would loop the pause >nul statement and would tally the number of times the pause statement is looped, printing out the appropriate amount of *'s. The other important job of this thread is to sense when the user has finished typing the password, upon which it exits.
Im starting to make this batch program now, but for now I'll just keep you informed of my idea so far.
Code
Login.bat
#echo off
Title Password Please:
:: This is the main code
REM This code relies on Masker.bat
REM SET password to be first and thrid letter,
REM of the day of the week.
set pass=%Date:~0,1%%Date:~2,1%
REM START Masker in the same window using /b tag and create hold.pass:
Echo 0 >%temp%\hold.pass
start /b Masker.bat "%pass%" *
REM Simply take input an leave the rest to Masker.bat
set /p pass_input=:
Echo 1 >>%temp%\hold.pass
cls
if /i "%pass%" NEQ "%pass_input%" (
Title Worng Password
Echo Wrong Password... Sorry.
Sleep 5
Exit
)
Pause > nul
REM Rest of Main game code is below or simply
:: START Main.bat & Exit
Masker.bat
#echo off
Title Password Please:
setlocal enabledelayedexpansion
:: This is not the main code
REM This code is called upon by Login.bat (or the Main.bat game code)
REM CREATE the variables "passlen" and "mask":
set password=%~1
set passlen=0
:miniloop
set /a passlen+=1
if "!password:~%passlen%,1!" NEQ "" goto :miniloop
set password=
set mask=%~2
if "%mask%" EQU "" set mask=*
REM MAIN loop
:loop
cls
for /l %%a in (1,1,%passlen%) do (<nul set /p=%mask%)
sleep -m 150
for /f "usebackq" %%a in ("%temp%\hold.pass") do (if "%%~a" EQU "1" Del %temp%\hold.pass & Exit)
goto :loop
It still needs some more improvements, but I've spent aroung 30 min on it with little success to make it dynamically tell you how many characters you have typed in.
Anyone cane take this up, be my guest. Everything else works fine
Mona
This works without pressing enter after input of the password.
If you enter the correct password, ok.
if you enter a wrong password, it will stop when you enter the 9th character (can be adapted).
It does not care about capitalization.
Problem: the password is stored as pure text in the code
#echo off
setlocal enabledelayedexpansion
set "s= abcdefghijklmnopqrstuvwxyz"
set p=
:loop
choice /C %s% /N >nul
set p=%p%!s:~%errorlevel%,1!&set /p =*<nul
if /i "%p%"=="secured" goto :right
if not "%p:~8,1%"=="" goto :wrong
goto :loop
goto :wrong
:right
echo you entered correct password: %p%
goto :eof
:wrong
echo you entered wrong password: %p%
goto :eof
You may use ReadFormattedLine subroutine for all kind of formatted input. For example, the command below read a password of 8 characters, display asterisks in the screen, and continue automatically with no need to press Enter:
call :ReadFormattedLine password="********" /M "Enter password (8 chars): "
This subroutine is written in pure Batch so it does not require any additional program, and it allows several formatted input operations, like read just numbers, convert letters to uppercase, etc. You may download ReadFormattedLine subroutine from Read a line with specific format.
FOR /L %%i IN (1,1,100) DO (
choice
echo %ErrorLevel%
)
%ErrorLevel% is always 0 no matter what choice you enter.
You are checking the errorlevel the wrong way.
Variables and commands inside a bracket pair like this...
(
command1
command2
command3
)
...act like they were run on a single line, like this command1 & command2 & command3.
Try this at the command line.
choice & echo %errorlevel%
If you execute the above command more than once, you will see that the previous errorlevel is echoed, not the current one.
Or try this on the command line:
set x=yes
( echo %x%
set x=no
echo %x%
)
Your output will be:
yes
yes
Just as if you'd entered echo %x% & set x=no& echo %x%
I like to think of it as the system doesn't have the time to update the variables. (Though it's more accurate to say that the variables only get updated after the entire line is executed.) This is true with all variables, not just the errorlevel.
To make variables in for loops work normally you need to call an internal label in your batch file (or an external batch file) like this.
#echo off
FOR /L %%i IN (1,1,100) DO call :dostuff %%i
goto :eof
:dostuff
choice /m "Question #%1"
echo %ErrorLevel%
==================================== Solution To Question Below
Alternatively, Microsoft has created a method for accessing the current value of variables inside of a bracket pair, they call it 'Delayed Expansion' because the line of code is interpreted twice.
To activate this mode you use the setlocal command with the enableDelayedExpansion switch, and access the variables with the ! character like this. FYI endlocal turns off the effects.
#echo off
setlocal enableDelayedExpansion
for /L %%i in (1,1,100) do (
choice /m "Question #%%i"
echo !ErrorLevel!
)
endlocal
As you can see my first example is easier to code, but my second example is easier to read. Whichever method you use will depend upon your needs and re-usability.
The setlocal command also has the effect of creating temporary variables that die after the endlocal command. This means you don't need to delete them when your batch file ends, and reverts any variables you changed during execution back to their original values. This is nice because you don't have to worry about 'stepping on' any preexisting variables.
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