Windows batch - Breaking out of a loop using variables - batch-file

So, I've been trying to create a simple spinning line thing that goes on for a set number of loops. I've encountered a problem: I can't find a way to add to a variable, or have a loop counter. This is my code so far (Other general criticisms are accepted too: I'm new to this and it all helps.)
#echo off
:1
echo
echo
echo
echo -
echo
cls
echo
echo
echo /
echo
cls
echo
echo
echo I
echo
cls
echo
echo
echo \
echo
cls
echo
echo
echo -
echo
IF %timer%
goto 1
pause
Really sorry if it's already been asked; I just can't seem to find what I'm looking for. Also, it's very possible this could just be a simple command, in which case i apologise again.

There's a couple of errors with your code as it stands.
The major one is
IF %timer%
goto 1
Batch is a ver old-fashioned language and is very particular about syntax. You can get a syntax description by typing
if /? |more
at the prompt. (replace if with the keyword you desire)
if requires
if string1==string2 thingtodoiftrue
Over the years, the syntax has been expanded, still maintaining the old version for compatibility so that the general form is now
if string1==string2 (thingtodoiftrue) else (thingtodoiffalse)
where == may now be a number of operators (equ, neq, ...); you can add a /i switch to make the strings case-insensitive, or a not keyword to reverse the sense. The parentheses are optional if the thingtodo is a single statement.
There are some quirky syntax requirements however. Either thingtodoiftrue or ( must be on the same physical line as the if keyword. The sequence ) else ( must all be on one physical line (if it's used)
As for performing some variety of count using your original structure, there are many ways. Here's one:
#echo off
set count=15
:1
....
set /a count=count-1
IF %count% neq 0 goto 1
pause

This may be what you are looking for. This runs the code 10000 times and it could be modified according to your need.
#echo off
for /l %%i in (1,1,10000) do (
echo -
cls
echo /
cls
echo I
cls
echo \
cls
)
Cheers, G

#echo off
setlocal enableextensions disabledelayedexpansion
rem Get a 0x13 character (carriage return) char inside a variable
for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
rem Spin the line
setlocal enabledelayedexpansion
for /l %%a in (1 1 5000) do for %%b in (- \ ^| /) do (
set /a "pct=%%a / 50"
<nul set /p ".=%%b !pct!%% !cr!"
)
endlocal
The basic idea is to output the spinning element, followed by the percentage. To keep all the output in the same line
<nul set /p "var=outputtext"
is used. This echoes the indicated text to console, waits for input, that is readed from nul, ending the prompt wait and continues without moving the cursor to the next line. There is no line feed
Now, it is necessary overwrite this line with the new information each time. To do it, it is necessary to get a carriage return character into a variable. For it, the for /f is used, retrieving the needed carriage return character from the output of the copy command.
note: the disabledelayedexpansion is necessary in this step to avoid problems if the full path/filename of the batch file (%~f0) contains any exclamation character.
The remaining code just iterates painting the corresponding character in the list, calculating the percentage to output and printing all to the console, without the line feed (<nul set /p ...) but with an aditional carriage return that moves the cursor to the start of the line. So, the next output will overwrite the previous one
note: in this case, enabledelayedexpansion is needed to get access to the content of the changed percentage variable and to output the carriage return. Without delayed expansion active, the parser removes the carriage return from the output.

Related

Writing to a file in batch

I've set up an app for a couple of friends and me in batch with a auto-updating system, but I need to add a line of code in the auto-updating system. I decided to completely rewrite the file, it takes a long time to add 'echo' in from to every line and, '>>text.txt' at the end of every line and added '^' when needed, so I was wondering if there was an easier way of writing lot's of code to a file in batch.
Example:
#echo off
rem I need a way to do the following without adding 'echo' and '>>text.txt'
echo echo Checking for updates... >text.txt
echo echo 1.4 ^>^>new.txt >>text.txt
echo ping localhost -n 2 ^>nul >>text.txt
rem and so on and so on.
Or if there is a way to simply add a new line of code in a specific place in the file, that would also help!
Thanks in advance!
The following is how you can more easily and efficiently do what your current code does, by removing all of those individual write processes.
#( Echo #Echo Checking for updates...
Echo #(Echo 1.4^)^>^>"new.txt"
Echo #(%__AppDir__%ping.exe LocalHost -n 2^)^>NUL
)>"text.txt"
There are other possibilities, but at this time, based on the lack of information in your question, I'm reluctant to expand further at this time.
If I understand correctly, then you could do the following:
in the batch file, prepend each line of text that you want to output with :::: (this constitutes an invalid label that is going to be ignored);
then use the following code:
rem // Redirect to file:
> "text.txt" (
rem // Find all lines that begin with `::::` and loop over them:
for /F "delims=" %%T in ('findstr "^::::" "%~f0"') do (
rem // Store currently iterated line:
set "TEXT=%%T"
rem // Toggle delayed expansion to avoid loss of `!`:
setlocal EnableDelayedExpansion
rem // Remove `::::` prefix and output remaining line:
echo(!TEXT:*::::=!
endlocal
)
)
replace set "TEXT=%%T" by call set "TEXT=%%T" if you want to enable percent expansion within the returned text (so it could, for example, contain %~nx0, which would then be expanded to the file name of the script).
I am using this technique a lot (without the output redirection) for help systems in my batch files (/?).
Your asked
I need a way to do the following without adding echo and >>text.txt
The script takes advantage of the line continuation character, the caret ^.
The first character after the caret ^ is always escaped, so do linefeed characters:
#echo off
SETLOCAL EnableDelayedExpansion
call :init
>text.txt (
echo #echo off%NL%
Checking for updates...%NL%
>^>new.txt echo 1.4%NL%
>NUL ping localhost -n 2
)
ENDLOCAL
exit /b
:init
( set LF=^
%= 0X0D FORM FEED =%
)
::0X0A Carriage Return
FOR /F %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
::Create newline/line continuation character
set ^"NL=^^^%LF%%LF%^%LF%%LF%^^" %= Unix-Style Endings \n =%
::set ^"NL=%CR%^^^%LF%%LF%^%LF%%LF%^^" %= Windows-Style Endings \r\n =%
exit /b
The variable %LF% is a escaped linefeed, and %NL% is a escaped %LF% plus a escaped caret ^ for line continuation.
The code
>^>new.txt echo 1.4%NL%
>NUL ping localhost -n 2
might seem strange. Why isn't the first caret ^ escaped?
Because %NL% already escaped it.
Sources:
Explain how Windows batch newline variable hack works
https://stackoverflow.com/a/5642300/12861751
https://www.dostips.com/forum/viewtopic.php?t=6369

How do I echo strings on the same line as a previous echo?

How do I echo "I have 1 number" using two echo commands in a batch file?
echo "I have "
...
... do stuff
...
echo "1 number"
creates two lines:
I have
1 number
how do I suppress the cr/lf on the first echo? to create:
I have 1 number
this appears to do it:
<nul set /p=I have
echo 1 number
note that this example is too simplistic- in a more general case the second part is not just an echo but the result of some calculation or manipulation, for example cd:
<nul set /p=set currentdir=>a.bat
cd>>a.bat
I can't see that this functionality is possible with the PART1 answer given above.
Perhaps I am missing part of the problem. Save the first part in a variable, then use it later with the latter part.
SET "PART_1=I have "
.
... do stuff
.
ECHO %PART_1% 1 number
How about...
C:\Users\lit\t>ECHO SET "currentdir=%CD%" >a.bat
C:\Users\lit\t>type a.bat
SET "currentdir=C:\Users\lit\t"
Multiple lines...? The ECHO command writes a newline CRLF at the end of anything it outputs. You are not going to get a subsequent ECHO command to write over the newline from the first ECHO. How about...
SET TMPFILE=%TEMP%\mytempfile.tmp
ECHO>"%TMPFILE%" I have
... do stuff
FOR /F "usebackq tokens=*" %%a IN (`TYPE "%TMPFILE%"`) DO (SET "S1=%%~a")
ECHO %S1% 1 file
IF EXIST "%TMPFILE%" (DEL "%TMPFILE%")

Get cursor position via Windows 10 console VT-100 escape sequence

I'm playing around with the new (limited) support for VT-100 escape sequences within the Windows 10 console. The supported sequences are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx.
In particular, the following sequence that reports the current cursor position interests me.
ESC[6n - responds with ESC[<n>;<m>R,
where <n> is the row number, and <m> the column number
The response is passed off as keyboard input, and appears on the screen, but I have no idea how to programmatically make use of the information. Ideally I would like to get the <n> and <m> values into environment variables from within a batch file.
But if anyone can demonstrate how to capture the variables using any language, then I may be able to use that knowledge to develop an effective batch file strategy.
I can get close with the following simple script called ANSI.BAT
#echo off
setlocal enableDelayedExpansion
for /f "delims=" %%C in (
'forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo(0x1B"'
) do set "esc=%%C"
set "csi=%esc%["
echo(Inquiry:%csi%6n
set /p "pos="
echo response=!pos:%esc%=ESC!
--OUTPUT--
C:\test>ansi
Inquiry:
^[[3;9R
response=ESC[3;9R
C:\test>
I can easily parse out the values using FOR /F, once I have the response in a variable. The problem I am having is I must manually press the <Enter> key after the response appears on the screen in order to terminate the input for my SET /P statement. I am stumped on where to go from here...
EDIT - One last requirement: I don't want the inquiry response to appear on the screen, as that disrupts the screen, and changes the cursor position. I suspect this may be the toughest nut to crack, perhaps impossible with pure batch.
Major change after three years
It works with reading the response by using XCOPY or REPLACE.
I'm using replace here, to avoid language dependent problems.
#echo off
for /F "delims=#" %%a in ('"prompt #$E# & for %%a in (1) do rem"') do set "ESC=%%a"
call :get_cursor_pos
exit /b
:get_cursor_pos
set "response="
set pos=2
:_get_loop
REM *** Request Cursor position
<nul set /p "=%ESC%[6n"
FOR /L %%# in (1 1 %pos%) DO pause < CON > NUL
for /F "tokens=1 skip=1 eol=" %%C in ('"REPLACE /W ? . < con"') DO (
set "char=%%C"
)
set "response=%response%%char%"
set /a pos+=1
if "%char%" NEQ "R" goto :_get_loop
set response
exit /b
The main problem is, XCOPY or REPLACE allows me to read one character from the input stream, but then clears the remaining buffer.
Conversely, PAUSE reads one character, preserving the remaining buffer, but does not reveal what character was read.
To solve this, I issue the query multiple times, reading a different character of the response each time. For each iteration I use a combination of 2 or more PAUSE statements followed by REPLACE to read a specific character of the response. Each iteration uses one more PAUSE than the prior iteration, until I am able to read the terminating R.
I developed this technique and initially posted it at DosTips - Query States using Console Virtual Terminal Sequences.
I have NOT Windows 10, so I can't complete any test. However, if the response of the Ansi ESC[6n sequence is fill the keyboard input buffer with ESC[<n>;<m>R characters, then it is just necessary to add an Enter key to such input in order to read it via SET /P command, and this can be done via SendKeys JScript method.
I also used a simpler method to get an ESC character in a variable.
EDIT: I modified the code accordingly to comments...
#if (#CodeSegment == #Batch) #then
#echo off
title Ansi Test
setlocal EnableDelayedExpansion
for /F %%a in ('echo prompt $E ^| cmd') do set "esc=%%a"
set "csi=%esc%["
echo Inquiry:%csi%6n
cscript //nologo //E:JScript "%~F0"
set /p "pos=" > NUL
echo response=!pos:%esc%=ESC!
#end
var sh = WScript.CreateObject("WScript.Shell");
sh.AppActivate("Ansi Test");
sh.SendKeys("{ENTER}");
Please, post the result...

Batch Loop menu (multiple selection)

I'm trying to set up a batch menu that allows for multiple selection at once then runs all the functions. Sequence that functions are not relevant just the fact that the functions will be run with out, outside errors. Here is the code that I have so far.
#Echo off
Echo Please Enter the corrasponding numbers separated by a space or colon (,)
Echo for the Options that you would like to run e.g. 1 4,3 2
Echo Option #1
Echo Option #2
Echo Option #3
Echo Option #4
Echo.
SET /P Selection=Please Select Restore Options?
echo You chose: %Selection%
setlocal ENABLEDELAYEDEXPANSION
Set /a index = 0
FOR %%A IN (%Selection%) DO (
SET Array[!index!] = %%A
SET /a index += 1
)
for /F "tokens=2 delims==" %%s in ('set Array[') DO (
set string=%%s
set string=%string: =%
echo %string%
Call :Opt%string%
)
pause
goto :EOF
:Opt1
ECHO Option 1's code
GOTO :EOF
:Opt2
ECHO Option 2's code
GOTO :EOF
:Opt3
ECHO Option 3's code
GOTO :EOF
:Opt4
ECHO Option 4's code
GOTO :EOF
The code I have works to the point where trying to call the Array veriable and attach it to a Call e.g. Call :Opt%%s
The probelm I have is that the array variable keeps coming out with a space proceeding the selected variable. So I have tried combating this with set string=%string:=% but I keep getting an error.
Error :
either echo is off and only opt is getting called with out the selected variable.
Help with this would be amazing, Thanks in advance.
The start of the problems is
SET Array[!index!] = %%A
------------------^-^---- = aditional spaces
This aditional spaces are used, so you end with a variable with an aditional space in its name and an aditional space in its value. So, better use
SET "Array[!index!]=%%A"
The reason for the echo error is you forget to use delayed expansion in the for %%s loop. You change the %string% variable inside the loop and try to use the changed value inside the same loop.
for /F "tokens=2 delims==" %%s in ('set Array[') DO (
set "string=%%s"
set "string=!string: =!"
echo !string!
Call :Opt!string!
)
But the corrections indicated in the original set make it unnecessary to replace the spaces.
MC ND solved most of the problems with your code.
One trivial issue - the punctuation is a comma, not a colon ;-)
But a more serious issue, what if the user entered 3 choices, and there already was a variable named Array[4]? It would run that extra value that hadn't been entered by the user. It would even attempt to run a value stored in Array[anythingGoes.
You've got the number of values stored in "index", so why not use it? A more common and simpler way to iterate the array is to use a FOR /L loop. This also preserves the original order. Your way would change the order once you get 10 or more entries. (I know you say order doesn't matter, but why change the order if you don't have to?)
setlocal enableDelayedExpansion
for /l %%N in (1 1 %index%) do (
echo !Array[%%N]!
call :Opt!Array[%%N]!
)
But I don't see a reason to mess with an array at all. Your loop that parses the user input could simply call the functions directly. Now you don't even need delayed expansion.
for %%A in (%Selection%) do (
echo %%A
call :Opt%%A
)

Batch Making Passwords

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.

Resources