Arithmetic operations with HH:MM:SS times in batch file - batch-file

In one of my batch scripts I need to calculate the duration of an interval in a video file. First the user is asked to input the start and end times:
set /p StartPosition=Start position (HH:MM:SS):
set /p EndPosition=End position (HH:MM:SS):
Then, I would like the batch script to calculate the duration in between.
How can I subtract %StartPosition% from %EndPosition% like this, for example:
00:10:40 - 00:10:30 = 00:00:10
The reason why I can't figure out how to do this is because these numbers are separated by colons.
Edit: This question is different to this question because I do not need the scrip to treat the numbers as time values.

#echo off
setlocal
set /p "StartPosition=Start position (HH:MM:SS): "
set /p "EndPosition=End position (HH:MM:SS): "
set /A "ss=(((1%EndPosition::=-100)*60+1%-100)-(((1%StartPosition::=-100)*60+1%-100)"
set /A "hh=ss/3600+100,ss%%=3600,mm=ss/60+100,ss=ss%%60+100"
echo Duration=%hh:~1%:%mm:~1%:%ss:~1%
EDIT: Some explanations added
This program use the usual method to convert a time in HH:MM:SS format into a number of seconds via the standard formula: seconds = (HH*60+MM)*60+SS. However, the set /A command consider the numbers that start with 0 as written in octal base, and hence 08 and 09 would be invalid octal numbers. To avoid this problem, a digit 1 is placed before expand the number and a 100 is subtracted after, so if HH=08 then 1%HH%-100 correctly gives 8; that is:
set /A seconds = ((1%HH%-100)*60+1%MM%-100)*60+1%SS%-100
There are several methods to split a time given in HH:MM:SS format into its three parts. For example, if we take set EndPosition=HH:MM:SS as base, then we may use a for /F command this way:
for /F "tokens=1-3 delims=:" %%a in ("%EndPosition%") do (
set /A "seconds=((1%%a-100)*60+1%%b-100)*60+1%%c-100"
)
In this program a different method is used. If we match the original EndPosition=HH:MM:SS string with the desired formula, we may construct this mapping scheme:
HH : MM : SS
((1 HH -100)*60+1 MM -100)*60+1 SS -100
In other words: if we replace the colons of the original string by -100)*60+1 and insert ((1 at beginning and -100 at end, we obtain the desired formula; that is:
set /A "seconds=((1%EndPosition::=-100)*60+1%-100"
This is a very efficient method that even allows to replace both EndPosition and StartPosition strings in the same formula (enclosing both parts in parentheses) and directly subtract them:
set /A "ss=(((1%EndPosition::=-100)*60+1%-100)-(((1%StartPosition::=-100)*60+1%-100)"
You may cancel the #echo off command and run the program to review the exact formula that is evaluated after the values of the variables are replaced. For example, when StartPosition=00:10:30 and EndPosition=00:10:40, this is the expression that is evaluated:
set /A "ss=(((100-100)*60+110-100)*60+140-100)-(((100-100)*60+110-100)*60+130-100)"
Just to complete this description, this is the "standard" way to evaluate the same formula using a for /F command:
for /F "tokens=1-6 delims=:" %%a in ("%EndPosition%:%StartPosition%") do (
set /A "ss=(((1%%a-100)*60+1%%b-100)*60+1%%c-100)-(((1%%d-100)*60+1%%e-100)*60+1%%f-100)"
)
The opposite conversion from number of seconds to HH:MM:SS parts is straightforward:
HH=SS/3600, rest=SS%3600, MM=rest/60, SS=rest%60
However, each part in the result must be displayed with two digits, but this formatting may be achieved in a very simple way. Instead of insert three if commands that check if each part is less than 10 and insert a padding zero in such a case, the number 100 is just added to the parts (converting an 8 into 108, for example), and when each part is displayed the first digit is omitted (so just 08 is shown). This is a very efficient method to format numbers that may be performed in the same set /A command used to obtain the parts. For example:
set /A "hh=ss/3600+100,ss%%=3600,mm=ss/60+100,ss=ss%%60+100"
echo Duration=%hh:~1%:%mm:~1%:%ss:~1%
In this way, the conversion of two times into two number of seconds, their subtraction and the opposite conversion and formatting to HH:MM:SS is performed in two SET /A commands, that even may be written in a single, long line.
Output examples:
Start position (HH:MM:SS): 00:10:30
End position (HH:MM:SS): 00:10:40
Duration=00:00:10
Start position (HH:MM:SS): 00:10:45
End position (HH:MM:SS): 00:11:05
Duration=00:00:20

This is possible to do in pure batch by parsing each field as an independent string, then doing arithmetic on them. Many practical solutions call into some other program to do the date math.
The following code calls into PowerShell to use the .NET DateTime class to do the parsing for you.
C:\> set "StartPosition=00:10:30"
C:\> set "EndPosition=00:10:40"
C:\> PowerShell.exe -c "$span=([datetime]'%EndPosition%' - [datetime]'%StartPosition%'); '{0:00}:{1:00}:{2:00}' -f $span.Hours, $span.Minutes, $span.Seconds"
00:00:10
This executes two lines of PowerShell code; one to convert both times into DateTime objects and subtract them, and the other to output the result in the format you specified.

Here's a working prototype:
#echo off
set T1=00:10:45
set T2=00:11:05
set HOUR1=%T1:~,2%
set MIN1=%T1:~3,-3%
set SEC1=%T1:~-2%
set HOUR2=%T2:~,2%
set MIN2=%T2:~3,-3%
set SEC2=%T2:~-2%
set /A TOTAL_SEC1=%HOUR1%*3600+%MIN1%*60+SEC1
set /A TOTAL_SEC2=%HOUR2%*3600+%MIN2%*60+SEC2
set /A DIFF=%TOTAL_SEC2%-%TOTAL_SEC1%
echo %DIFF%
Output:
20
Its not complete, but its a reasonable start.

I think, #Aacini has cleared Everything here. He got you, Before I Do. But, I want to Improved on him as - by using For Loop to make code Easier.
Note: Everything after 'REM' is a Comment for the sake of understanding easily...
All You need to DO is Copy It into Your Batch File. And, Use it as follows (in your main code):
Syntax: Call :Time [Your Time 1] [Operation] [Your Time 2]
And, You can Now apply - any operation - including 'Addition, Substraction, Division, Multiplication' ;)
The Time Function
--------------Copy the Below Code----------
:Time [Start_Time] [Operation] [End_Time]
SetLocal EnableDelayedExpansion
REM Creating a portable Function for your Job. :)
REM Reading Start-time...
For /F "Tokens=1,2,3 Delims=:" %%A in ("%~1") Do (
Set _Start_Hour=%%A
Set _Start_Min=%%B
Set _Start_Sec=%%C
)
REM Reading End-time...
For /F "Tokens=1,2,3 Delims=:" %%A in ("%~3") Do (
Set _End_Hour=%%A
Set _End_Min=%%B
Set _End_Sec=%%C
)
REM Removing leading Zero's - if any... 'CMD assumes it as octal - otherwise'
For %%A In (Hour Min Sec) Do (
For %%B In (Start End) Do (
IF /I "!_%%B_%%A:~0,1!" == "0" (Set _%%B_%%A=!_%%B_%%A:~1!)
)
)
REM Applying Operation on the given times.
For %%A In (Hour Min Sec) Do (Set /A _Final_%%A=!_Start_%%A! %~2 !_End_%%A!)
REM Handling a little Exceptional errors! - due to the nature of time (60 sec for a min.)
SET _Extra_Hour=0
SET _Extra_Min=0
REM Two Cases can arise in each part of time...
:Sec_loop
IF %_Final_Sec% GTR 59 (Set /A _Extra_Min+=1 & Set /A _Final_Sec-=60 & Goto :Sec_loop)
IF %_Final_Sec% LSS 0 (Set /A _Extra_Min-=1 & Set /A _Final_Sec+=60 & Goto :Sec_loop)
Set /A _Final_Min+=%_Extra_Min%
:Min_loop
IF %_Final_Min% GTR 59 (Set /A _Extra_Hour+=1 & Set /A _Final_Min-=60 & Goto :Min_loop)
IF %_Final_Min% LSS 0 (Set /A _Extra_Hour-=1 & Set /A _Final_Min+=60 & Goto :Min_loop)
Set /A _Final_Hour+=%_Extra_Hour%
REM Saving Everything into a Single Variable - string.
Set _Final_Time=%_Final_Hour%:%_Final_Min%:%_Final_Sec%
REM Displaying it on the console. ;)
Echo.%_Final_Time%
Goto :EOF
--------------End OF Code----------------------
You can Also visit, my Website - based on Batch Programming. (www.thebateam.org) You'll find alot of stuff there - to help you out. :)
Here's the Final Output - When I saved the Code in Answer.bat File

