Batch /a doesn't store a value - variables

Code:
#echo off
set /a x=5
set /a TNUM=%x%-3
echo %TNUM%
pause
And the output is:
ECHO is off
So, how do I fix this?
Here's the full code:
#echo off
setlocal enableextensions
set /a COLUMNS=60
set /a ROWS=40
mode con: cols=%COLUMNS% lines=%ROWS%
:main
set /a xpos=5
set /a ypos=5
call:displayScreen
pause >nul
goto :eof
::FUNCTIONS::
:displayScreen
set /a TEMPR=%ROWS%-3
for /l %%c in (1,1,%COLUMNS%) do echo|set /p=#
for /l %%r in (1,1,%TEMPR%) do (
echo|set /p=#
if %%r==%ypos% (
set /a TNUM=%xpos%-3
echo %TNUM%
for /l %%t in (1,1,%TNUM%) do echo|set /p=A
echo|set /p=o
set /a TNUM=%COLUMNS%-%xpos%-1
for /l %%t in (1,1,%TNUM%) do echo|set /p=A
) else (
set /a TNUM=%COLUMNS%-2
for /l %%t in (1,1,%TNUM%) do (
echo|set /p=A
)
)
echo|set /p=#
echo.
)
for /l %%c in (1,1,%COLUMNS%) do echo|set /p=#
The main problem is in the display screen function. It is supposed to print the screen and have the x at the two coordinates I gave it. I put in echo %TNUM% so I could see what was in it, and it always amounts to "".

There are mainly three ways to obtain the indicated behaviour
1 - Problems with variable expansion. In batch files, lines or blocks of lines (a set of lines enclosed in parenthesis), are parsed, executed and then the operation is repeated on the next line/block.
During the parse phase all read operations on variables (any %var%) are removed from code, replacing them with the value stored in the variable before starting to execute the line/block of code.
So, if inside the line/block the value of a variable is changed, this value can not be readed/retrieved inside the same line/block during execution phase. Why? Because there are no read operations in the line/block to retrieve the value of the variable. This code
set x=0
if 1==1 (
set x=1
set y=%x%
)
echo x=%x% y=%y%
will echo to console x=1 y=0 Why? When the if block of code is parsed (the full block), the read operations (=%x%) where replaced at parse time with the values inside the variables at parse time and the code executed is
if 1==1 (
set x=1
set y=0 <- Read operation in x replaced with its value at parse time
)
that is, the changed value stored in x can not be retrieved.
This case is usually solved with the usage of delayed expansion. It is a behaviour that can be activated with command setlocal enabledelayedexpansion and that allow the programmer to change, where needed, the syntax to access variable content from %var% to !var!, indicating to the parser that the expansion of the variable (the replacement of the read operation with the value of the variable) must be delayed until the execution phase. In the proposed sample it should be
setlocal enabledelayedexpansion <- delayed expansion enabled
set x=0
if 1==1 (
set x=1
set y=!x! <- Read operation delayed
)
echo x=%x% y=%y%
2 - Spaces. An habitual problem with variable declaration is
set /a TNUM = %x%-3
^.......This value is included in the variable name
The space is included in the variable name, and echoing %TNUM% fails as this is not the name of the variable, it is named %TNUM %.
BUT as i forgot and #jeb corrects in comments, while set and set /p exhibit this behaviour, this will NOT happen when set /a is used. In this case a parser with different rules is used (for example, it allows variables references without previous expansion) and the indicated space will be supressed.
3 - You machine has extensions disabled by default. If extensions are disabled, set /a will not work and no value will be assigned to the variable
#echo off
setlocal enableextensions
set /a x=5
set /a TNUM=x-3
echo %TNUM%
Summarizing, to execute the line echo %TNUM% and obtain the indicated ECHO is off it is necessary that when the line is parsed TNUM is not defined or it only contain spaces or tabs.
As, a priori, when the line set /a TNUM=%x%-3 is executed, a value is assigned to the variable (at least the -3), it seems your problem is the case 1 (the echoed value was replaced before the execution) or the case 3 (you have no value to echo as the set operation failed).
edited With all the code posted, the problem is delayed expansion.
#echo off
setlocal enableextensions enabledelayedexpansion
set /a COLUMNS=60
set /a ROWS=40
mode con: cols=%COLUMNS% lines=%ROWS%
:main
set /a xpos=5
set /a ypos=5
call :displayScreen
pause >nul
goto :eof
::FUNCTIONS::
:displayScreen
set /a TEMPR=%ROWS%-3
for /l %%c in (1,1,%COLUMNS%) do echo|set /p=#
for /l %%r in (1,1,%TEMPR%) do (
echo|set /p=#
if %%r==%ypos% (
set /a TNUM=%xpos%-3
for /l %%t in (1,1,!TNUM!) do echo|set /p=A
echo|set /p=o
set /a TNUM=%COLUMNS%-%xpos%
for /l %%t in (1,1,!TNUM!) do echo|set /p=A
) else (
set /a TNUM=%COLUMNS%-2
for /l %%t in (1,1,!TNUM!) do (
echo|set /p=A
)
)
echo|set /p=#
)
for /l %%c in (1,1,%COLUMNS%) do echo|set /p=#

