Is it possible to get a variable from a string in batch? - batch-file

I've recently started to learn batch for the sake of writing batch sims for a game that I've been playing. I was wondering if its possible to somehow iterate through like named variables (since I can't seem to find anything about a list?). Also I'm not sure if I can put a label to call to as a variable passed.
Code Example:
:: Enemy Fortress level.
SET EFORTLVL=4
:: Don't mess with anything below here only the variables above.
:: Enemy Fortress that will be simmmed against. Note this batch sim is only built to run against one tower, as this is what you should be doing.
SET EFORTRESS1="Foreboding Archway-%EFORTLVL%"
SET EFORTRESS2="Illuminary Blockade-%EFORTLVL%"
SET EFORTRESS3="Tesla Coil-%EFORTLVL%"
SET EFORTRESS4="Minefield-%EFORTLVL%"
SET EFORTRESS5="Forcefield-%EFORTLVL%"
call :sim 1
:sim
SET /a "COUNTER=1"
SETLOCAL enabledelayedexpansion
SET times=!ITERATIONS%1!
ENDLOCAL & SET TIMES=%times%
:whilesim
SETLOCAL enabledelayedexpansion
SET fort=!EFORTRESS%COUNTER%!
ENDLOCAL & SET FORT=%fort%
tuo.exe %DECK0% %ENEMY% surge random -v efort %FORT% yfort %YFORTRESSES% climb %TIMES% >> %PATH%\WarDefClimbData%DECK0%.txt
SET /a "COUNTER=COUNTER+1"
if %COUNTER% leq 5 GOTO :whilesim else GOTO :eof
The result that I get for the line on the console:
RESOLVED:
What I want to do is get a value from a variable that holds a string name that relates to the variable in question. (Ex when the for loop passes 1 I want to get EFORTRESS1 value, 2 I want EFORTRESS2 value etc).
E:\Programs\Tyrant Unleashed Optimizer>tuo.exe oconzer "VetDecks" surge random -v efort EFORTRESS1 yfort "Inspiring Altar #2" climb ITERATIONS1 1>>"e:\Programs\Tyrant Unleashed Optimizer\BatchSimResults"WarDefClimbDataoconzer.txt
Error: unrecognized fortress EFORTRESS1
Now I understand why its saying the error, what I don't understand is why its not getting the value from the string that is contained in FORT.
RESOLVED
Getting an endless loop, where the iteration variable isn't updating.

:sim
SETLOCAL ENABLEDELAYEDEXPANSION
SET "FORT=!EFORTRESS%1!"
ENDLOCAL&SET "fort=%fort%"
SET TIMES=ITERATIONS%2
SET LABEL=%3
The issue is to get the contents of (the contents of a variable), often call "indirection".
This is probably the easiest way. It uses setlocal enabledelayedexpansion which places cmd in delayedexpansion mode, where !var! is evaluated after its contents.
The drawback is that it must be invoked using setlocal, which establishes a local environment. The loacl environment must eventually be closed (you cannot keep opening more) and at that time, all changes to the environment are discarded and it is restored to its state at the point of executing setlocal.
The endlocal&... uses a parsing trick to transfer the changes out of the setlocal/endlocal bracket.
As for the other questions - yes, you can goto a variable (and the contents of the variable need not have a leading colon). It is quite possible to use goto somewhere%1 for instance, and supply %1 as a parameter as you've done. The text somewhere would be simply prepended to the value %1.
BTW - it would appear that you are changing path. This is not a good idea. path is the variable that contains a ;-separated list of directories which are searched for an executable if that executable is invoked and not found in the current directory. Best left well alone. Same goes for tmp and temp (which point to a temporary directory) and date and time and random and cd (the current date, time, a random number and the current directory)

Related

Batch File - How to copy text from file, and then update it to a new value

Each time I open the batch file, I would like it to read the information currently stored in the text file, and then apply that stored information it pulled to calculating a new integer.
I'm trying to figure out how to get a number copied from a text file, stored as a variable, and then updated to a new integer in that text file, say adding 1 to the value.
I've been sifting through information online, and everything seems to point in a different direction.
Here is a test code I've gotten from digging thus-far:
set file="Test.txt"
set /a _Counter =< %file%
echo:%_Counter%
set /a "_Update=%_Counter%+1"
echo:%_Update% >%file%
timeout /t 10
For some reason when I try to get the information for the counter, it doesn't pull any data from the text file, and I'm left with this line output by the batch file.
F:\Users\Test\Documents\JumbledDirectory> set /a _Counter = Directory\Test.txt 0<F:\Users\Test\Documents\Jumbled
The most common answer I've seen is to use something along the lines of
set /p _Counter=< Test.txt
echo %_Counter%
As seen here: Windows batch command(s) to read first line from text file
But upon doing this I've either ended up with
echo:%_Counter%
being completely blank, or it defaults to 0 each time.
Any help would be appreciated as I've sadly been trying to find how to get this simple function for around 6 hours now.
#ECHO Off
SETLOCAL
set "file=q72474185.txt"
set /p _Counter=< "%file%"
echo:%_Counter%
set /a _Update=_Counter+1
echo:%_Update% >"%file%"
TYPE "%file%"
GOTO :EOF
When you use the point-click-and-giggle method of executing a batch, the batch window will close if a syntax-error is found or the script runs to completion. You can put a pause after statements and home in on the error, but better to open a 'command prompt' and run your batch from there so that the window remains open and any (error) messages will be displayed.
The error message would be about missing operand.
I've changed the filename as I track each question I respond to with its own set of data.
Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign a terminal \, Space or " - build pathnames from the elements - counterintuitively, it is likely to make the process easier.
set /a does not expect input from anywhere, it simply performs the calculation and assigns the result. Quotes are not necessary so I've removed them. % are also not required in a set /a but can be required if you are using delayedexpansion.
set /p expects input, so I've used that to read the file. Note that set /a disregards spaces, but set and set /p will include the space before the = in the variablename assigned, and _Counter & _Counter are different variables.
So having the batch in the same directory as the textfile this will work:
REM get input of file as Counter
set /p Counter=<number.txt
REM add a number to Counter and assign it as Counter
set /a "Counter=%Counter%+3
REM empty the file
break>number.txt
REM write Counter in the file
echo %Counter% >> number.txt

Using xcopy or copy for a single file from multiple folders

So in the batch script I'm building I am taking a single file from a folder, copying it over to a destination folder, and renaming it based on the number of times that the script has been looped. Essentially I need to take a file that's named the samething from a bunch of different folders spread across multiple computers at times and copy them into a new folder to work with. I've read up on xcopy and copy as that seemed like the thing to use but I haven't been able to find anything that lets me tell it to only copy over a single named file. I've posted what I have so far for the script below with commented lines for the sections I haven't figured out:
ECHO off
SETLOCAL enabledelayedexpansion
ECHO Note: Your combined permission list cvs can be found in the desktop folder
SET /A #=-1
:start
SET /A #+=1
:again
ECHO Please input the file path to the permissionoutput.txt
SET /p permissionoutputpath=
SET "sourcefolder=%permissionoutputpath%"
SET "destinationfolder=C:\Users\kayla\Desktop\HOLDER-CombinedPermissionsLists"
IF not exist "%sourcefolder%\permissionoutput.txt" Echo file not found&goto again
copy "%sourcefolder%\permissionoutput.txt" "%destinationfolder%\permissionoutput%#%.txt"
ECHO Add another file to combine: y or n?
SET /p addanotherfile=
if %addanotherfile%==y goto :start
UPDATE: Code corrected with answer to be fully functional for use as a reference
SET /A #=-1
:start
SET /A #+=1
:again
ECHO Please input the file path to the permissionoutput.txt
SET /p permissionoutputpath=
SET "sourcefolder=%permissionoutputpath%"
SET "destinationfolder=C:\Users\kayla\Desktop\HOLDER-CombinedPermissionsLists"
IF not exist "%sourcefolder%\permissionoutput.txt" Echo file not found&goto again
copy "%sourcefolder%\permissionoutput.txt" "%destinationfolder%\permissionoutput%#%.txt"
ECHO Add another file to combine: y or n?
SET /p addanotherfile=
if /i "%addanotherfile%"=="y" goto start
# is a legitimate variable-name. It's initialised to -1 then incremented on each loop through :start so the first value it will have when it's used is 0. (If you want to start at 1 just initialise it to 0 instead)
Next - your sets - BUT spaces are significant in a string set command are would be included in the variablename/value assigned if present in the set instruction. "quoting the assignment" ensures any stray trailing spaces on the line are not included in the value assigned.
Well - next, make sure the file exists and if it doesn't, then produce a message and loop back to :again which bypasses the increment of #.
Otherwise, simply copy the file. You're aware of its sourcename, and your destinationname is constructed by including %#% to include the current value of # (all batch variables without exception are strings - the set /a instruction merely converts from string to binary to perform the required calculation, then converts the result back to a string for storage in the environment.)
Finally, interpreting the request to add another file. if /i makes the comparison case-insensitive. Since you have no direct control over the user's response, "quoting each side" ensures the if syntax isn't violated in case the user enters "yup sure 'nuff" or some other unexpected response.
The leading colon is not required in a goto. I prefer to omit it to keep conguity with the call command where no-colon means an external routine will be called and a colon means the routine is in this batch file.

How do you set variables NORMALLY after using setlocal?

I'm trying to make a simple batch file ("javapath.bat") to add the Java compiler to the path when I need it so it won't be on the path all the time. I also want be able to do something like #call javapath.bat in other build scripts so the path can be added automatically when needed. Since those will be run repeatedly during the edit-save-compile-run grind, that means that javapath.bat needs to check if Java is already on the path and not readd it if it is, because apparently Microsoft thinks it's a good idea to let the path variable have lots of silly duplicates.
So to detect if it needs to be added I use setlocal to enable "command extensions" so I can use the environment variable string substitution thing. That ugliness works fine.
Then I use endlocal so I can actually set the enviroment variables without the changes being reverted at the end of the script. That's not working. Or, it certainly stops the variable changes being reverted, but it's not normal: it completely stops them from being visible locally, but they are still visible afterwards.
#echo off
setlocal enableextensions
if "%path:jdk1=%"=="%path%" (
endlocal
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
)
After the above, ANT_HOME and JAVA_HOME are properly set. But the only change to PATH is that "\bin;" has been prepended to it, because none of the variables set during the script seem to be visible until afterwards (so ANT_HOME and JAVA_HOME are blank, and the first change to PATH is forgotten). Therefore, running it twice adds Java to the path okay, and not Ant. I could hardcode the paths twice but this behavior is so bizarre and ridiculous and I've been stuck on it for an hour.
Edit: Adding enabledelayedexpansion had no effect either.
#echo OFF
ECHO starting %PATH%
if "%path:jdk1=%"=="%path%" CALL :addjava
ECHO.
ECHO resulting %PATH%
GOTO :eof
:addjava
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
SET "path=%ANT_HOME%\bin;%JAVA_HOME%\bin;%path%"
GOTO :eof
This is what I'd use - other methods run afoul of the mininterpreted closing-parenthesis problem.
The key to understanding this odd behaviour is history. Batch has always substituted the parse-time value of any %var% into the code, then validated the result and executed if valid. As the language developed, it was necessary to maintain compatibility with existing batches, so you could only ADD new keywords and functionality, not remove or alter functionality.
So, as the capacity to call internal subroutines was added, and cascade instructions on a single line with '&' and allow multi-line instructions for if and for by enclosing the instructions in parentheses were introduced, and the capacity to use spaces and other separator characters in file or directory names was required, the batch language began to have a few little quirks.
It was a really bizarre decision to have the ! to access the run-time value of a variable invoked as a subclause of setlocal - personally, I'd have used a switch like ECHO on/off (ie EXPANSION on/off) but I'm not running the project. In the same way, DATE could have been equipped with a /u switch to return the date in a universal form, but the opportunity was missed (and continues to be missed, 17 years after NT4 and 5 wingenerations later...)
As others have noted, extensions should already be enabled except under rather extraordinary circumstances. All you need is to eliminate your SETLOCAL and restructure your IF a bit so that it exits the script if the PATH is already set.
#echo off
if not "%path:jdk1=%"=="%path%" exit /b
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
If you really need to enable extensions, then
#echo off
setlocal enableExtensions
if not "%path:jdk1=%"=="%path%" exit /b
endlocal
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
If your script has additional work to do, then
#echo off
setlocal enableExtensions
if not "%path:jdk1=%"=="%path%" goto :skip
endlocal
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
:skip
REM carry on with additional code as needed
Everything inside the if block is evaluated in one go. So %ANT_HOME% has no effect after set ANT_HOME, you want delayed expansion you need to type:
setlocal enabledelayedexpansion
if "%path:jdk1=%"=="%path%" (
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
path = !ANT_HOME!;!path!
path = !JAVA_HOME!;!path!
)
:: important trick since they evaluate together %path% is still
:: what is inside local
endlocal & path %path%
path
Otherwise no delayed expansion. Also you need to use on undeleyed call with endlocal to escape the block. remember % variables never delay.

How would I set each line of a text document to separate variables using batch?

How would I set a each line of a text document to separate variables using Batch? I know how to set a variable to the first line of a text document using:
Set /p Variable=<Test.txt
...but I don't know how to read other lines of the file. Lets say for example I had a text document with 3 lines, the first line had 'Apples' written on it, the second had 'Bananas' and the third had 'Pears', and lets say the document was called Fruit.txt. How would I set the variable 'Line_1' to the first line of the document, 'Line_2' to the second line and 'Line_3' to the last line?. Just to keep it simple, lets just say the batch file and Fruit.txt are both in the same folder. I don't want to do this in VBScript, so please only post Batch code. I would have thought that it would be something like:
#Echo off
Set /p Line_1=<Fruit.txt:1
Set /p Line_2=<Fruit.txt:2
Set /p Line_3=<Fruit.txt:3
Echo Fruit 1 is %Line_1%, Fruit 2 is %Line_2% and Fruit 3 is %Line_3%
Pause
Exit
...but quite clearly it isn't. Any help?
EDIT: This is for arbitrary-length files, then. jeb has an answer that solves your particular problem for a known number of lines. I will leave this here, though, as I hate deleting posts I put some time into for explanation :-)
Well, you obviously need some sort of counter. Let's start with 1:
set Counter=1
Then, you need to go line-wise through the file:
for /f %%x in (somefile) do ...
Then store the line in a numbered variable (that's what we have the counter for):
set "Line_!Counter!=%%x"
aaaaand increment the counter:
set /a Counter+=1
And that's it. Add a few more necessary things, you know, the boring stuff that's always needed in such cases (strange statements before and after, block delimiters, etc.), and you're done:
#echo off
setlocal enabledelayedexpansion
set Counter=1
for /f %%x in (somefile) do (
set "Line_!Counter!=%%x"
set /a Counter+=1
)
set /a NumLines=Counter - 1
Echo Fruit 1 is %Line_1%, Fruit 2 is %Line_2% and Fruit 3 is %Line_3%
rem or, for arbitrary file lengths:
for /l %%x in (1,1,%NumLines%) do echo Fruit %%x is !Line_%%x!
Some explanation:
set /p Var=<file will set the variable to the first line of a file, as you noted. That works because set /p will prompt for input and < file will redirect the file into standard input of a command. Thus set /p will interpret the file's contents as the entered input up until the user hits Return (i.e. the file contains a line break). That's why you get the first line. The system would throw the whole file at set /p but since the command only reads the first line and then is done they just get discarded.
The syntax you were proposing there is actually for accessing Alternate Data Streams of files on NTFS, which is somethhing totally different.
<short-detour> However, jeb has a way of reading multiple lines. This works because the block (delimited by parentheses) is a single command (see below) you can redirect a file's contents into. Except that command is comprised of multiple statements, each of which will read a single line and store it away. </short-detour>
Which brings us to for /f which iterates over the contents of a file (or the output of a command) line by line and executes a command or block of commands for each line. We can now read the whole file into as many variables as there are lines. We don't even need to know how many in advance.
You may have noticed the Line_!Counter! in there which uses Counter a little bit differently from how you're used to use environment variables, I guess. This is called delayed expansion and is necessary in some cases due to how cmd parses and executes batch files. Environment variables in a command are expanded to their values upon parsing that command. In this case the whole for /f including the block containing two statements is a single command for cmd. So if we used %Counter% it would be replaced by the value Counter had before the loop (1) and never change while the loop is running (as it is parsed once and run multiple times. Delayed expansion (signaled by using ! instead of % for variable access changes that and expands environment variables just prior to running a command.
This is almost always necessary if you change a variable within a loop and use it within the same loop again. Also this makes it necessary to first enable delayed expansion which is done with the setlocal command and an appropriate argument:
setlocal enabledelayedexpansion
set /a will perform arithmetic. We use it here to increment Counter by one for each line read.
To read multiple lines with set/p you need brackets around the set/p block.
#Echo off
(
Set /p Line_1=
Set /p Line_2=
Set /p Line_3=
) <Fruit.txt
Echo Fruit 1 is %Line_1%, Fruit 2 is %Line_2% and Fruit 3 is %Line_3%

Iterating arrays in a batch file

I am writing a batch file (I asked a question on SU) to iterate over terminal servers searching for a specific user. So, I got the basic start of what I'm trying to do.
Enter a user name
Iterate terminal servers
Display servers where user is found (they can be found on multiple servers now and again depending on how the connection is lost)
Display a menu of options
Iterating terminal servers I have:
for /f "tokens=1" %%Q in ('query termserver') do (set __TermServers.%%Q)
Now, I am getting the error...
Environment variable __TermServers.SERVER1 not defined
...for each of the terminal servers. This is really the only thing in my batch file at this point. Any idea on why this error is occurring? Obviously, the variable is not defined, but I understood the SET command to do just that.
I'm also thinking that in order to continue working on the iteration (each terminal server), I will need to do something like:
:Search
for /f "tokens=1" %%Q in ('query termserver') do (call Process)
goto Break
:Process
for /f "tokens=1" %%U in ('query user %%username%% /server:%%Q') do (set __UserConnection = %%C)
goto Search
However, there are 2 things that bug me about this:
Is the %%Q value still alive when calling Process?
When I goto Search, will the for-loop be starting over?
I'm doing this with the tools I have at my disposal, so as much as I'd like to hear about PowerShell and other ways to do this, it would be futile. I have notepad and that's it.
Note: I would continue this line of questions on SuperUser, except that it seems to be getting more into programming specifics.
Ok, those are quite a few questions/issues/etc. in one :-)
And I still don't quite get where exactly you're headed with that script.
First of all, the syntax for the set command is
set <variable name>=<value>
If you do just
set <variable name>
then it will list all environment variables starting with <variable name>. If there are none, then it will output the error message you're seeing.
If you want to define a variable without actually caring about its value, you still need to provide a value. I usually use 1 for such flags, since it's then more an on/off switch than an actual variable holding a value:
set Foo=1
In your case you probably want something else, though. There are no arrays per se in batch files, you can mimic them by creating a number of variables and holding a count somewhere. I've written about that once before (a little outdated by now, but still valid).
In your case you want to iterate over a number of servers and for each server over a number of users. You can do that with a nested loop:
for /f "tokens=1" %%Q in ('query termserver') do (
for /f "tokens=1" %%U in ('query user ... /server:%%Q' do (
...
)
)
As for your two questions there:
No, the loop variable is only valid inside the loop, not when calling a subroutine. You can pass it to the subroutine, however:
for ... in (...) do call Process %%Q
You can then access it with %1 in the subroutine. Honestly, though, in most cases I think the nested loops are easier to read.
Yes.
Another error (one that will bite you): As mentioned before, the set syntax is
set variable=value
Note that there is no space around the = sign. If there is, then you have a space at the end of the variable name or at the start of the value:
> set foo = bar
> echo %foo%
%foo%
> echo %foo %
bar

Resources