To offer a concise alternative to Ryan Bemrose's helpful, PowerShell-based answer:
:: Sample variable values.
set "StartPosition=00:10:30"
set "EndPosition=00:10:40"
:: Use PowerShell to perform the calculation,
:: using the .NET System.Timespan ([timespan]) type.
powershell -c \"$([timespan] '%EndPosition%' - '%StartPosition%')\"
Yes, you pay a performance penalty for invoking the PowerShell CLI, but I invite you to compare this solution to Aacini's clever, but highly obscure batch-language-only solution in terms of readability and conceptual complexity.
Generally speaking:
cmd.exe is a shell and, historically, shells have provided very limited language capabilities themselves, as their focus was on calling built-in or external commands.
cmd.exe's language, as used in batch files (.cmd, .bat) is very limited, and saddled with many counterintuitive behaviors that can't be fixed so as not to break backward compatibility.
Over the decades, users have learned to stretch the language to its limits, coming up with many clever techniques to squeeze more functionality out of it. While helpful if you're stuck on pre-PowerShell systems (virtually extinct at this point) or you must use batch files and performance is paramount (rarely the case), the obscurity of these techniques makes them both hard to understand and to remember.
cmd.exe's successor, PowerShell, with its .ps1 scripts, offers a far superior language that offers virtually unlimited access to .NET functionality and COM.
PowerShell too has its fair share of counterintuitive behaviors that can't be fixed, but, by and large, it is a far more capable and predictable language than the batch language; some of its undeniable, but unavoidable complexity comes from having to talk to multiple worlds (.NET, COM, WMI) while still also acting as a shell (with respect to calling external programs and the shell-like syntax of its built-in command as well as user-defined ones).
Here, the batch file uses a call an external program, powershell.exe, the PowerShell CLI, to delegate the task at hand to its superior language.
Calling the PowerShell CLI is expensive in terms of performance, but offers a way to perform tasks that batch files either cannot, or can only do with much more effort and/or highly obscure techniques.
Of course, needing to "speak" both the batch language and PowerShell to implement a given task adds complexity of its own, so the logical progression is to implement the entire task in PowerShell (in a .ps1 script).
Unfortunately, PowerShell puts up some road blocks here, in the name of security:
In workstation editions of Windows, execution of scripts is disabled by default, and requires a one-time call such as the following to enable it (see this answer for background information):
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
.ps1 scripts - unlike batch files - cannot be executed directly from outside PowerShell, notably not by double-clicking in File Explorer or from a cmd.exe session.
From inside a PowerShell session that is not a concern, but if you do need this capability in a given scenario, a simple workaround is to create a companion batch file with the same base file name as the .ps1 script (e.g., foo.cmd in the same directory as the target PowerShell script, foo.ps1, with the following, generic content:
#powershell -noprofile -file "%~dpn0.ps1"

Related

How to squeeze lots of variables?

I am setting up some code that will help me run a mathematical experiment. Is there a way to use loops in batch to create multiple variables automatically.
I have tried to join a variable to a set command (before the equal sign), but then the command does not run.
#echo off
set variablerep=0
pause
Set p1=0
Set p2=0
Set p3=0
...
...
Set p34=0
Set p35=0
Set p36=0
Pause
I hope to find ways by which I don't need to copy each set command and make minor changes and code efficiently.
Use for /l loop:
#echo off
setlocal
set p_
for /l %%l in ( 1, 1, 36 ) do set p_%%l=0
set p_
Please remember that math in (pure) batch is restricted to 32bit signed integers (+/- ~2GB).
and NO floating point math.
All variables are stored as strings and only set /A and the if commands try to convert to integers.
the syntax of set /a allows several calculatons on one line delimited with a comma,
set /A p1=0,p2=0,p3=0
there also is a special variant allowing to set several variables to the same value:
set /A p1=p2=p3=0
When using an index to address variables the pseudo array[%%I] format is common, whereas every valid naming scheme can be used - as jsxts answer shows.

Holding a formula with variables inside another variable

I decided I wanted to beautify my batch code by assigning a formula (which contains its own sub-variables ('hrs' and 'min' and 'sec') as well as numerical operations and math operators) to a variable ('myformula') at the top of my code, then call and expand the variable (%myformula%) later on in multiple "set /a ..." commands.
I have tried %%hrs%% and !hrs! and ^carets, as well as !myformula!, but I always get a "Missing operand." error.
This suggests to me that the maths operators are not being expanded correctly, or that to numerical values are being converted to characters.
I should add that everything works perfectly if I admit defeat, and replace %myformula% in the code with the actual formula.
Screen output gives correct display of the formula -
My Formula : %hrs%*60*60*100+%min%*60*100+%sec%*100+%cen%
Missing operand.
My Time :
( OR - My Time : 1 depending on %, %%, !, ^, etc, etc ... )
Can anyone suggest how to make the formula expansion %myformula% work correctly?
(I would consider NON native solutions such as - use javascript, use cscript, use powershell - as well as defeatest answers like - "set /a mytime=the+original+formula" - as not being an answer to my real question).
#echooff
SETLOCAL ENABLEDELAYEDEXPANSION
:start
set myformula=%hrs%*60*60*100+%min%*60*100+%sec%*100+%cen%
:timer
rem - Timer code credits go to - rberteig (2014)
rem Convert t0 into a scaler in 100th of sec with no
seperator chars.
set /a t0=%time: =0%
set /a hrs=1%t0:~0,2%-100
set /a min=1%t0:~3,2%-100
set /a sec=1%t0:~6,2%-100
set /a cen=1%t0:~9,2%-100
echo My Formula : %myformula%
set /a mytime=%myformula%
rem - set /a mytime=!myformula! - does not work either.
echo My Time : %mytime%
:finish
pause
ENDLOCAL
exit /B 0
:EOF
Ok, with lots more trial and error, I found the solution myself. When saving formula (which is intended for arithmetic evaluation) to a variable, no % character wrapping is required.
This is apparently because the SET coomand, when used with the /A switch, automatically assumes any alphabet characters to be variables, and expands them without requiring any % wrapping.
This is alluded to in the SET /? help topic, and is mentioned a bit more in various web pages and forums. The only example I could find in retrospect was -
https://ss64.com/nt/set.html
Thus the answer to my question is to use this code line when seting the "myformula" variable:
:start
set myformula=hrs*60*60*100+min*60*100+sec*100+cen
which expands perfectly as intended at the later code line:
set /a mytime=%myformula%
as its preceding and proceeding echo lines prove from their screen output.
Who would have thought???

Batch file: how to prompt the user for exactly 2 numeric characters?

How could I limit the user input to only two numeric characters in the command below?
set "nfilial="
set /p "nfilial=Number Filial (2 Digits):"
for /f "delims=1234567890" %%a in ("%nfilial%") do goto :nfilial
There are many different ways I can think of.
You could use substring extraction to ensure that the 2nd character is not null, and the third character is.
:nfilial
set /p "nfilial=Number Filial (2 Digits):"
for /f "delims=1234567890" %%a in ("%nfilial%") do goto nfilial
if "%nfilial:~1"=="" goto nfilial
if not "%nfilial:~2"=="" goto nfilial
Or if you prefer, you could zero pad the left side, then extract the last two characters regardless of the number entered.
:nfilial
set /p "nfilial=Number Filial (2 Digits):"
for /f "delims=1234567890" %%a in ("%nfilial%") do goto nfilial
set "nfilial=0%nfilial%"
set "nfilial=%nfilial:~-2%"
You could add a numeric check to that if you wish, to ensure that if %nfilial% lss 0 goto nfilial and if %nfilial% gtr 99 goto nfilial, but that's probably overkill.
Or you could force user entry of two numerals with the choice command. Or you could use findstr to match a regexp of "^[0-9][0-9]$". Or, because set /a only calculates integers, you could try to divide by 100 and make sure the result is 0. Or there are probably other ways. Really, you're limited only by your imagination. You could turn it into a Rube Goldberg assembly of checks. You could have PowerShell evaluate it, then use conditional execution to goto nfilial based on the exit code. You could compile a c# program. You could have the script email you the value entered and wait for you to respond with an OK. It just depends on how bored you are, really.
You should realize that one thing is to read any input from the user, check that the input is correct and repeat it if it does not, and an entirely different thing is to limit the user input to the required input format only. The first method may be achieved in several ways, but if you want to use the second method, you may do it via ReadFormattedLine subroutine (suggested in the link given above), that is written in pure Batch. Using it, you may solve your problem this way:
call :ReadFormattedLine nfilial="##" /M "Number Filial (2 Digits):"
You may download ReadFormattedLine subroutine from this post.

Batch script to grab lines with findstr without filepath

I've got a log file that monitors a large system including requests and acknowledgements. My objective is to be able to:
1. Loop through the script and get the lines where requests & their acknowledgements happen
2. Pull the entire lines of importance as strings and store them as variables for string modifying to output somewhere else.
Here's what I have so far:
#ECHO off
setlocal
setlocal enabledelayedexpansion
setlocal enableextensions
:: Lets get today's date, formatted the way the ABCD File is named
for /f "tokens=1-5 delims=/ " %%d in ("%date%") do set targetDate=%%f-%%d-%%e
:: Now we set the targetFile name
SET ABCDLogsFile=C:\Users\me\Documents\monitoring_file_for_jim\ABCDFIX*%targetDate%.log
::****Scrapped original approach*****
set "ackFoundCount=0"
set "reqFoundCount=0"
::Get lines with acks
for /f delims^=^ eol^= %%a in ('findstr /c:"\<ACK\>" "%ABCDLogsFile%"') do (
set /a "ackFoundCount+=1"
setlocal enabledelayedexpansion
for %%N in (!ackFoundCount!) do (
endlocal
set "ackFound%%N=%%a"
)
)
::Get lines with requests
for /f delims^=^ eol^= %%b in ('findstr /c:"ReqSingle" "%ABCDLogsFile%"') do (
set /a "reqFoundCount+=1"
setlocal enabledelayedexpansion
for %%N in (!reqFoundCount!) do (
endlocal
set "reqFound%%N=%%b"
)
)
setlocal enabledelayedexpansion
for /l %%N in (1,1,2 %reqFoundCount%) do echo REQ %%N FOUND= !reqFound%%N!
pause
for /l %%N in (1,1,2 %ackFoundCount%) do echo ACK %%N FOUND= !ackfound%%N!
endlocal
EDIT 2 dbenham
The roundabout way I was trying to accomplish this before was totally unnecessary.
Thanks to the questions and answer here:
'findstr' with multiple search results (BATCH)
I've got my script working similarly. However, I'm curious if its possible to get findstr output without the filepath at the beginning. I only need to substring out the timestamp in the log, which would always be the first 12 characters of each line (without the filepath). My output currently is prefixed with the path, and while I could get the path where the log would eventually be in production, it would be safer to try and do it another way. At the time that this script would eventually be run, there would only be 1 or 2 reqs and acks each, that is why I store all which are found. It's not necessary but I think it would be reassuring to see two if there are two. Here is what the output looks like for acks and reqs alike:
C:\Users\me\Documents\monitoring_file_for_jim\ABCDFIX 2015-04-01.log:2015-03-26 07:00:11,028 INFO etc...
I'm thinking that if I could strip the filepath off the start, then all I'd need to do to get just the timestamps of the events would be
for /l %%N in (1,1,1 %reqFoundCount%) do echo Req %%N occurred at: !reqFound%%N:~0,12! >> MorningAckChecks.txt
for /l %%N in (1,1,1 %ackFoundCount%) do echo ACK %%N occurred at: !ackfound%%N:~0,12! >> MorningAckChecks.txt
I suspect you could not get SKIP to work because you you were iterating the delimited list of line numbers with a FOR statement, which means the number is in a FOR variable. Problem is, you cannot include FOR variables or (delayed expansion) when specifying a SKIP value, or any other FOR option. The batch parser evaluates the FOR options before FOR variables are expanded, so it couldn't possibly work. Only normal expansion can be used when including a variable as part of FOR options.
But I don't understand why you think you need the line numbers at all. FINDSTR is already able to parse out the lines you want. Simply use FOR /F to iterate each matching line. For each line, define a variable containing the line content, and then use substring operations to parse out your desired values.
But I can offer an alternative that I think could make your life much easier. JREPL.BAT is a sophisticated regular expression text processor that could identify the lines and parse out and transform your desired values, all in one pass. JREPL.BAT is a hybrid JScript/batch script that runs natively on any Windows machine from XP onward.
If I knew what your input looked like, and what your desired output is, then I could probably knock up a simple solution using JREPL.BAT. Or you could read the extensive built in documentation and figure it out for yourself.
Documentation is accessed from the command line via jrepl /?. You might want to pipe the output through MORE so you get one screen of help at a time. But I never do because my command line console is configured with a large output buffer, so I can simply scroll up to see past output.
EDIT - In response to comment and updated question
Here are the relevant snippets of your code that are causing the problem.
SET ABCDLogsFile=C:\Users\me\Documents\monitoring_file_for_jim\ABCDFIX*%targetDate%.log
findstr /c:"\<ACK\>" "%ABCDLogsFile%"
findstr /c:"ReqSingle" "%ABCDLogsFile%
The issue is your ABCDLogsFile definition includes a wildcard, which causes FINDSTR to prefix each matching line with the full path to the file name where the match occurred.
I have a simple solution for you - Just change the definition of ABCDLogsFile as follows:
SET "ABCDLogsFile=C:\Users\me\Documents\monitoring_file_for_jim\ABCDFIX<%targetDate%.log"
Explanation
My solution relies on two undocumented features
1) Undocumented file mask wildcards.
< - Very similar to *
> - Very similar to ?
These symbols are normally used for redirection, so they must be either quoted or escaped if you want to use them as file mask wildcards.
We discuss the undocumented feature at DosTips - Dir undocumented wildcards. Sprinkled throughout the thread (and a link) are some example use cases.
I document my understanding of exactly how the non-standard wildcards work at http://www.dostips.com/forum/viewtopic.php?p=39420#p39420
2) FINDSTR works with the non-standard wildcards
FINDSTR will prefix each matching line with the file name (and possibly path) if any of the following conditions occur
The /M option is used
The /F option is used
Multiple input files are explicitly listed on the command line
Multiple input files are implied via a file mask with at least one * or ? wildcard on the command line
Your are getting the file path prefix because of the last trigger - the * in your file mask.
But you can use < instead to get the same result, except the non-standard wildcards do not trigger the file prefix in the output.
Problem solved :-)
I talk about this FINDSTR feature at http://www.dostips.com/forum/viewtopic.php?p=39464#p39464.
Some day I hope to update my What are the undocumented features and limitations of the Windows FINDSTR command? post with this tasty little tidbit.
This post has become a bit cluttered. It would be very helpful if you posted the lines of input that correspond to the output you are getting. If you can't do that then add this statement before your FOR. I am sure you will find that testReqSkip is blank.
echo.testReqSkip=%testReqSkip%