It looks like it isn't waiting to evaluate %x%. So you can add ENABLEDELAYEDEXPANSION so that it will wait to evaluate x so that it will be initialized during the math operation.
#echo off
setlocal ENABLEDELAYEDEXPANSION
set /a x=5
set /a TNUM=!x!-3
echo !TNUM!
endlocal
pause

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.
Since the for...%%r... loop executes a block of code (from the ( following the do through to the matching ) ) then any %tnum% appearing within that block will be replaced by the value of tnum when the loop was parsed. Tnum has no value when the block is started, so that's what is shown.
tnum is being set within the loop, so since you have enabledelayedexpansion set to ON in the setlocal enabledelayedexpansion statement at the start of the program, you need to use !tnum! in place of %tnum% within the loop to access the value of tnum as it changes.

Related

Batch: variable definition useable in count loop

In my batch I want to copy a variable amount of source- to target destinations.
I want to define like this:
#setlocal EnableDelayedExpansion
set source1="C:\folder1"
set target1="f:\folder1"
set source2="C:\folder2"
set target2="f:\folder2"
...
set sourcen="C:\foldern"
set targetn="f:\foldern"
Dependently from a defined amount of folders
set numFolder=5
I want to go through the folders in a loop:
set /a COUNT=0
:LOOP
echo %COUNT%
set /a COUNT+=1
rem write the NAME of the parameter variable (source1,source2 etc.) in nameor
set "nameor=source%COUNT%"
rem write the VALUE of the parameter variable (source1,source2 etc.) into origin ("C:\folder1", "C:\folder2")
set "origin=%nameor%"
echo %origin%
if %COUNT% lss %numFolder% goto LOOP
When I show
echo %nameor%
I get what I expectet: source1, source2 etc.
but
echo %%%origin%%%
only provides
source1
instead of the expected value
"C:\folder1"
I thought, that I could resolve this by using DelayedExpansion but what did I miss?
To avoid confusion for me, I change the "origin" to "source". E.g. set "origin=%nameor%" changed to set "source=%nameor%".
To print out "C:\folder1" to "C:\foldern", you should use echo !%source%!, else you will just see "source1" to "sourcen".
Your problem is just about array element management. Try this:
#echo off
setlocal EnableDelayedExpansion
rem Define the two arrays
set i=0
for %%a in ("C:\folder1=f:\folder1"
"C:\folder2=f:\folder2"
"C:\foldern=f:\foldern") do (
set /A i+=1
for /F "tokens=1,2 delims==" %%b in (%%a) do (
set source!i!="%%a"
set target!i!="%%b"
)
)
rem Show up to numFolder elements of both arrays
set numFolder=5
for /L %%i in (1,1,%numFolder%) do (
echo %%i- Source%%i=!source%%i!, Target%%i=!target%%i!
)
The first part is equivalent to your series of individual element assignments. In this way is easier to add new pairs of values.
For further description on array management in Batch files, see: Arrays, linked lists and other data structures in cmd.exe (batch) script

Variable set in batch file won't show

When I do this in Notepad, the command prompt doesn't show ping %ip% -t -l %package%, but it shows ping %ip% -t -l and doesn't show the package variable.
#echo off
set data=0
set package = -600
IF %data% == 0 (
set /a package= %package% + 1600
#echo ping %ip% -t -l %package%
)
echo %package%
pause
What am I doing wrong?
Batch is sensitive to spaces in a SET statement. SET FLAG = N sets a variable named "FLAGSpace" to a value of "SpaceN"
The set "var=value" syntax ensures that any trailing spaces on the batch line are not included in the value assigned to var.
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.
So:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
set /A data=0
set /A package=-600
IF %data% == 0 (
set /a package=!package!+1600
echo ping %ip% -t -l !package!
)
echo %package%
pause
Noting: The setlocal statement should normally be placed at the start of the code. Your posted code is evidently a snip, since you do not appear to be setting ip.
Spaces are irrelevant in a set /a but even so, removing them fosters a habit
Set /a uses the run-time value, not the parse-time value of var when the syntax set /a var=var+1 is used within a loop, so set /a var=var+1 and set /a var=!var!+1 are equivalent but set /a var=%var%+1 uses the value of var at the time the loop is parsed.
Since echoing is set to off by the initial statement, the leading # on the second echo is redundant.

