Odd behavior IF statement in Batch - batch-file

I have a very simple Batch script that I'm working on
I'm considering implementing an updater with wget and version files locally and hosted on my server (yet I'm using Batch for some reason)
#echo off
for /f "tokens=* delims=" %%x in (version.txt) do set version=%%x
if "%version%" == "1.0.0" (echo yes)
the text file it's referencing contains nothing but
1.0.0
Not even a new line. Echoing %version% gives me 1.0.0 in the console, but the if statement gets nothing.
Anyone wanna point out my error? I'm honestly clueless

You appear to have a naughy space on the end of the set command.
Batch is sensitive to spaces in a SET statement. SET FLAG = N sets a variable named "FLAGSpace" to a value of "SpaceN"
The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned. set /a can safely be used "quoteless".

This is tricky. You have a space at the end of set version=%%x.
As a general point of scripting convention, whenever you set a variable to a string in a batch script, unless you have a specific reason not to do so, you should do set "variable=value" with the var=value pair quoted. That helps avoid problems like this. Your for /f line should end with set "version=%%x"
With the trailing space, you're basically saying if "1.0.0 " == "1.0.0", which is false.
As a side note, it is possible to fetch the content from your web server without using wget if you wish.

Related

Batch to loop through variables and send API request

I am facing an issue to get some data from an API using the command lines in Windows. Basically, this is what I want to do:
1- Get a list of clients from an API (using curl and jq),
2- Save this list in a .txt (I wanted to save it in a variable but did not manage),
3- Loop through the list of clients within the .txt and send a new API request to download a csv specific to this client
The first 2 steps are working fine, but I'm stuck on the last bit to loop through the clients. Here is my code:
setlocal ENABLEDELAYEDEXPANSION
Set tok=XXXXX
Set hURL="https://api.website.com/v2/clients?token=%tok%"
Set IDPath="C:\Users\My Self\subIDs.txt"
Set cuPath=%~dp0
del %IDPath%
curl -sS %hURL% | jq ".clients[].id" > %idPath%
FOR /F %%i in (%IDPath%) do (
echo %%i
Set subID=%%i
Set rURL="https://api.website.com/v2/data?token=%tok%&subscriptionId=!subID!"
curl -sS -o cuPath!subID!.csv !rURL!
)
endlocal
pause
What looks odd to me:
- echo %%i returns: C:\Users\My
It looks like the space within the path is a problem for this loop, but it didn't cause any issue to save the file, so I'm a bit lost
- I am using !subID! as I understand it's the only way to get the variable ajusted each time however when looking at the output of the !rURL! variable, while %tok% is successfuly passed as XXXXX, !subID! remains as !subID!
- I finally get, of course, a curl: (6) Could not resolve host: rURL
I am completely new to APIs, batch or JSON and even though I feel I am getting closer, I am now stuck on this last bit. Any idea how to solve this?
Thanks,
You had some mistakes in your code which is fixed in the following code.
But since you have not specified the exact format that is returned by jq ".clients[].id" I could only guess that it returns a single token without any spaces between the characters, but it may have leading or trailing spaces.
So if this is not the case you have to setup the FOR /F options accordingly.
#echo off
setlocal DisableDelayedExpansion
Set "tok=XXXXX"
Set "hURL=https://api.website.com/v2/clients?token=%tok%"
Set "rURL=https://api.website.com/v2/data?token=%tok%&subscriptionId="
Set "IDPath=C:\Users\My Self\subIDs.txt"
:: No need to delete %IDPath%, It will be overwrriten by the below redirection
curl -sS "%hURL%" | jq ".clients[].id" > "%IDPath%"
FOR /F "usebackq" %%i in ("%IDPath%") do (
echo subID=%%i
curl -sS -o "%~dp0%%i.csv" "%rURL%%%i"
)
REM del "%IDPath%"
endlocal
pause
Some points:
You have assigned the variables with embedded surrounding quotes in their values, while this will work in your particular case, It is a best practice to not embed the quotes in variables values, So instead of Set hURL="https://..." use Set "hURL=https://..." and you can later surround the variable in quotes when actually using them: "%hURL%"
Think of the situation where you want to access the value without quotes, then you have to write additional code to remove the quotes from the value if you assigned the value by set a="value" . But by set "a=value" you can access to both quoted ("%a%" ) and unquoted (%a%) value at the same time.
You have used Set cuPath=%~dp0 without quotes, it should be Set "cuPath=%~dp0" but it is not needed either, you could just use %~dp0 directly in the loop.
And later you've used cuPath in the FOR loop without expanding it and without surrounding the cURL output file in quotes.
When reading a file contents by FOR /F, if the path to the is surrounded in quotes then you have to use the UseBackq option to change the interpretation of quotes in the IN clause of the FOR to not interpret it as literal string.
Based on what you are doing, delayed expansion is not needed at all.
But as side note you have to be more careful when using delayed expansion. when delayed expansion is enabled, any literal string that you may use in your code, including file and url paths, will get corrupted if they contain the ! character.
So It is better setup the required variables and strings while delayed expansion is off and later access them by !var! syntax when delayed expansion is enabled.

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.

