I'm trying to determine the format of a text file by looping through the first 10 lines, perform some regex matching and then compare the results at the end. I can easily loop through the entire file, but I only want the first N lines (in this case 10)
I'm familiar with other languages, but the idiosyncrasies of this batch file is throwing me for a loop so to say.
Here is what I have so far:
#echo off
setlocal enableDelayedExpansion
set /A REGEXCOUNTER=0
set /A COUNTER=0
for /F %A in (%submitfile%) do (
set /A COUNTER=COUNTER+1
rem echo %A
setlocal enableDelayedExpansion
echo(%A|findstr /r /c:"[0-9].*" >nul && (
set /A REGEXCOUNTER=REGEXCOUNTER+1
echo %COUNTER% - %REGEXCOUNTER% - FOUND - %A
rem any commands can go here
) || (
echo NOT FOUND
rem any commands can go here
)
rem LOOP END
if %COUNTER% GEQ 10 do (goto loop_over)
)
)
:loop_over
echo "END HERE!"
I've got counters set up that incrementally tick up to count my matches and how many times it's looped. However here is some sample output of variable values:
110 - 0 - FOUND - 003
220 - 0 - FOUND - 2
330 - 0 - FOUND - 1
440 - 0 - FOUND - 029
The loop counter variable is increasing by ten for each loop and the regex match counter is not going up at all. I'm pretty sure this has something to do with variable scope but I'm not sure where to begin.
This should fix all the issues I talked about in my comments above.
#echo off
setlocal enableDelayedExpansion
set /A REGEXCOUNTER=0
set /A COUNTER=0
for /F %%A in (%submitfile%) do (
set /A COUNTER=COUNTER+1
rem echo %%A
echo(%%A|findstr /r /c:"[0-9].*" >nul && (
set /A REGEXCOUNTER=REGEXCOUNTER+1
echo !COUNTER! - !REGEXCOUNTER! - FOUND - %%A
rem any commands can go here
) || (
echo NOT FOUND
rem any commands can go here
)
rem LOOP END
if !COUNTER! GEQ 10 goto loop_over
)
)
:loop_over
echo "END HERE"
The extra setlocal within the loop should be removed. Once setlocal enabledelayedexpansion has been executed, it remains in effect until a setlocal disabledelayedexpansion is executed or until the batch terminates.
Each %A (the loop-control metavariable must be %%A (one % if run from the command-line, two if within a batch file)
If you change the value of a variable within the loop, then you need to refer to the changed value as !varname!, not %varname% which is the original value (search SO for delayed expansion). set /a always works on the current variable value.
Related
I'm making a small little game I want a random chance to get certain items (e.g. if the random number is greater than 10 but less than 15 then you will get a certain item). Here's what I've already tried which resulted in a crash.
set /a chance= %random% %%30+1
if %chance% gtr 10 && lss 30 (
set /a %nails%+1
echo You got nails! %chance%
)
This piece right here was just a test, but should give you an idea of what I am going for. This is really the only way I can think of doing it. If you could help, please do! :)
I see a number of problems in that code:
set /a chance= %random% %%30+1
if %chance% gtr 10 && lss 30 (
set /a %nails%+1
echo You got nails! %chance%
)
Going through them:
The if statement is not valid, && is the "execute next command if previous command worked" conjunction, not a general "and" operator. To do what you want would be:if %chance% gtr 10 if %chance% lss 30.See here for a way to do and and or in cmd language.
The command set /a %nails%+1 does not actually change nails in any way, it just evaluates an expression and throws it away. You need an assignment to assign a value, and you don't need the variable markers in this case:set /a "nails += 1".
If you're using delayedexpansion to print out nails (and you should be), you need a ! both before and after the variable name:echo You got !nails! %chance%.
As an aside, you'll probably notice I have a penchant for quoting my set /a expressions and spacing them nicely - I find this aids readability.
That will fix some specific problems but, to be honest, you're probably better off making a generic function that can give you a yes/no answer for some probability of an event happening. That way, you can reuse it anywhere you need it.
You can use a function like chance, shown below in a complete program, to decide whether something should happen based on a percentage:
#echo off
goto :main
:chance
setlocal enableextensions enabledelayedexpansion
set retcode=1==0
set /a "value = %random% %% 100"
rem echo %value% rem uncomment for debugging
if %value% lss %2 set retcode=1==1
endlocal && set %1=%retcode%
goto :eof
:main
call :chance result 50
echo %result%
It should be called with both a variable name to put the result into, and the percentage level you want to use. For example, if you wanted to set a variable hasdied based on a 5% chance, you would call it with:
call :chance hasdied 5
if %hasdied% goto :handlebeingdead
The function contains a number of features which probably bear explanation:
The setlocal command ensures that no variables escape the scope of this function (but see below), useful for proper encapsulation.
The value variable is set to some random value between 0 and 99 inclusive. It's not perfectly distributed since %random% will give you a value up to 32767 so will be slightly skewed toward numbers less than 68. Said skew is probably not enough to concern yourself with.
This value is then compared with the threshold you provided (the second argument) to decide the return value true or false.
The return value is rather sneaky in that it gives you an expression that you can put into an if statement without having to do an explicit comparison like:if %hasdied%==1By returning such an equality comparison directly, you can just use the return value as if it was boolean.
The endlocal then cleans up any variable changes that have been made in this function, including the return code. However, the fact that the substitutions on this line take place before any of it is executed means that the set part of it will already have the correct value of retcode substituted before the endlocal cleans it up. This is a way to have specific variables "escape" the scope bounded by setlocal/endlocal. The retcode value is therefor placed in the parameter whose name you provided as the first argument.
The set %1= part of that command is a way to allow you to specify what variable should receive the value in the call itself, akin to myvar = function(). That stops you from having to allocate a hard-coded variable name to each function and then assign it to another variable after the call.
And, of course, the goto :eof is simply a return instruction.
I'm pretty sure the && does not exist in batch. Nested if statements work:
set /a chance= %random% %%30+1
echo %chance%
IF %chance% GTR 10 (IF %chance% LSS 15 (
echo You got nails! %chance%
))
You cannot use && like that. You need to run the if statement twice to match both gtr and lss you can put them one after the other:
#echo off
set /a chance=%random% %%30+1
if %chance% gtr 10 if %chance% lss 30 (
set /a nails+=1
echo You got nails! %chance%
)
Also note the correct way of increasing a variable set /a nails+=1
the if condition approach works and all, but is somewhat clunky if your going to be scripting in many loot situations. it is by far easier to use an array setup with a macro that can access ranges within the array to allow you to simply and easily script loot boxes that roll different items by using substring modification to change the index of the array the random number can access. a demonstration:
#Echo off
:new
::: -------------------------------------------------------------------|| MACRO DEFINITIONS
Setlocal DisableDelayedExpansion
(Set \n=^^^
%=DNR=%
)
rem ********************* Display any existing character names for continuation or deletion of characters
If Exist "%TEMP%\%~n0_*_save.bat" (Echo/Your Characters:&Echo/&(For /F "Delims=" %%G in ('Dir "%TEMP%\%~n0_*_save.bat" /B')Do For /F "Tokens=2 Delims=_" %%o in ("%%~nG") Do < Nul Set /P "=[%%o] ")&Echo/)
:character
Set /P "Name=Name: "
If Exist "%TEMP%\%~n0_%Name%_save.bat" (Echo/[C]ontinue / [D]elete?&For /F "Delims=" %%O in ('Choice /N /C:cd')Do If /I "%%O"=="C" (Goto :playon)Else (Del /P "%TEMP%\%~n0_%Name%_save.bat" & Goto :character))
If "%Name%"=="" Goto :character
:playon
rem *** Inventory Macro. Displays all elements for the given group and their current values.
rem ::: Usage: %INV:#=$varname[%
Set "INV=Echo/&(For /F "Tokens=2 Delims==" %%i in ('Set #') Do (Set "VN=%%i"&^< Nul Set /P"=[!VN:$=!:!%%i!] "))&Echo/"
rem *** Autosave macro. Can be incorperated into other macro's
rem ::: Usage: %Save%
Set SAVE=(For /F "Tokens=1 Delims==" %%i in ('Set $') Do (If not "!%%i!"=="" Echo/Set "%%i=!%%i!"))^>"%TEMP%\%~n0_!name!_save.bat"
rem *** Location Display Macro with autosave macro included
rem ::: Usage: %Loc:#=LocationLABEL%
Set "Loc=(Set "$Loc=#"&Title !$Loc:_= !)&%Save%"
rem *** Loot box Macro to generate random loot from specified range of an indexed array
rem *** !random! %%4 + Index# will access an index range between the index # and 4 above the index number.
rem ::: Usage: %Loot:#=index#%
Set "LOOT=(For /F "UsebackQ Delims=" %%i in (`"Set /A i#=!Random! %%4 + #"`) Do For /F "UsebackQ Delims=" %%v in (`"Set /A v#=!Random! %%3 + 1"`) Do (Set "VN=!$Loot[%%i]:$=!"&Echo/You got %%v !VN!&Set /A "!$Loot[%%i]!+=%%v")) 2> Nul & %SAVE%"
rem *** the below macros /I /V and /P are not used in this example. - They are an optional method for defining
rem *** variables prefixed with $ that automatically saves them for reloading
rem ::: usage: %/I:V=Varname%Input Prompt String:
Set "/I=For %%n in (1 2)Do If %%n==2 (Set /P "$V=!$PromptStr:$=!: "&%Save%)Else Set $PromptStr="
rem ::: usage: %/P:V=Varname%VariableValue
Set "/V=For %%n in (1 2)Do If %%n==2 (Set "$V=!str!"&%Save%)Else Set str="
rem ::: usage: %/A:V=Varname%=Equation
Set "/A=For %%n in (1 2)Do If %%n==2 (Set /A "$V!sum!"&%Save%)Else Set sum="
rem *** Wait prompt Macro
rem ::: usage: %Wait:#=Integer value for time in seconds%Wait Prompt String
Set "Wait=For %%n in (1 2)Do If %%n==2 (Timeout # /Nobreak > Nul & (Pause | Echo/!Output!) 2> Nul )Else Set Output="
rem *** Array definition macro. Asigns the element names to an indexed Groupname (Array), With each element being assigned an initial 0 value
Rem ::: Usage: %DefArray%{VarGroupName}{Element names as list}
Set DefArray=For %%n in (1 2) Do if %%n==2 (%\n%
Set "i#=0"%\n%
For /F "Tokens=1,2 Delims={}" %%G in ("!List!") Do (%\n%
For %%i in (%%~H) Do (%\n%
Set "$%%~G[!i#!]=$%%i"%\n%
Set "$%%i=0"%\n%
Set /A i#+=1 ^> Nul%\n%
)%\n%
)%\n%
) Else Set List=
Set Menu=CLS^&Set "Copt="^&For %%n in (1 2) Do if %%n==2 (%\n%
Echo/[E]xit%\n%
For %%G in (!OPTS!)Do (%\n%
Set "opt=#%%~G"%\n%
Set "opt=!opt:_= !"^&Set "Opt=!Opt:~,-1!"%\n%
Set "Copt=!Copt!%%~G"%\n%
Echo/!Opt! [%%~G]%\n%
)%\n%
(For /F "Delims=" %%O in ('Choice /N /C !Copt!E')Do If "%%O"=="E" (Endlocal^&Endlocal^&Set "Name="^&Goto :New) Else (CLS^&Goto :#%%O))%\n%
) Else Set OPTS=
::: -------------------------------------------------------------------|| END MACRO DEFINITIONS
::: -------------------------------------------------------------------|| Example Script
REM // required to be enabled PRIOR to macro Use, AFTER definition.
Setlocal EnableDelayedExpansion
%DefArray%{Loot}{Wood Nails Ore Leather Gold Silver Bronze Jade}
IF Exist "%TEMP%\%~n0_!name!_save.bat" (
Call "%TEMP%\%~n0_!name!_save.bat"
Goto :!$Loc!
)
:Menu
%Loc:#=Menu%
%Menu:#=Loot_Box_% "1" "2"
Goto :Menu
:Loot_Box_1
%Loc:#=Loot_Box_1%
%Loot:#=0%
%INV:#=$Loot[%
%Wait:#=1%
Goto :Menu
:Loot_Box_2
%Loc:#=Loot_Box_2%
%Loot:#=4%
%INV:#=$Loot[%
%Wait:#=1%Demo wait prompt
Goto :Menu
I have a program that reads the file and outputs a random file line number, but when it goes to output it, it just says "Echo is off" Is it possible to fix this?
Here is my code:
#echo off
setlocal enabledelayedexpansion
set Counter=1
for /f "tokens=* delims=" %%x in (Lists.txt) do (
set "Line_!Counter!=%%x"
set /a Counter+=1
)
set /a Counter=%random% * 100 / 32768 + 1
echo %Counter%
echo "%Line_!Counter!%"
::Just displays echo is off
pause
echo "!Line_%Counter%!" will work. (not very intuitive, but makes sense, if you think about it)
There are two problems in your code.
The first has been pointed by Stephan. If you read the answers to this question, you will see that the parser replaces variables referenced with percents before variables referenced with exclamation marks. So when the parsers tries to handle %Line_!Counter!%, !Counter! is still not replaced in the line so %Line_!Counter!% is an undefined variable and is replaced with nothing. The inverse (!Line_%Counter%!) works because when the parser reaches the line, the first substitution is the percent variable and the last the exclamation mark variable.
The second is a logic error. The line
set /a Counter=%random% * 100 / 32768 + 1
will not work as intended if the file has more or less than 100 lines. If it has more, the higher numbered lines will never be selected. If it has less lines, a high numbered non existing line can be selected and as it does no exist, you will again get an echo is off message trying to echo the variable.
#echo off
setlocal enabledelayedexpansion
set Counter=0
for /f "tokens=* delims=" %%x in (Lists.txt) do (
set /a Counter+=1
set "Line_!Counter!=%%x"
)
set /a "selected=%random% %% Counter + 1"
echo %selected%
echo "!Line_%selected%!"
pause
Am very new to batch scritping. I just tried to execute the below code for which am totally confused.
#echo off
set m=100
set i=0
FOR /L %%N IN (0,1,%m%)do (
set /a i=1+%i%
)
echo %i%
pause
Here, am incrementing i value until N reaches 100. So, am expecting the value of i at the end of loop to be 101 but it shows 1. Can anyone explain what's the reason behind this.
thanks
You need delayed expansion : http://www.robvanderwoude.com/variableexpansion.php
#echo off
setlocal enableDelayedExpansion
set m=100
set i=0
FOR /L %%N IN (;;0,1,%m%;;) do (
set /a i=1+!i!
)
endlocal & set /a i=%i%
echo %i%
In batch files, variable reads are replaced with their values at parse time, before executing the line or the block (code enclosed in parenthesis).
So, the %i% reference inside the for loop is replaced with the value of the variable before the for loop, that is, 0 , and %m% is replaced with 100, and what gets executed is
for /l %%n in (0 1 100) do ( set /a i=1+0 )
You can enable delayed expansion (setlocal enabledelayedexpansion command) and change the sintax from %i% to !i! to indicate the parser that reads to the variable should be delayed until the moment of executing the line. The code should be
#echo off
setlocal enabledelayedexpansion
set m=100
set i=0
FOR /L %%N IN (0,1,%m%)do (
set /a i=1+!i!
)
echo %i%
endlocal
The read of the value of i inside the for loop is delayed. This way, the real value is readed just before each execution of the line. Reference to i out of the loop does not need it. When the line is reached and parsed, the correct value is retrieved and echoed.
Anyway, while this is the general rule, set /a has a different behaviour. If you change your code to
set /a i+=1
or
set /a i=i+1
the parser will correctly identify the operation and execute the expected code with or without delayed expansion enabled.
I am new in batch. Trying for some days to make something in batch but have a problem I cannot solve. I read a lot of your comments but did not find answer. Maybe you can help me?
The point is:
I input string from keyboard( e.g. 10 characters ). name of it is"allinputstring"
Calculate of length is ok ( by redirect in txt file and expand its bytes ). name "length"
Parse string in 10 pieces (strings) is ok.
So here is a problem, I want to echo these pieces, so I use next code, I use a counter to find out is the counter give me good count as output variable, and echo it to see on screen if it is good. Counter seems good, end echo of pieces strings is good enough. But I want to put in line 5. Variable count instead of "%%m", and cannot find a syntax way how to do it.
setlocal enabledelayedexpansion
for /l %%m in (1,1,!lenght!) do (
set /a count=0
set /a count=count+%%m
echo !count!!allinputstring:~%%m,1!
)
endlocal
please help me.
Try this:
#echo off &setlocal enabledelayedexpansion
set /a lenght=9
set "allinputstring=ABCDEFGHIJ"
for /l %%m in (0,1,%lenght%) do (
set /a count=0
set /a count+=%%m
echo !count! !allinputstring:~%%m,1!
)
endlocal
Output is:
0 A
1 B
2 C
3 D
4 E
5 F
6 G
7 H
8 I
9 J
#ECHO off
setlocal ENABLEDELAYEDEXPANSION
SET allinputstring=abcdefghijk
SET lenght=10
for /l %%m in (1,1,!lenght!) do (
set /a count=0
set /a count=count+%%m
FOR %%z IN (!count!) DO echo !count! !allinputstring:~%%z,1!
)
GOTO :eof
Does this do what you require?
So... to make COUNT show (I've assigned it to KOWNT, but the syntax endlocal&set count=%count% would assign it to COUNT instead)
I've changed the starting value of the FOR/L because character counting starts from character#0 in the string.
#ECHO off
setlocal ENABLEDELAYEDEXPANSION
SET allinputstring=abcdefghij
SET lenght=9
for /l %%m in (0,1,!lenght!) do (
set /a count=0
set /a count=count+%%m
FOR %%z IN (!count!) DO echo !count! !allinputstring:~%%z,1!
)
endlocal&SET KOWNT=%count%
ECHO Now KOWNT=%KOWNT% but count=%count% because we have exited the SETLOCAL
GOTO :eof
When the ENDLOCAL is encountered, the parser substitutes the CURRENT value of the variables in the line and THEN executes the line.
Hence, the line is executed as
endlocal&set KOWNT=9
since the value of count at the time is 9.
When the SETLOCAL is executed, all changes to the environment since the matching SETLOCAL are thrown away. The environment variables are restored to their state when the SETLOCAL was executed and count becomes empty again (as it was before the routine.) THEN the SET instruction is executed, which sets KOWNT to 9.
I have a for loop in a batch file that looks like this:
for %%y in (100 200 300 400 500) do (
set /a x = y/25
echo %x%
)
The line:
set /a x = y/25
Doesn't seem to be doing any division. What is the correct syntax for dividing each y by 25? I only need the integer result from this division.
Environment variables need not be expanded to use in a SET /A statement. But FOR variables must be expanded.
Also, even if your computation worked, the ECHO would fail because percent expansion takes place when a statement is parsed, and the entire FOR construct is parsed at once. So the value of %x% would be the value as it existed before the loop is executed. To get the value that was set within the loop you should use delayed expansion.
Also, you should remove the space before the assignment operator. You are declaring a variable with a space in the name.
#echo off
setlocal enableDelayedExpansion
for %%A in (100 200 300 400 500) do (
set n=%%A
REM a FOR variable must be expanded
set /a x=%%A/25
REM an environment variable need not be expanded
set /a y=n/25
REM variables that were set within a block must be expanded using delayed expansion
echo x=!x!, y=!y!
REM another technique is to use CALL with doubled percents, but it is slower and less reliable
call echo x=%%x%%, y=%%y%%
)
It's not doing anything because "y" is just a letter. You need percent signs to reference the variable.
set /a x = %%y/25
I was having the same issue but turned out to be an integer issue. I was multiplying after dividing but need to before. What was happening is something like this:
1/100x100 which operates like 1\100=0 then 0x100=0
I changed it to
1x100/100 which operates like 1x100=100 then 100/100=1
#echo off
setlocal ENABLEDELAYEDEXPANSION
for /f "usebackq" %%b in (`type List.txt ^| find "" /v /c`) do (
set Count=%%b
)
)
REM Echo !Count! -->Returns the correct number of lines in the file
for /F "tokens=*" %%A in (List.txt) do (
set cName=%%A
set /a Number+=1
REM Echo !Number! -->Returns the correct increment of the loop
set /a Percentage=100*!Number!/!Count!
REM Echo !Percentage! -->Returns 1 when on the first line of a 100 line file
set a=1
set b=1000
set /a c=100*1/100
Rem -->echo c = !c! --Returns "C = 1"
)