Batch increment part of variable name

How can I increment part of a variable name?
EG. i want to increment
filename holds something like mmddyyyy-prev01-Database.mdb
and I'm trying to rename it to mmddyyyy-prev02-Database.mdb
With the code
Setlocal EnableDelayedExpansion
set /a nextFileName=!fileName:~0,13!!fileName:~13!+1
and I get the error
Missing operator.
I am using this to make a batch sub that will recursively rename the prevXX items so that the newest one is always prev01. I'm also doing regular setlocal in my method to keep the variables from getting mixed up between the deeper calls
I'm going the hard way:
#ECHO OFF &SETLOCAL disableDelayedExpansion
FOR /f "delims=" %%x IN ('DIR /b /a-d /o-n *-prev*-Database.mdb') DO (
FOR /f "tokens=1-3*delims=v-" %%a IN ("%%~x") DO (
FOR /f "tokens=*delims=0" %%e IN ("0%%c") DO (
FOR /f %%f IN ('SET /a %%e+1') DO (
IF %%f LEQ 9 (
ECHO REN "%%~x" "%%~a-%%~bv0%%~f-%%~d"
) ELSE (
ECHO REN "%%~x" "%%~a-%%~bv%%~f-%%~d"
)
)
)
)
)
Just remove the ECHOs to make it working.
SET /A won't allow you do this.
You'll have to break up into two lines, like this:
SET BaseFileName=!FileName:~0,13!
SET /A Counter=!FileName:~13!+1
Then, put the pieces back together:
SET NextFileName=%BaseFileName%%Counter%-Database.mdb
NOTE: This will not handle your leading 0...you'll have to write more code for that.
...
set filename=mmddyyyy-prev01-Database.mdb
call :nextname
ren %filename% %newname%
...
:nextname
set /a newname=1%filename:~13,2%+1
set newname=%filename:~0,13%%newname:~-2%%filename:~15%
goto :eof
Whether you'd use ren %filename% %newname% or ren !filename! !newname! is a matter of context. You've posted insufficient code to be certain. If, as I suspect, you are executing the ren in a loop, then you would need the !var! form and delayedexpansion (with which you appear familiar.) Within the subroutine :nextname, the %var% form should be used.
but remember that you need to rename your existing files in reverse-alphabetical order, so
mmddyyyy-prev03-Database.mdb becomes mmddyyyy-prev04-Database.mdb
mmddyyyy-prev02-Database.mdb becomes mmddyyyy-prev03-Database.mdb
mmddyyyy-prev01-Database.mdb becomes mmddyyyy-prev02-Database.mdb

How to create new variable out of current variable contents

I have a variable "var1" with contents of "sharp=soothe"
var1=sharp=soothe
How do I create a new variable using only var1 contents to get this
sharp=soothe
I want to use the set command somehow like this
set %var1%=
but I can't get further than this. I hope I don't have to search for strings before and after the = sign. I thought there would be a faster way than that.
OK, I am so close. I found some other code I'm using and this nearly works for an infinite number of variables but the set command syntax is wrong again.
set var1=unfilter=rem
set var2=mdegrain=rem
set n=0
:parseArgs2
set /a n+=1
if defined var%n% (
set %var%n%%
shift /1
goto :parseArgs2
)
Just remove one character from your attempt :-)
set %var1%
If you have an "array" of variables: var1, var2, ... var20, then you can use a FOR /L loop with delayed expansion:
setlocal enableDelayedExpansion
rem define your variables here ...
for /l %%N in (1 1 20) do set !var%%N!
Or if you have a series of randomly named variables, a simple FOR will do:
setlocal enableDelayedExpansion
rem define your variables here ...
for %%V in (varA varB varC varD) do set !%%V!
update in response to add-on question
I don't see why you are using SHIFT, unless there is other code you haven't shown. I removed the SHIFT.
set var1=unfilter=rem
set var2=mdegrain=rem
set n=0
:parseArgs2
set /a n+=1
if defined var%n% (
call set %%var%n%%%
goto :parseArgs2
)
or
setlocal enableDelayedExpansion
set var1=unfilter=rem
set var2=mdegrain=rem
set n=0
:parseArgs2
set /a n+=1
if defined var%n% (
set !var%n%!
goto :parseArgs2
)
Try this:
set var1=sharp=soothe
for /f "tokens=*" %%a in ("%var1%") do set %%a
echo %sharp%
or just:
for %%a in ("%var1%") do set %%a
Perhaps is this what you want?
#echo off
setlocal EnableDelayedExpansion
set var1=unfilter=rem
set var2=mdegrain=rem
set var10=resize=rem
for /F "tokens=1* delims==" %%a in ('set var') do set %%b
In this method does not matter the number of defined variables nor its order; all defined variables are always processed.
EDIT: Below is the output when an ECHO command is placed before the set %%b:
set unfilter=rem
set resize=rem
set mdegrain=rem