set PATH with multiple lines

In a Batch file I need to add some paths to the PATH env variable. Since its a larger numer of long paths I tried to spread them on multiple line and tried to make the bat-file as clean as I can by indenting the lines.
But it seems that the spaces at the beginning of the newline (and so in the %PATH%) are interpreted as part of the actual path.
So
SET PATH=^
\\somewhere\Tools\strawberryperl\perl\site\bin;^
\\somewhere\Tools\strawberryperl\perl\bin;^
\\somewhere\Tools\strawberryperl\c\bin;^
\\somewhere\Tools\KDiff3;^
%PATH%
does not work (programs are not found). Is there some trick I can use?
Because it is a medium complex batch file some indentation would be nice.
for %%x in (
"\\somewhere\Tools\strawberryperl\perl\site\bin;"
"\\somewhere\Tools\strawberryperl\perl\bin;"
"\\somewhere\Tools\strawberryperl\c\bin;"
"\\somewhere\Tools\KDiff3;"
) do call set "path=%%path%%%%~x"
this will append the extra items to the path. You'd need to initialise path to nothing first if all you want is to build the directory sequence specified.
There is no way to have PATH ignore the leading spaces. I see two possible options if you want indented lines:
Option 1 - Use undefined variable to indent, so spaces never get in the value
#echo off
SET PATH=^
% =%\\somewhere\Tools\strawberryperl\perl\site\bin;^
% =%\\somewhere\Tools\strawberryperl\perl\bin;^
% =%\\somewhere\Tools\strawberryperl\c\bin;^
% =%\\somewhere\Tools\KDiff3;^
% =%%PATH%
Option 2 - Remove the spaces afterwards
#echo off
SET PATH=^
\\somewhere\Tools\strawberryperl\perl\site\bin;^
\\somewhere\Tools\strawberryperl\perl\bin;^
\\somewhere\Tools\strawberryperl\c\bin;^
\\somewhere\Tools\KDiff3;^
%PATH%
set "PATH=%PATH:; =%"
First let me start by informing you that adding to the PATH variable in this way is ONLY for the running session. Once the cmd session is closed that variable returns to its previous value.
Here are a suggestion, append each addition one by one:
SET "ToAdd=\\somewhere\Tools\strawberryperl\perl\site\bin;"
SET "ToAdd=%ToAdd%;\\somewhere\Tools\strawberryperl\perl\bin;"
SET "ToAdd=%ToAdd%;\\somewhere\Tools\strawberryperl\c\bin;"
SET "ToAdd=%ToAdd%;\\somewhere\Tools\KDiff3"
SET "PATH=%PATH%;%ToAdd%"
BTW, if you were hoping to add to the environment variable beyond the running session then it is important that you ignore anyone suggesting you use SETX instead of SET. (the variable will be truncated at 1024 bytes therefore corrupting it). Your best solutions would involve editing the registry and possibly using a built in tool such as powershell.
Edit
This shows the method mentioned in my comment and uses the same structure as Magoo's answer:
C:\MyDir\Paths.txt
\\somewhere\Tools\strawberryperl\perl\site\bin
\\somewhere\Tools\strawberryperl\perl\bin
\\somewhere\Tools\strawberryperl\c\bin
\\somewhere\Tools\KDiff3
batch file
#Echo Off
SetLocal EnableDelayedExpansion
For /F "UseBackQDelims=" %%A In ("C:\MyDir\paths.txt") Do Set "Path=!Path!;%%~A"
Echo(%Path%
EndLocal
Timeout -1
This means that you only really need to include the for loop each time instead of adding each of the paths to it.
Not even remotely bulletproof, but the Magoo's answer reminded me this. Just because someone, somewhere, could find a better usage for this construct
#echo off
setlocal enableextensions disabledelayedexpansion
for /f "delims=" %%a in ('echo "%path:;=" "%"
"\\somewhere\Tools\strawberryperl\perl\site\bin"
"\\somewhere\Tools\strawberryperl\perl\bin"
"\\somewhere\Tools\strawberryperl\c\bin"
"\\somewhere\Tools\KDiff3"
""') do (set "path=%%~a") & call set "path=%%path:" "=;%%"
path

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

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)

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.

Resources