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.
Related
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???
%x:~12,3% Returns 3 characters starting at the 12:th character in x variable.
What I have been trying to accomplish is using variables instead of 12 and 3.
Let's say y=12 and z=3.
Then, you can't use %x:~%y%,%z%%, because CMD will think %x:~% is a variable.
What you can do is set var=%%x:~%y%,%z%%%. This will expand the inside variables y and z, but not x, so that the value of var is %x:~12,3%.
The remaining task at hand now is to finally expand %x:~12,3%. I have been trying to append echo in the beginning so that var=echo %x:~12,3%.
If at the commandline or in a batch file you now use %var%, this should execute the echo command, and expand the succeeding expression, but it doesnt, instead echo %x:~12,3% results in simply %x:~12,3% being printed to the screen, unexpanded.
I was thinking that maybe if you set var to %x:~12,3%, then echo it
and pipe the output into another ECHO command or SET command that the expression would be expanded, but it seems that ECHOand SETdoesn't accept data being piped into it at all?
How can I make this work?
I copied the entire text below from this answer; I just changed the names of variables and particular examples to match the ones of this question:
%x:~12,3% returns 3 characters starting at the 12:th character in x
variable. What I have been trying to accomplish is using variables
instead of 12 and 3. Let's say y=12 and z=3.
If you want to use another variables for substring position and lenght, then you must know that the replacement of variables enclosed in percents by their values is parsed from left to right; this mean that: %x:~%y%,%z%% don't give the desired result because it mean: show the value of x:~ variable, followed by y, followed by the value of , variable, etc.
To solve this problem you must use Delayed Expansion, that is, insert setlocal EnableDelayedExpansion command at beginning, enclose substring variables in percents, and enclose the original variable in exclamation marks:
setlocal EnableDelayedExpansion
set x=0123456789ABCDEF
set y=12
set z=3
set var=!x:~%y%,%z%!
You may also use parameters of FOR commands as indexes: for /F "tokens=1,2" %%i in ("%y% %z%") do set var=!x:~%%i,%%j!.
To get the value of a substring when the index change inside FOR/IF enclose the variable in double percents and precede the command with call. For example, to show a substring at a random y position between 0 and 12 and lenght z:
if %some% == %test% (
set /A y=!random! %% 13
call echo %%x:~!y!,%z%%%
)
You may also use this method outside parentheses in order to avoid the Delayed Expansion:
call echo %%x:~%y%,%z%%%
Another way to achieve previous process is using an additional FOR command to change the delayed expansion of the index by an equivalent replaceable parameter, and then use the delayed expansion for the original variable. This method run faster than previous CALL:
if %some% == %test% (
set /A y=!random! %% 13
for %%y in (!y!) do echo !x:~%%y,%z%!
)
You need to enable delayed expansion.
#echo off
setlocal enabledelayedexpansion
set string=1234567890abcdef
set substring_start=12
set substring_length=3
set substring=!string:~%substring_start%, %substring_length%!
set command=echo !substring!
!command!
pause
#ECHO OFF
SETLOCAL
SET "x=abcdefghijklmmopqrstuvwxyz"
SET /a start=12
SET /a length=3
CALL SET "var=%%x:~%start%,%length%%%"
ECHO var=%var%
CALL echo %%x:~%start%,%length%%%
SETLOCAL ENABLEDELAYEDEXPANSION
SET /a start=6
SET /a length=4
SET "var=!x:~%start%,%length%!"
ECHO var=%var%
echo !x:~%start%,%length%!
GOTO :EOF
Two methods - the first in standard mode and the second using delayedexpansion. There are hundreds of examples on SO about delayedexpansion.
I am trying to write a batch file that does the following:
Prompt user for the directory to create the new folder newest
Prompt user for an integer limit
Create directory newest
CD newest
FOR loop for limit iterations
Create directory "Month " + iteration
For example:
newest = Reports
limit = 12
I should end up with:
\Reports\Month 1
\Reports\Month 2
\Reports\Month 3
...
\Reports\Month 12
This is my code so far:
setlocal enabledelayedexpansion
FOR /L %%i IN (1,1,%limit%) DO (
set "month_counter=Month %%i"
echo %month_counter%
MD %month_counter%
)
endlocal
If I set limit = 12, I get 12 error messages stating:
Echo is off.
The syntax of the command is incorrect.
I appreciate the help.
FOR /L %%i IN (1,1,%limit%) DO (
MD "Month %%i"
)
You have the standard delayed expansion problem - hundreds of articles on SO about this.
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Note therefore the use of CALL ECHO %%var%% which displays the changed value of var. CALL ECHO %%errorlevel%% displays, but sadly then RESETS errorlevel.
So - you could use
set "month_counter=Month %%i"
CALL echo %%month_counter%%
If you really, really want to - or one of the other techniques, but it's far easier to simply make the directory from your constant data + the iteration counter in %%i as shown.
#echo off
cd %~dp0
md .\newfolder
for /f "usebackq delims=" %%f in ("list.txt") do (
call set /a add=%%add%%+1
call set addx=0000%%add%%
call set addx=%%addx:~-3%%
call copy "%%f" ".\newfolder\%%addx%%_%%f"
)
pause
I made simple namechange code. I usually use command without 'call' but here it makes error message . why is that? .. and when i use %variable% not %%variable%% , It doesn't work well..
plz tell me why it happens.. and last question.. environment variable's value is stored until exit cmd . I want to know how i can unset that.. thank you..
All code within a parenthesized block is parsed in one pass. Normal variable expansion using percents occurs at parse time. So if you set a variable within a block, you cannot access the value using normal expansion because the value will be the value that existed before you entered the block.
You have the above situation. There are two classic ways to resolve the problem.
1) You can use CALL and double the percents as you have done. The CALL solves the problem because normal expansion occurs twice for a called line - once for the entire block, and again before the line is executed, but after previous lines in the block have executed. The first expansion converts the double percents to single percents, and the second expansion actually expands the variable.
I do not like this solution because it is slow, and also because the CALL causes problems with quoted ^ characters - they are doubled.
You can use multiple CALLs on the same command. Each Call requires the percents to be doubled. So one CALL requires 2 percents, two CALLs requires 4 perecents, three CALLs 8 percents, etc.
2) I think the preferred solution is to use delayed expansion. It is much faster, and also you never have to worry about escaping or quoting special characters like &, |, >, < etc. when you used delayed expansion. Delayed expansion does just what it says - the variable is not expanded until just before the line is executed. Delayed expansion must be enabled before it can be used. Within a batch file you can use setlocal enableDelayedExpansion.
The one problem that can occur with delayed expansion is FOR variables are corrupted if they contain ! and delayed expansion is enabled when they are expanded. That can usually be solved by toggling delayed expansion on and off within the loop.
If you type HELP SET from the command prompt, you will get a pretty good description of the problem with expanding variables within a block of code, and how delayed expansion can help. The description starts about half way down with the words Finally, support for delayed environment variable expansion....
Note - you do not need to expand variables when used within a SET /A computation. SET /A will automatically expand the value at execution time. Undefined variables are treated as zero.
In your code, you can simply use set /a add=add+1
But there is an even simpler shorthand way - you can use the += operator: set /a add+=1.
Here is another way your code could be written without using CALL. The code is untested, but I think I got it right.
#echo off
setlocal disableDelayedExpansion
cd "%~dp0"
md newfolder
set add=0
for /f "usebackq eol=: delims=" %%F in ("list.txt") do (
set /a add+=1
set "file=%%F"
setlocal enableDelayedExpansion
set "addx=00!add!"
copy "!file!" "newfolder\!addx:~-3!_!file!"
endlocal
)
pause
I explicitly initialize add to 0 because it might already be set to a value. If you know that it is undefined or already set to 0, then the initialization is not needed.
Your FOR loop is dealing with file names, and ! is valid within file names. That is the reason I toggle delayed expansion on and off within the loop - I don't want file names with ! to be corrupted when I expand %%F. File names can also start with ; (though highly unlikely). If it does, then FOR will skip that file because the default EOL character is ;. A file can never start with :, so I like to set EOL to : instead.
I put SETLOCAL near the top so that the environment variable definitions do not persist after the batch file completes.
Hi Can anyone help me out in this problem.
I need to create multiple file?? i give with any example. In some folder, say Folder Records.
"Record" folder contain 1 file by name "example2tought1023.au" . i need to generate same file contains, multiple time just by increasing the numbers.
i should get result like this example2tought1023.au example3tought1024.au example4tought1025.au example5tought1026.au
This is what I currently have:
SET count=9
SET filename_1=example
SET filename_2=thought
SET extension=.au
SET start_1=2
SET start_2=1023
SET source=%filename_1%%start_1%%filename_2%%start_2%%extension%
FOR /L %%i IN (1, 1, %count%) DO (
REM These two lines do not work!
SET /a n=%start_1%+%%i
SET /a number_2=%start_2% + %%i
SET destination=%filename_1%%number_1%%filename_2%%number_2%%extension%
ECHO %destination%
REM COPY %source% %destination%
)
PAUSE
but the lines in the FOR /L loop do not work
You have mis-identified which lines are not working :-)
The problem you are having relates to when variables are expanded. Normal expansion using percents occurs when the line is parsed, and your entire FOR statement, including the parenthesised DO clause, is parsed in one go. So the following line
SET destination=%filename_1%%number_1%%filename_2%%number_2%%extension%
is seeing the values of %number_1% and %number_2% that existed before the loop was executed. Obviously not what you want. The solution is simple - you need to use delayed expansion (the value at run time instead of parse time). You do that by 1st enabling delayed expansion using setlocal enableDelayedExpansion, and then use !number_1! instead of %number_1%.
You are not consistent with your variable names (n vs number_1).
I think you want to count from 0 to count-1 instead of from 1 to count.
You do not have to explictly expand the variable when using a variable in a SET /A expression. You can simply use the variable name without percents or exclamations. But this only works with the SET /A command.
You can also perform multiple computations and assignments with a single SET /A command by using a comma between each assignment.
#echo off
setlocal enableDelayedExpansion
REM counts from 0 to count, so 8 = 9 copies
set count=8
set filename_1=example
set filename_2=thought
set extension=.au
set start_1=2
set start_2=1023
set source=%filename_1%%start_1%%filename_2%%start_2%%extension%
for /L %%i in (0, 1, %count%) do (
set /a "number_1=start_1+%%i, number_2=start_2+%%i"
set destination=%filename_1%!number_1!%filename_2%!number_2!%extension%
echo !destination!
REM copy %source% !destination!
)
pause