Variables in loops Dont Work, Batch

This is the new Script and it Still Doesn't Work
I Get The syntax of the command is incorrect.
on FOR /F "USEBACKQ tokens=*" %%A IN (TYPE "C:\Windows\System32\tasks\at!num! ^| FIND "Command") DO (
SETLOCAL ENABLEDELAYEDEXPANSION
set num=1
:START
IF NOT EXIST "C:\Windows\System32\tasks\at%num%" (GOTO:EOF)
FOR /F "USEBACKQ tokens=*" %%A IN (`TYPE "C:\Windows\System32\tasks\at!num! ^| FIND "Command"`) DO (
set var=%%A
ECHO %var%
SET /a num=%num%+1
PAUSE
)
GOTO:START
To understand your code, I'm going to break it down into logic first then try to solve it. Let me know if I miss a detail...
Set num var to 0
Begin :Loop
set num var to its current value ::NOT NEEDED - You've specified this prior to the GOTO
increment num var by +1
if myfolder\at* file exists then read at%num% and find a string then output that line to %tmp%\1.txt ::Need quotations on file location.
set F var to the line stored in %tmp%\1.txt
set F="%%F: =%%" ::Please explain what you are trying to do with this command.
set F to start on 10th character and remove the last 11 characters from the line.
echo the variable
If it doesn't exist, exit, but if it does return to :Loop
You should tell us what you are attempting. If it is as simple as saving a variable from a text file output, set F=<file.txt will work. If it didn't, then something happened prior to that command. Still... what is set F="%%F: =%%"?
Unless you are using a FOR loop variable, there is no need to use %% on each end of the variable.
If this were a FOR loop, it would look like this:
SETLOCAL ENABLEDELAYEDEXPANSION
set num=1
:START
IF NOT EXIST "myFolder\at%num%.txt" (GOTO:EOF)
FOR /F "USEBACKQ tokens=*" %%A IN (`TYPE "myFolder\at%num%.txt" ^| FIND /i "string"`) DO (
PAUSE
SET var=%%A
ECHO !var!
PAUSE
SET var=!var: =!
ECHO !var!
PAUSE
SET var=!var:~10,-11!
ECHO !var!
PAUSE
SET /a num=!num!+1
ECHO !num!
PAUSE
)
GOTO:START
One good practice to check if commands are working, such as SET, insert an ECHO on the variable and a PAUSE right after each time you believe the variable should be changed. This will track what has changed on the variable so you can see if your command was correct and the changes were made.
I'd suggest using Batch's inbuilt function for loops, see here.
Conditionally perform a command for a range of numbers
Syntax
FOR /L %%parameter IN (start,step,end) DO command
Or maybe iterating over files in a folder would be better for what you are trying to do?
Loop through files (Recurse subfolders)
Syntax
FOR /R [[drive:]path] %%parameter IN (set) DO command
Or iterating over file contents?
Loop command: against a set of files - conditionally perform
a command against each item.
Syntax
FOR /F ["options"] %%parameter IN (filenameset) DO command
FOR /F ["options"] %%parameter IN ("Text string to process") DO command
This site has plenty of examples here which should point you in the right direction.
There are a few issues with your code, I've amended as follows to get the variable populated with the contents of the temp file.
set num=0
:Loop
set /a num=%num%+1
if exist "myFolder\at*" (
TYPE "myFolder\at%num%" | FINDSTR "\<Command\>" > "%temp%\1.txt"
set /P F=<"%TEMP%\1.txt"
Echo %F%
Pause
)
I don't know if this is the problem, but have you tried enabling:
setlocal enabledelayedexpansion
Then, inside the loop (or the IF(...)), you use !foo! to signify environment variables instead of %foo%.
See setlocal /? and set /? for more information.