Variable refuses to be set to a different value after first declaration - windows

In a for each loop, I'm trying to perform a task on each iteration except the first. This is my attempt
#echo off
set sources=file1.txt file2.txt
set output=output.txt
set comment_prefix=--
break>%output%
setlocal EnableDelayedExpansion
set first=1
for %%a in (%sources%) do (
if %first%==0 (
echo.>> %output%
echo.>> %output%
echo.>> %output%
)
set first=0
echo %first%
echo %comment_prefix%>>%output% %%a
echo.>> %output%
type %%a>>%output%
)
The problem is that the variable first seems to be constantly set to 1, even though it should be set to 0 after the first iteration (I think)...
How can I make the value of first change to zero?
Is there a better way to make a condition to check if the iteration is not the first one?
Cheers
EDIT:
This is the current output
1
1

You are setting and expanding (reading) the variable within the same line or block of code, so you need delayed expansion. Otherwise, %first% will expand to the value the variable was set to at the time the entire line/block is parsed (so the variable is in fact set, but an old value is read). To use delayed expansion, replace %first% by !first!.
However, since you are using the variable as a boolean flag only, you could reflect the boolean False by an empty variable rather than by the value 0, so you could use if not defined first instead of if !first!==0, which delayed expansion is not necessary for:
set "first=1"
for %%a in (%sources%) do (
if not defined first (
echo.>> %output%
echo.>> %output%
echo.>> %output%
)
set "first="
echo %comment_prefix%>>%output% %%a
echo.>> %output%
type %%a>>%output%
)

Related

Delayed variable expansion inside a substring operation

SETLOCAL EnableDelayedExpansion
SET str=123456789abcdefgh
FOR /l %%x IN (1, 1, 10) DO (
SET /a intLength=10-%%x
SET result=!str:~-%%x!
ECHO "Works as intended: " !result!
SET result=!str:~-intLength!
ECHO "Does NOT work as intended: " !result!
)
endlocal
You're using the literal string intLength instead of the %intLength% variable.
Since you're initializing a variable inside of a for loop, you're going to have to use the !intLength! variation of this variable name. Unfortunately, since you're already using exclamation points to get the substring from str, you can't also use them in that line to get the value of intLength, since you'd then essentially have a variable !str:~!, an unrelated string that batch really isn't going to like, and a !!.
You can get around this by running !intLength! through another for loop and using the %%var variable instead, since you've already shown that that works.
#echo off
setlocal EnableDelayedExpansion
set str=123456789abcdefgh
for /l %%x in (1, 1, 10) DO (
set /a intLength=10-%%x
SET result=!str:~-%%x!
echo Works as intended: !result!
for /f %%A in ("!intLength!") do SET result=!str:~-%%A!
echo Now works as intended: !result!
echo.
)
endlocal

goto was not expected at this time batch

