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()
);