(This is my first post here, so bear with me)
Can you show the last user-input in a batch file? I'm gonna try to keep it simple here.
#echo off
:menu
echo Type 1 to proceed.
set /p example=
if "%example%" == "1" GOTO :proceed
GOTO :error
:proceed
pause
:error
cls
echo You wrote (last user input), that's not correct.
timeout 30
GOTO :menu
I know that I could replace the (last user input) with %example%, but then I'd have to make custom error messages for every category, and there are about 50 of them. It'd be easier with a last input command.
By the way, I've taught myself everything that I know about batch, so my example probably has major issues right now, but it works somehow.
You could centralize all user input into a function (user_input)
:menu1
echo Type 1 to proceed.
call :userInput example
if "%example%" == "1" GOTO :proceed
GOTO :error
:menu2
echo Type 42 to proceed.
call :userInput answer
if "%answer%" == "42" GOTO :proceed
GOTO :error
:userInput
set /p LAST_INPUT=
set "%1=%LAST_INPUT%"
exit /b
:proceed
pause
:error
cls
echo You wrote "%LAST_INPUT%", that's not correct.
timeout 30
GOTO :menu
I don't know how to do it without temp file. TO get the things written int the console you need the doskey /history (this will skip the running of the script itself):
#echo off
setlocal enableDelayedExpansion
set "last="
set "but_last="
doskey /history > log.txt
for /f "tokens=* delims=" %%# in (log.txt) do (
set "but_last=!last!"
set "last=%%#"
)
echo "%but_last%"
del /s /q log.txt >nul 2>nul
Everything works fine in this program except when user enters the correct number generated by the program. For example, it replies "too low" if you enter 13, and "too high" if you enter 14. How do you fix this so the 3rd if statement functions properly?
#echo off
title Game
set num1=%random%%101
:start
set /p num2=Enter a number:
if %num2% gtr %num1% (echo too high
pause
cls
goto :start
)
if %num2% lss %num1% (echo too low
pause
cls
goto :start
)
if %num2% equ %num1% (echo Bingo!)
pause
cls
exit
This batch file works fine:
#echo off
title Game
setlocal EnableDelayedExpansion
set /A num1=%random% %% 101
:Retry
cls
set "num2="
set /P "num2=Enter a number (%num1%): "
if !num2! GTR %num1% (
echo too high
pause
goto Retry
)
if !num2! LSS %num1% (
echo too low
pause
goto Retry
)
if !num2! EQU %num1% (
endlocal
echo Bingo!
pause
exit /B
)
goto Retry
The line
set num1=%random%%101
could be also used in the batch file above, but creates
a number with a random part and
appended the first argument passed to the batch file if called with a parameter at all and
appended the digits 0 and 1.
I suppose like SomethingDark that a random number between 0 and 100 is wanted here.
start is a standard Windows command and therefore it is advisable not to use start as label although possible.
Delayed expansion is enabled and used for num2 to avoid an exit of the batch file caused by a syntax error when the batch user enters nothing or by mistake not a number.
Also the environment variable num2 is always unset before prompting the user as otherwise the batch user could just hit RETURN or ENTER to continue with whatever variable num2 currently has as value.
Note: The random number is output in prompt text to see what to enter for testing.
I'm working on a program to backup Fallout 4 Saves, because using console commands can bork a save file this time around. Unfortuanly, while all of the parts are working independently, the little menu I have made isn't working!
For some reason the if %1m% == _ goto _ commands are doing nothing, and the program skips back to the label 1, a feature I put there in case of invalid input.
What's wrong here?
#echo off
title Fallout 4 Save Backup Utility
color 0a
:1
cls
setLocal EnableDelayedExpansion
pushd "C:\FalloutBackup\data\"
set /a count=0
for /d /r %%i in (*.*) do set /a count+=1
popd
echo %count% Backup(s^) currently exist.
echo.
echo.
set /a value=0
set /a sum=0
FOR /R %1 %%I IN (*) DO (
set /a value=%%~zI/1000000
set /a sum=!sum!+!value!
)
#echo Backups files using about: !sum! Mb
endlocal
echo.
echo.
echo Delete all but last backup? y/n?
set /p 1m=
if %1m% == y goto 3
if %1m% == Y goto 3
if %1m% == n goto 2
if %1m% == N goto 2
cls
goto 1
As Squash man Said:
Change your variable 1m to m1. CMD interpreter is not smart enough to know that you are trying to reference an environmental variable and not a argument passed to the batch file. –
I also recommend using quotes for the "%m1%"=="y". Also, the /I parameter after IF makes the answer not cap-sensitive, a big plus.
Another option would be to use CHOICE and ERRORLEVEL for option selection. The relevant code changes would be:
echo.
choice /c yn /m "Delete all but last backup? "
IF ERRORLEVEL 255 goto end
IF ERRORLEVEL 2 goto donotdelete
IF ERRORLEVEL 1 goto dodelete
IF ERRORLEVEL 0 goto end
goto begin
(255 and 0 are there for escapes.)
(You will need to obtain the choice COM file, which is readily available.)
I've been trying to create a file that times the user's input.
I could get this file
start /min b1.bat
(:b1.bat)
#echo off
cls
set num=0
:time
set /a num=%num%+1
echo %num% >waiter.txt
set time=0
:wait
cls
set /a time=%time%+1
if "%time%" equ "1000" goto time
goto wait
Here, b1.bat counts the secconds passed since it has started.
(:b2.bat)
cls
:begin
set /p time= <waiter.txt
echo %time%
if "%time%" equ "5" echo Five secconds.
set /p cho=Input:
goto begin
I would like to know how to make b2.bat do something if no input was given for an amount of time. No input at all, not even a press of enter.
So, if I didn't press any key at all and a minute has passed, it prints A minute has passed without input.
I hope this makes sense to you.
Before I answer your question, I need to point out that looping 1000 times does not necessarily mean you've paused for 1 second. There are better ways to sleep -- timeout /t seconds in Vista or newer, or ping -n seconds+1 localhost or choice /t seconds in XP. You should also avoid stomping on existing environment variables, such as %time%.
To answer your question, there's no graceful way to set a timeout for set /p. You can, however, launch a background thread within the same window using start /b to echo things on an interval. You can set up inter-thread communication between the active thread and the background using waitfor (Vista or newer only) to reset the timer, and set the helper thread to self-destruct when waiter.txt disappears.
And as long as you're launching one helper thread within the same window, might as well put your waiter.txt thread there as well. Why spawn a minimized window when you can reuse the one you already have? And since we're merging process threads within the same window, might as well go one step further and merge all your scripts into one script that re-launches itself with different arguments for different threads.
#echo off
setlocal
if "%~1"=="" (
>waiter.txt echo 0
start /b "" "%~f0" helper1
start /b "" "%~f0" helper2
) else goto %~1
:begin
cls
set /p "seconds="<waiter.txt
echo Alive for %seconds%s.
:read-host
set "cho="
set /p "cho="
if not defined cho goto read-host
rem // delayed expansion prevents evaluation of special characters in user input
setlocal enabledelayedexpansion
for %%c in (quit exit die EOL) do if /i "!cho!"=="%%c" (
del waiter.txt
echo OK, bye!
)
rem // send signal to helper2 acknowledging entry
waitfor /s %computername% /si UserEntry >NUL 2>NUL
if not exist waiter.txt exit /b
rem // ========================================
rem // Do something meaningful with !cho! here.
rem // ========================================
endlocal
goto begin
rem // formerly b1.bat
:helper1
set "num=0"
:helper1_loop
timeout /t 1 /nobreak >NUL 2>NUL
set /a num+=1
if not exist waiter.txt exit
>waiter.txt echo %num%
goto helper1_loop
:helper2
if not exist waiter.txt exit
waitfor /t 60 UserEntry >NUL 2>NUL || (
echo A minute has passed without input.
)
goto helper2
For extra credit, let's get rid of waiter.txt as well. I'm not sure whether this paranoia is founded or not, but I believe that hard drive sectors can endure a finite number of writes. Rewriting waiter.txt once a second for potentially hours bothers me. So let's create a hybrid Jscript chunk to calculate the seconds elapsed between the start of the script and now.
To control execution of the background helper thread, we'll still use a temporary file, but we'll use a lock file that will only be written once, then deleted at the end.
#if (#CodeSection == #Batch) #then
#echo off
setlocal
if "%~1"=="" (
rem // relaunch self as helper if lock file is writeable
2>NUL (>"%~dpn0.lock" type NUL) && start /b "" "%~f0" helper
rem // create a lock file for the duration of the main runtime
rem // Credit: Dave Benham -- https://stackoverflow.com/a/27756667/1683264
8>&2 2>NUL (2>&8 9>"%~dpn0.lock" call :init) || (
echo Only one instance is allowed.
timeout /t 3 /nobreak >NUL
exit /b
)
) else goto %~1
rem // cleanup and end main runtime
del "%~dpn0.lock" >NUL 2>NUL
waitfor /s %computername% /si UserEntry >NUL 2>NUL
exit /b
:init
rem // set %start% to current epoch
call :jscript start
:begin
cls
call :jscript seconds
echo Alive for %seconds%s.
:read-host
set "cho="
set /p "cho="
if not defined cho goto read-host
rem // delayed expansion prevents evaluation of special characters in user input
setlocal enabledelayedexpansion
for %%c in (quit exit die EOL) do if /i "!cho!"=="%%c" (
echo OK, bye!
exit /b
)
rem // send signal to helper2 acknowledging entry
waitfor /s %computername% /si UserEntry >NUL 2>NUL
rem // ========================================
rem // Do something meaningful with !cho! here.
rem // ========================================
endlocal
goto begin
:helper
del "%~dpn0.lock" >NUL 2>NUL
if not exist "%~dpn0.lock" exit
waitfor /t 60 UserEntry >NUL 2>NUL || (
echo A minute has passed without input.
)
goto helper
:jscript
for /f %%I in ('cscript /nologo /e:JScript "%~f0" "%start%"') do set "%~1=%%I"
goto :EOF
#end // end batch / begin JScript hybrid code
WSH.Echo(
WSH.Arguments(0)
? Math.round((new Date() - WSH.Arguments(0)) / 1000)
: new Date().getTime()
);
Why does this batch file never break out of the loop?
For /L %%f In (1,1,1000000) Do #If Not Exist %%f Goto :EOF
Shouldn't the Goto :EOF break out of the loop?
Edit:
I guess I should've asked more explicitly... how can I break out of the loop?
Based on Tim's second edit and this page you could do this:
#echo off
if "%1"=="loop" (
for /l %%f in (1,1,1000000) do (
echo %%f
if exist %%f exit
)
goto :eof
)
cmd /v:on /q /d /c "%0 loop"
echo done
This page suggests a way to use a goto inside a loop, it seems it does work, but it takes some time in a large loop. So internally it finishes the loop before the goto is executed.
You could simply use echo on and you will see that goto :eof or even exit /b doesn't work as expected.
The code inside of the loop isn't executed anymore, but the loop is expanded for all numbers to the end.
That's why it's so slow.
The only way to exit a FOR /L loop seems to be the variant of exit like the exsample of Wimmel, but this isn't very fast nor useful to access any results from the loop.
This shows 10 expansions, but none of them will be executed
echo on
for /l %%n in (1,1,10) do (
goto :eof
echo %%n
)
My answer
Use nested for loops to provide break points to the for /l loop.
for %%a in (0 1 2 3 4 5 6 7 8 9) do (
for %%b in (0 1 2 3 4 5 6 7 8 9) do (
for /l %%c in (1,1,10) do (
if not exist %%a%%b%%c goto :continue
)
)
)
:continue
Explanation
The code must be tweaked significantly to properly use the nested loops. For example, what is written will have leading zeros.
"Regular" for loops can be immediately broken out of with a simple goto command, where for /l loops cannot. This code's innermost for /l loop cannot be immediately broken, but an overall break point is present after every 10 iterations (as written). The innermost loop doesn't have to be 10 iterations -- you'll just have to account for the math properly if you choose to do 100 or 1000 or 2873 for that matter (if math even matters to the loop).
History
I found this question while trying to figure out why a certain script was running slowly. It turns out I used multiple loops with a traditional loop structure:
set cnt=1
:loop
if "%somecriteria%"=="finished" goto :continue
rem do some things here
set /a cnt += 1
goto :loop
:continue
echo the loop ran %cnt% times
This script file had become somewhat long and it was being run from a network drive. This type of loop file was called maybe 20 times and each time it would loop 50-100 times. The script file was taking too long to run. I had the bright idea of attempting to convert it to a for /l loop. The number of needed iterations is unknown, but less than 10000. My first attempt was this:
setlocal enabledelayedexpansion
set cnt=1
for /l %%a in (1,1,10000) do (
if "!somecriteria!"=="finished" goto :continue
rem do some things here
set /a cnt += 1
)
:continue
echo the loop ran %cnt% times
With echo on, I quickly found out that the for /l loop still did ... something ... without actually doing anything. It ran much faster, but still slower than I thought it could/should. Therefore I found this question and ended up with the nested loop idea presented above.
Side note
It turns out that the for /l loop can be sped up quite a bit by simply making sure it doesn't have any output. I was able to do this for a noticeable speed increase:
setlocal enabledelayedexpansion
set cnt=1
#for /l %%a in (1,1,10000) do #(
if "!somecriteria!"=="finished" goto :continue
rem do some things here
set /a cnt += 1
) > nul
:continue
echo the loop ran %cnt% times
you do not need a seperate batch file to exit a loop using exit /b if you are using call instead of goto like
call :loop
echo loop finished
goto :eof
:loop
FOR /L %%I IN (1,1,10) DO (
echo %%I
IF %%I==5 exit /b
)
in this case, the "exit /b" will exit the 'call' and continue from the line after 'call'
So the output is this:
1
2
3
4
5
loop finished
So I realize this is kind of old, but after much Googling, I couldn't find an answer I was happy with, so I came up with my own solution for breaking a FOR loop that immediately stops iteration, and thought I'd share it.
It requires the loop to be in a separate file, and exploits a bug in CMD error handling to immediately crash the batch processing of the loop file when redirecting the STDOUT of DIR to STDIN.
MainFile.cmd
ECHO Simple test demonstrating loop breaking.
ECHO.
CMD /C %~dp0\LOOP.cmd
ECHO.
ECHO After LOOP
PAUSE
LOOP.cmd
FOR /L %%A IN (1,1,10) DO (
ECHO %%A
IF %%A EQU 3 DIR >&0 2>NUL )
)
When run, this produces the following output. You'll notice that both iteration and execution of the loop stops when %A = 3.
:>MainFile.cmd
:>ECHO Simple test demonstrating loop breaking.
Simple test demonstrating loop breaking.
:>ECHO.
:>CMD /C Z:\LOOP.cmd
:>FOR /L %A IN (1 1 10) DO (
ECHO %A
IF %A EQU 3 DIR 1>&0 2>NUL
)
:>(
ECHO 1
IF 1 EQU 3 DIR 1>&0 2>NUL
)
1
:>(
ECHO 2
IF 2 EQU 3 DIR 1>&0 2>NUL
)
2
:>(
ECHO 3
IF 3 EQU 3 DIR 1>&0 2>NUL
)
3
:>ECHO.
:>ECHO After LOOP
After LOOP
:>PAUSE
Press any key to continue . . .
If you need to preserve a single variable from the loop, have the loop ECHO the result of the variable, and use a FOR /F loop in the MainFile.cmd to parse the output of the LOOP.cmd file.
Example (using the same LOOP.cmd file as above):
MainFile.cmd
#ECHO OFF
ECHO.
ECHO Simple test demonstrating loop breaking.
ECHO.
FOR /F "delims=" %%L IN ('CMD /C %~dp0\LOOP.cmd') DO SET VARIABLE=%%L
ECHO After LOOP
ECHO.
ECHO %VARIABLE%
ECHO.
PAUSE
Output:
:>MainFile.cmd
Simple test demonstrating loop breaking.
After LOOP
3
Press any key to continue . . .
If you need to preserve multiple variables, you'll need to redirect them to temporary files as shown below.
MainFile.cmd
#ECHO OFF
ECHO.
ECHO Simple test demonstrating loop breaking.
ECHO.
CMD /C %~dp0\LOOP.cmd
ECHO After LOOP
ECHO.
SET /P VARIABLE1=<%TEMP%\1
SET /P VARIABLE2=<%TEMP%\2
ECHO %VARIABLE1%
ECHO %VARIABLE2%
ECHO.
PAUSE
LOOP.cmd
#ECHO OFF
FOR /L %%A IN (1,1,10) DO (
IF %%A EQU 1 ECHO ONE >%TEMP%\1
IF %%A EQU 2 ECHO TWO >%TEMP%\2
IF %%A EQU 3 DIR >&0 2>NUL
)
Output:
:>MainFile.cmd
Simple test demonstrating loop breaking.
After LOOP
ONE
TWO
Press any key to continue . . .
I hope others find this useful for breaking loops that would otherwise take too long to exit due to continued iteration.
As jeb noted, the rest of the loop is skipped but evaluated, which makes the FOR solution too slow for this purpose. An alternative:
set F=1
:nextpart
if not exist "%F%" goto :EOF
echo %F%
set /a F=%F%+1
goto nextpart
You might need to use delayed expansion and call subroutines when using this in loops.
It is impossible to get out of a FOR /L before it completes all iterations.
I have debugged the execution of a FOR /L by the cmd.exe process.
Microsoft could document it better and save us all this effort.
Facts:
The loop is a simple while (TRUE) and the break only happens when the iteration limit is reached.
When an EXIT /b or a GOTO is encountered, no more commands are executed until the end of the iterations.
When an EXIT is encountered, the cmd.exe process is terminated.
Tests:
12 seconds
FOR /L %%G in (1,1,5000000) do (ECHO Only once & GOTO :EOF)
7 seconds
FOR /L %%G in (1,1,5000000) do (ECHO Only once & EXIT /b)
0 seconds, but this terminates the cmd.exe process
FOR /L %%G in (1,1,5000000) do (ECHO Only once & EXIT)
Assuming that the OP is invoking a batch file with cmd.exe, to properly break out of a for loop just goto a label;
Change this:
For /L %%f In (1,1,1000000) Do If Not Exist %%f Goto :EOF
To this:
For /L %%f In (1,1,1000000) Do If Not Exist %%f Goto:fileError
.. do something
.. then exit or do somethign else
:fileError
GOTO:EOF
Better still, add some error reporting:
set filename=
For /L %%f In (1,1,1000000) Do(
set filename=%%f
If Not Exist %%f set tempGoto:fileError
)
.. do something
.. then exit or do somethign else
:fileError
echo file does not exist '%filename%'
GOTO:EOF
I find this to be a helpful site about lesser known cmd.exe/DOS batch file functions and tricks: https://www.dostips.com/
Did a little research on this, it appears that you are looping from 1 to 2147483647, in increments of 1.
(1, 1, 2147483647): The firs number is the starting number, the next number is the step, and the last number is the end number.
Edited To Add
It appears that the loop runs to completion regardless of any test conditions. I tested
FOR /L %%F IN (1, 1, 5) DO SET %%F=6
And it ran very quickly.
Second Edit
Since this is the only line in the batch file, you might try the EXIT command:
FOR /L %%F IN (1, 1, 2147483647) DO #IF NOT EXIST %%F EXIT
However, this will also close the windows cmd prompt window.