#echo off
:start
set string=
set lo=1
set a=0
set b=0
set cl=1
set cloop=
set google=0
set k=0
set r=0
set id=
set t=0
set f=0
set /p string=?
if defined string (
echo %string%
goto loop
) else (
echo please enter a string
goto start
)
:loop
set a=
for /f "tokens=%lo%" %%G IN ("%string%") DO echo %%G
if defined a (
echo %a%
set google=0
set /p cloop=<greetings.txt
pause
:cloop
set b=
for /f "tokens=%cl%" %%g IN ("%cloop%") DO set b=%%g
if defined string (
if %a%==%b% goto greetings
set /a cl=%cl%+1
goto cloop
) else (
set cl=0
set /a lo=%lo%+1
goto loop
)
) else (
goto google
)
:greetings
set f=0
set k=0
set r=0
set /p id=<greetingtone.dat
for /f "tokens=%cl%" %%g IN ("%id%") DO set t=%%g
start greeting.bat
call greeting.bat
goto talk
:google
echo not done yet
pause
goto start
i have narrowed it down to this line
if %a%==%b% goto greetings
when i remove it it runs
i have looked but i have no idea why it does not work
please help the greetings.txt has "hi hello grunt"
i think it might be the variables
If %a% or %b% are empty values, it is likely the compare is incomplete, and it is saying that the goto is not expected yet. For instance, if you type the following at a C:\ prompt:
c:\>if a== echo ok
c:\>if ==a echo ok
echo was unexpected at this time.
c:\>if == echo ok
ok was unexpected at this time.
c:\>
If you enclose each value in quotes, then the comparison will still work even if one or both of the values are empty. For instance:
if "%a%"=="%b%" goto greetings
The normal reason for that an unexpected word in an IF statement is that IF has a very specific syntax, IF item1 operator item2 actionstatement(s).
What is likely to be happening is that item1 AND item2 appear to be missing, so IF resolves that as IF == goto greetings. Since goto is not one of its known operators (==, equ, neq, leq, lss, geq, gtr`) then it complains.
The question from here is - why do %a% and %b% appear to be empty?
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.
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered. In your case, that means the outermost IF - in if defined string.
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.
Next problem is using a label within a block. Not a good idea. On some versions, a label will terminate the block. Call a subroutine instead.
call :cloop
...
goto start
:cloop
(whatever needs to be done)
goto :eof
(note that :cloop and :EOF have a required colon. on cloop it means "this is an internal subroutine - it's in the cuurrent batchfile." :EOF is a predefined label understood by CMD to mean end of file.)

How to loop through array in batch?

I created an array like this:
set sources[0]="\\sources\folder1\"
set sources[1]="\\sources\folder2\"
set sources[2]="\\sources\folder3\"
set sources[3]="\\sources\folder4\"
Now I want to iterate through this array:
for %%s in (%sources%) do echo %%s
It doesn't work! It seems that script is not going into the loop. Why is that? How can I iterate then?
Another Alternative using defined and a loop that doesn't require delayed expansion:
set Arr[0]=apple
set Arr[1]=banana
set Arr[2]=cherry
set Arr[3]=donut
set "x=0"
:SymLoop
if defined Arr[%x%] (
call echo %%Arr[%x%]%%
set /a "x+=1"
GOTO :SymLoop
)
Be sure you use "call echo" as echo won't work unless you have delayedexpansion and use ! instead of %%
If you don't know how many elements the array have (that seems is the case), you may use this method:
for /F "tokens=2 delims==" %%s in ('set sources[') do echo %%s
Note that the elements will be processed in alphabetical order, that is, if you have more than 9 (or 99, etc) elements, the index must have left zero(s) in elements 1..9 (or 1..99, etc.)
If you don't need environment variables, do:
for %%s in ("\\sources\folder1\" "\\sources\folder2\" "\\sources\folder3\" "\\sources\folder4\") do echo %%s
This is one way:
#echo off
set sources[0]="\\sources\folder1\"
set sources[1]="\\sources\folder2\"
set sources[2]="\\sources\folder3\"
set sources[3]="\\sources\folder4\"
for /L %%a in (0,1,3) do call echo %%sources[%%a]%%
For posterity: I just wanted to propose a slight modification on #dss otherwise great answer.
In the current structure the way that the DEFINED check is done causes unexpected output when you assign the value from Arr to a temporary variable inside the loop:
Example:
#echo off
set Arr[0]=apple
set Arr[1]=banana
set Arr[2]=cherry
set Arr[3]=donut
set "x=0"
:SymLoop
if defined Arr[%x%] (
call set VAL=%%Arr[%x%]%%
echo %VAL%
REM do stuff with VAL
set /a "x+=1"
GOTO :SymLoop
)
This actually produces the following incorrect output
donut
apple
banana
cherry
The last element is printed first.
To fix this it is simpler to invert the DEFINED check to have it jump over the loop when we're done with the array instead of executing it. Like so:
#echo off
set Arr[0]=apple
set Arr[1]=banana
set Arr[2]=cherry
set Arr[3]=donut
set "x=0"
:SymLoop
if not defined Arr[%x%] goto :endLoop
call set VAL=echo %%Arr[%x%]%%
echo %VAL%
REM do your stuff VAL
SET /a "x+=1"
GOTO :SymLoop
:endLoop
echo "Done"
This regardless of what you do inside the SymLoop always produces the desired correct output of
apple
banana
cherry
donut
"Done"
i use like this, what is important is that the variable is only 1 length, like %%a, and not like %%repo:
for %%r in ("https://github.com/patrikx3/gitlist" "https://github.com/patrikx3/gitter" "https://github.com/patrikx3/corifeus" "https://github.com/patrikx3/corifeus-builder" "https://github.com/patrikx3/gitlist-workspace" "https://github.com/patrikx3/onenote" "https://github.com/patrikx3/resume-web") do (
echo %%r
git clone --bare %%r
)

Find a substring (filepath) from a string containing variable order arguments

%params% contains a variable set of arguments:
/tidy /log /truncate /convert D:\libdir
or maybe
/log /tidy D:\cyclea\libfolder /test /convert /truncate
for everything but the (currently single) filepath element I use it such:
if "%params%"=="%params:log=%" goto :DontLogit
if NOT "%params%"=="%params:/tidy=%" (call tidysub: & do something else )
Now I want to extract the filepath element and use it as an argument to a command eg chdir
I've played with, but I'm weak with CMD string manipulation and for loops.
I'd like to keep the order of params variable.
For info it comes from here:
FOR %%s IN (%*) DO (set params=!params! %%s)
#ECHO OFF
SETLOCAL
SET swparams=log tidy test convert truncate
FOR %%i IN (%swparams% other) DO SET "%%i="
FOR %%i IN (%*) DO (
SET "used="
FOR %%p IN (%swparams%) DO (IF /i "/%%p"=="%%~i" SET %%p=Y&SET used=Y)
IF NOT DEFINED used CALL SET other=%%other%% "%%~i"
)
ECHO =============paramsreport===========
FOR %%i IN (%swparams%) DO IF DEFINED %%i (ECHO %%i:set) ELSE (ECHO %%i:clear)
ECHO other=%other%
FOR %%i IN (%other%) DO ECHO %%i or %%~i
GOTO :EOF
Here's a way that should be extensible for you.
Simply set you switch-parameters into the list in swparams.
the parameter-names and OTHER are set to [nothing] to ensure they're not already set in the environment.
Ech supplied parameter is applied to %%i in turn, and matched against each defined swparam in turn. the variable USED is cleared before the match and if the match (of /switchparametername is found, the switch parameter is set and the USED flag is set.
if the used flag is not set gainst any of the switch parameters, then a parsing trick is used to accumulate any unrecognised strings into OTHER
The "%%~i" mechanism first dequotes the item in %%i, then quotes it. In this way, it ends up quoted, regardless of whether it originally has quotes or not.
The /i on the if performs a case-insensitive match.
hence running this batch
thisbatch /tidy "C:\some filename with spaces.txt"
will yield TIDY set to Y, LOG,test, convert, truncate not set and other set to "C:\some filename with spaces.txt"
#echo off
setlocal EnableDelayedExpansion
rem Get the single filepath element (with colon in second character):
set params=/tidy /log /truncate /convert D:\libdir
set filepath=
for %%a in (%params%) do (
set par=%%a
if "!par:~1,1!" == ":" (
set filepath=%%a
)
)
if defined filepath (
echo Filepath = %filepath%
) else (
echo Filepath not given
)
echo/
rem Get multiple filepath elements in an *array*:
set params=/log /tidy D:\cyclea\libfolder /test /convert D:\libdir /truncate
set i=0
for %%a in (%params%) do (
set par=%%a
if "!par:~1,1!" == ":" (
set /A i+=1
set filepath[!i!]=%%a
)
)
echo There are %i% filepath elements:
for /L %%i in (1,1,%i%) do (
echo %%i- !filepath[%%i]!
)
You may review a further description on array management at this post: Arrays, linked lists and other data structures in cmd.exe (batch) script

windows batch file array extraction counter not being incremented by +=

I am translating a shell script to windows batch. What I need to do is take all except 1,2 and last from command line arguments. join them and send to another program as argv.
#echo off
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF %i% geq 2 (
set /a c+=1;
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
I want the candidates array be argv[3..n-1]
e.g. If I write batch x 2 a b p it should pass a b to that another program
The problem is loop counter is not being incremented by += operator. If I write echo %1% inside FOR I see 0 always
You should not use for %%A in (%*) as it treats %* as filename set. This may cause problems, especially if you can pass * or ? (wildcard match characters in cmd) in parameters - as they will be expanded to all files satisfying pattern. Second, batch does really know nothing about arrays - a[1] and a[2] are just a shorthand notation for humans - they are two distinct variables.
Given the problem Parse command line, take second parameter as count of parameters to concatenate into a variable, here is my take:
#echo off
setlocal
set subject=%1
shift
set exp_count=%1
if not defined exp_count (
echo Count not specified
exit /b 1
)
set /a "verify=%exp_count%"
if %verify% leq 0 (
echo Count not valid /not a positive integer/
exit /b 2
)
set real_count=0
:loop
shift
if "%~1"=="" goto end_params
set /a real_count+=1
if %real_count% leq %exp_count% set "candidates=%candidates%%~1"
goto loop
)
:end_params
if %real_count% lss %exp_count% (
echo Less parameters passed than specified!
exit /b 3
)
echo %subject%
echo %candidates%
Please note I'm not checking if there is a 'hanging' parameter (the last, not being concatenated) but it should be trivial to add that check. I left it out on purpose to make the code more flexible.
I have two answers for your question:
1- The first problem is that in IF %i% ... command the value of i variable not change (although set /a i+=1 command will correctly increment the variable) and the way to solve it is by including setlocal EnableDelayedExpansion command at beginning and enclose i in percents signs this way: IF !i! ... (as said in previous answers). However, you must note that an array variable in Batch is different than a simple variable with same name (they both can exist at same time), so array elements must always be written with subscripts and there is NO way to process an entire array in a single operation. See this topic for further details.
In your program you must transfer the elements of candidates array into a simple variable, that in the example below have the same name (just to state my point):
#echo off
setlocal EnableDelayedExpansion
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF !i! geq 2 (
set /a c+=1
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
REM Transfer "candidates" array elements into "candidates" simple variable:
set candidates=
FOR /L %%i IN (1,1,%c%) do (
set candidates=!candidates! !candidates[%%i]!
)
REM Show "candidates" simple variable:
echo %candidates%
Note that in Batch files you may insert commas, semicolons and equal-signs as separators instead spaces in most commands. However, SET /A command have other rules at this respect, so the semicolon must be omitted.
2- Independently of the array management explained above, this is the way I would solve your problem using a list instead of an array:
#echo off
SET subject=%1
shift
set count=%1
set candidates=
set lastArg=
set i=0
:nextArg
shift
if "%1" equ "" goto endArgv
set /a i+=1
set candidates=!candidates! !lastArg!
set lastArg=%1
goto nextArg
:endArgv
SET /a count_actual=i-3, count_expected=count
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
Antonio
Yes your code will not increment i. Batch variable replacement occurs when a block is parsed, not when it is executed. The entire for block is parsed once, so %i% is replaced with zero before the for block is executed.
To disable that you need to enable delayed expansion and change your variable escape characters from %'s to !'s to have the replacement made at runtime. Then you will see i incremented in the for loop.
#echo off
Setlocal EnableDelayedExpansion
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF !i! geq 2 (
set /a c+=1
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
You will also need to get rid of the ; at the end of the set /a c+=1; line and I'm not sure what you are trying to do on line set candidates[!c!]=%%A as the brackets don't mean anything in batch.
While there are a bunch of answers already listed, I decided to add one more. My approach is to keep the answer as simple as possible for your specific needs. If you have any questions feel free to ask.
This will create the array as you desired [3,...,n-1] without the need for delayed expansion or fancy logic.
#echo off
:: Get the First Two Parameters
set "subject=%1"
shift
set "count=%1"
shift
:: Loop through the rest
set "index=0"
:NextParam
set "param=%1"
shift
set "next=%1"
:: Skip the last parameter
if not defined next goto EndParam
set "candidates[%index%]=%param%"
set /a "index+=1"
goto NextParam
:EndParam
set "count_actual=%index%"
set "count_expected=%count%"
:: Show the Results
echo %count_actual%
echo %count_expected%
echo %subject%
set candidates
Here is an alternate where the candidates are stored in a space delimited string instead of seperate variables. Replace the space between the %candidates% %param% to whatever delimiter you desire.
#echo off
:: Get the First Two Parameters
set "subject=%1"
shift
set "count=%1"
shift
:: Loop through the rest
set "index=0"
:NextParam
set "param=%1"
shift
set "next=%1"
:: Skip the last parameter
if not defined next goto EndParam
set "candidates=%candidates% %param%"
set /a "index+=1"
goto NextParam
:EndParam
set "count_actual=%index%"
set "count_expected=%count%"
:: Show Results
echo %count_actual%
echo %count_expected%
echo %subject%
echo %candidates%

Resources