Batch script error: "/ was unexpected at this time."

I wrote a little script to display every line of a text file with a random amount of pause between each of them:
SET pauseTime=10
SET maxval=20
SET minval=5
FOR /f %%j in (search1.txt) DO (
SET pauseTime=%RANDOM% * (%maxval% - %minval% + 1) / 32768 + %minval%
ECHO.%pauseTime%
TIMEOUT %pauseTime%
ECHO.%%j
)
Running this in cmd.exe gives me:
C:\Users\Tim\Desktop>SET minval=5
/ was unexpected at this time.
However, if I simply do:
FOR /f %%j in (search1.txt) DO (
ECHO.%%j
)
I get all the lines printed with no errors
What's going on?
You need to add the /A switch to your SET statement in order to do math operations. (I usually also have to remove any embedded spaces to get it to work properly as well.)
SET /A pausetime=%RANDOM%*(%maxval%)-%minval%)/32768+%minval%
The error you're getting is also caused by the batch processor not properly handing the parentheses within the for expression (the nested pair in the numeric expression). You can fix that by breaking that part of the expression out to a separate variable, and then using that variable in place of the portion within the parentheses:
SET pauseTime=10
SET maxval=20
SET minval=5
SET /A maxmin=%maxval% - %minval% + 1
FOR /f %%j in (search1.txt) DO (
SET /A pauseTime=%RANDOM% * %maxmin% / 32768 + %minval%
ECHO. %pauseTime%
TIMEOUT %pauseTime%
ECHO. %j%
)
the issue here is because you use nested ( ) brackets which batch can't handle and causes your issue, you might need to split your pausetime calculation into 2 lines.
see this article
The CMD shell statement does not use any great intelligence when evaluating parenthesis, so for example the command below will fail:
IF EXIST MyFile.txt (ECHO Some(more)Potatoes)
As mentioned within the other answers, the problem is caused by the nested parenthesis.
Instead of avoiding them, you could also escape them by preceding ^ characters -- this works:
IF EXIST MyFile.txt (ECHO Some^(More^)Potatoes)
Without the caret ^ here, the command line interpreter takes the first ) to close the first (.
However, applying the above escape technique to the code of the original question avoids the error message, but does not result in a working script, even if the inner SET is replaced by SET /a (as mentioned by #KenWhite), because you need to establish delayed environment variable expansion; otherwise, the displayed pauseTime value is always the initial one, 10. The following fix works:
#ECHO OFF
SET pauseTime=10
SET maxval=20
SET minval=5
SETLOCAL EnableDelayedExpansion
FOR /f %%j in (search1.txt) DO (
SET /a pauseTime=!RANDOM!*^(maxval-minval+1^)/32768+minval
ECHO.!pauseTime!
TIMEOUT !pauseTime!
ECHO.%%j
)
ENDLOCAL
Herein, the SETLOCAL statement enables the delayed expansion before the FOR command executes. In the body of the FOR loop, %pauseTime% and %RANDOM% are replaced by !pauseTime! and !RANDOM!, respectively; those are the variables subject to delayed expansion (because of the enclosing !!), meaning that they are not (as usual) expanded immediately when the entire FOR command is parsed, but when it is executed. For the sake of legibility, I removed the %% from the other variables in the SET /a command since they are not required when the /a switch is present (note that there is also a % operator supported!!).
Note: After this script has run, pauseTime is reset to the initial value 10 as the SETLOCAL/ENDLOCAL block constitutes a new localised environment namespace; if you need the last value, replace line ENDLOCAL by ENDLOCAL & SET pauseTime=%pauseTime%; this utilises standard immediate expansion...
Nevertheless, since minval and maxval are constants in the script it is most intelligent to do the calculation maxval-minval+1 outside of FOR once only, like #KenWhite suggested.

Resources