BATCH why does running this FOR loop inside an IF statement not work until I run it once outside of it [duplicate] - batch-file

This question already has an answer here:
Variables are not behaving as expected
(1 answer)
Closed 24 days ago.
I've been racking my head over my script on why the section for finding out the directory size in bytes does not work but after a long time of debugging i've encountered the following scenario:
if I run the for loop inside an IF statement it doesn't work
if I run the for loop outside the IF statement it does work
if I run the for loop outside the IF statement and then run it inside afterwards, it does work
Is the for loop written incorrectly or am I misunderstanding or missing something?
The following does NOT work
#ECHO OFF
set "test=1"
if %test% GTR 0 (
echo Scenario A
pushd "%~dp0"
set dir="C:\temp"
for /f "tokens=3" %%i in ('robocopy /l /e /bytes %dir% %dir% ^| findstr Bytes') do set size=%%i
echo Size = %size%
pause
) else (
echo Scenario B
pause
)
But this DOES work for both runs
#ECHO OFF
set "test=1"
echo Method a
pushd "%~dp0"
set dir="C:\temp""
for /f "tokens=3" %%i in ('robocopy /l /e /bytes %dir% %dir% ^| findstr Bytes') do set size=%%i
echo Size = %size%
echo Now Method A in IF statement
if %test% GTR 0 (
echo Scenario A
pushd "%~dp0"
set dir="C:\temp"
for /f "tokens=3" %%i in ('robocopy /l /e /bytes %dir% %dir% ^| findstr Bytes') do set size=%%i
echo Size = %size%
pause
) else (
echo Scenario B
pause
)

The reason is that variables in if/else statement are expanded before executing it. You are setting dir variable inside it, but all %dir% occurrences are expanded at the same time, before running if/else.
Basically the following command is actually executed. As you can see all variables were expanded:
if 1 GTR 0 (
echo Scenario A
pushd "C:\Workdir\"
set dir="C:\temp"
for /f "tokens=3" %i in ('robocopy /l /e /bytes | findstr Bytes') do set size=%i
echo Size =
pause
) else (
echo Scenario B
pause
)
As an alternative you can use if/goto commands to organize the flow. In this case every command will be expanded individually. It will look like this:
if %test% NEQ 1 goto ScenarioB
rem Scenario A
goto Done
:ScenarioB
rem Scenario B
:Done
Also double quotes are not needed in set test=1.
To troubleshoot such issues you can turn echo on, then you will see the commands being executed.

Related

How do I substring a dynamic variable value in windows batch scripting

Not able to substring the dynamic variable inside forloop in Windows batch script.
I have the properties file in my git hub in the below format.
"collectionName=TestCollectionRun.json=test"
So I have written the below code to fetch this values.But the requirement is that I need to strip of the '.json' part from collection name.With the below code I am not able to set/echo that value.Can you please help on this!
#ECHO ON
:BEGIN
IF EXIST "test.properties" ECHO Found properties file, reading file..
SET props=test.properties
setlocal EnableDelayedExpansion
For /F "delims== tokens=1,2,3" %%G in (%props%) Do (
if "%%I" EQU "test" if "%%G" EQU "collectionName" SET collName=%%H(
SET finalCollName=%collName%:~0,-5
ECHO %finalCollName%
)
)
:END
We need the ECHO to return "TestCollectionRun".currently its not returning anything.
For /F "delims== tokens=1,2,3" %%G in (%props%) Do (
if "%%I" EQU "test" if "%%G" EQU "collectionName" SET "collName=%%~nH"&echo %%~nH
)
ECHO %CollName%
Note the second ) is now redundant. Your problem has to do with delayedexpansion which you are invoking but not using. call %%collname%% within the for loop would have shown the value after assignment if required.
This code works by interpreting %%H as a filename and assigning simply the name part of %%H (%%~nH)
Given a line content of collectionName=TestCollectionRun.json=test, here's a quick rewrite of what I think you're tring to do:
#Echo Off
Set "props=test.properties"
If Not Exist "%props%" (
Echo Properties file not found!
Echo Exiting..
Timeout /T 3 /NoBreak >NUL
Exit /B
)
Echo Found properties file, reading file..
For /F "UseBackQ Tokens=1-3 Delims==" %%A In ("%props%") Do (
If /I "%%C" == "test" If /I "%%A" == "collectionName" Echo %%~nB
)
Pause
If you wanted to do something with the collection name within the loop then you would probably need to use delayed expansion:
#Echo Off
SetLocal DisableDelayedExpansion
Set "props=test.properties"
If Not Exist "%props%" (
Echo Properties file not found!
Echo Exiting..
Timeout /T 3 /NoBreak >NUL
Exit /B
)
Echo Found properties file, reading file..
For /F "UseBackQ Tokens=1-3 Delims==" %%A In ("%props%") Do (
If /I "%%C" == "test" If /I "%%A" == "collectionName" (
Set "collName=%%B"
SetLocalEnableDelayedExpansion
Echo !collName!
Rem Perform substring task on the variable named collName
Set "finalCollName=!collName%:~0,-5!"
Echo !finalCollName!
EndLocal
)
)
Pause
Note, these answers will not work, as is, if your string is surrounded by doublequotes, (as in your question body), or if the line content differs (e.g. begins with spaces or tabs).
[Edit /]
Looking at your 'after the fact' question in the comments, it is clear that you do not need to substring the variable at all, so should use the first method posted:
Echo Found properties file, reading file..
For /F "UseBackQ Tokens=1-3 Delims==" %%A In ("%props%") Do (
If /I "%%C" == "test" If /I "%%A" == "collectionName" (
newman run "%%B" -e "%envName%" --insecure --reporters cli,htmlextra --reporter-htmlextra-export "newman\%BUILD_NUMBER%\%%~nB.html" --disable-unicode
)
)
Pause
This assumes that both %envName% and %BUILD_NUMBER% have been previously defined correctly.

Variables not incrementing within a batch file

So I am trying to create a file that will read a text file called saves. In saves I want it to check up to 5 time for 1:, 2:, 3: ,4:, and 5:. The issue is that when I run this for loop to check the file 5 times it always outputs the same thing and no matter what will say there are no saves. Any help or advice is much appreciated.
#ECHO off
SETLOCAL EnableDelayedExpansion
SET test=0
FOR /L %%i IN (1,1,5) DO (
FINDSTR %%i: Saves.txt >NUL && SET /a %test%+1
ECHO %test%
)
IF %test% EQU 0 ECHO There are no saves. & GOTO :end
IF %test% GTR 0 ECHO There are saves.
:END
PAUSE
You are not using the SET command correctly and you need to use delayed expansion to display the variable correctly when inside a parenthesized code block.
#ECHO off
SETLOCAL EnableDelayedExpansion
SET test=0
FOR /L %%i IN (1,1,5) DO (
FINDSTR /B %%i: Saves.txt >NUL && SET /a test+=1
ECHO !test!
)
IF %test% EQU 0 ECHO There are no saves. & GOTO :end
IF %test% GTR 0 ECHO There are saves.
:END
PAUSE
An easier option would be.
FINDSTR /B "1: 2: 3: 4: 5:" Saves.txt >NUL 2>&1 && (ECHO There are saves.) || (ECHO There are no saves.)
If all your lines begin with a number and colon then you can just do this.
FOR /F "tokens=1 delims=:" %%G IN (saves.txt) do set numsaves=%%G

Trouble with using For variables within for loop Batch

I am having trouble with a bit of code, I don't really know how to describe it
but I can explain what doesn't work
FOR /D /r "%cd%\files\" %%G in ("*") DO (
echo In folder: %%~nxG
set /a count=1
echo %%~fG
For /R "%%~fG" %%B in ("*.mp3") do (
call :subroutine "%%~nB"
) & echo. >>%archive%.txt
)
just if you want to know what the subroutine does:
:subroutine
echo %count%:%1>>%archive%.txt
echo %count%: %1
set /a count+=1
GOTO :eof
I figured out that it doesn't read the %%~fG inside the second for loop.
Can someone please help me.
I am using SETLOCAL EnableDelayedExpansion
Thank you in advance.
Unfortunately you'll need another subroutine as for options are parsed before outer for tokens. Check the following example:
#echo off
echo :::ATTEMPT 1:::
for %%a in (z) do (
rem the expected delimiter is z and result should be ++::%%a
for /f "delims=%%a tokens=1,2" %%A in ("++z%%%%az--") do echo %%A::%%B
)
echo :::ATTEMPT 2:::
for %%a in (z) do (
call :subr "%%~a"
)
exit /b
:subr
rem the expected delimiter is z and result should be ++::%%a
for /f "delims=%~1 tokens=1,2" %%A in ("++z%%%%az--") do echo %%A::%%B
the output is:
:::ATTEMPT 1:::
++z::zz--
:::ATTEMPT 2:::
++::%%a
As you can see in the first attempt the %%a symbols are taken as delimiters. But subroutine arguments are parsed imminently so they can be used.
To make your code work you can try with:
FOR /D /r "%cd%\files\" %%G in ("*") DO (
echo In folder: %%~nxG
set /a count=1
echo %%~fG
call ::innerFor "%%~fG"
)
...
exit /b %errorlevel%
:innerFor
For /R "%~1" %%B in ("*.mp3") do (
call :subroutine "%%~nB"
) & echo. >>%archive%.txt
For /R "%%~fG" %%B in ("*.mp3") do (
Sadly, for/r can't be run with a variable as the dirname.
I'd suggest
call :anothersubroutine "%%~fG"
and
:anothersubroutine
For /R "%~1" %%B in ("*.mp3") do (
but I've not tried it. Perhaps you'd need to set %%~fG into a variable and use %var% (not tried that either...)

Batch File String Management in FOR Loop

Am trying to use substring manipulation within a FOR loop and I can't get it to work for love nor money. I've been told that we can't use substring manipulation on a loop variable (%%f etc), so you have to set another variable to equal the loop (set MyVariable=%%f) and then use a substring option to work on this decendent (set MyOtherVar=%MyVariable:~0,-3%). However when echoing these variables only the loop variable is set/non-null.
Here's the code I'm currently using.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET OutPref="373137#"
SET OutSuf1="#Window Clean POD "
SET OutSuf2="#1520755.pdf"
SET MatchStr=#mydomain.com
cd c:\Oscar\Scripts\FileNamer\Inbound\
for /f "tokens=*" %%f in ('dir /b *.pdf') do (
c:\Oscar\Scripts\FileNamer\pdftotext.exe -q %%f
set InPdfVer=%%f
set InTxtVer=%InPdfVer:~0,-3%txt
echo Loop Val= %%f
echo InPdfVer= %InPdfVer%
echo InTxtVer= %InTxtVer%
pause
set InAddLine=findstr %MatchStr% %InTxtVer%
set stemp=%InAddLine%
set pos=0
:loop
set /a pos+=1
echo %stemp%|findstr /b /c:"%MatchStr%" >NUL
if errorlevel 1 (
set stemp=%stemp:~1%
if defined stemp GOTO loop
set pos=0
)
set /a pos-=3
call set StoreNo=%InAddLine:~%pos%,-25%
call:getvalue C:\Oscar\Scripts\FileNamer\StoreList.inf %StoreNum% StoreName
set OutFile=%OutPerf%%StoreNo%%OutSuf1%%StoreName%%OutSuf2%
move %%f c:\Oscar\Scripts\FileNamer\Outbound\%OutFile%
)
cd c:\Oscar\Scripts\FileNamer\
exit 0
:getvalue
rem This function reads a value from a file and stored it in a variable
rem %1 = name of file to search in
rem %2 = search term to look for
rem %3 = variable to place search result
FOR /F "tokens=1,2* delims==" %%i in ('findstr /b /l /i %~2= %1') DO set %~3=%%~j
goto:eof
Hope this makes sense, can try and explain it. Quite likely the bottom part doesnt work either but didnt get that far!
Thanks for any thoughts either way, as a general overview the script is supposed to take PDF files in the incoming folder, convert them to text, search for an email address in that file, look that email address in an external list and then move the PDF file (renaming the file with an aggreed convention in the process) and then move onto the next file, in a loop, until the end of the matching files.
Kind regards,
Oscar
OK so the rest of it seems to what what it should now but I still can't get this substring to set, I just end up with the whole string in the decendent variable. Here's the new code (please excuse the pauses and echos used for troubleshooting).
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET OutPref=373137#
SET OutSuf1=#Window Clean POD
SET OutSuf2=#1520755.pdf
SET MatchStr=#mydomain.com
cd c:\Oscar\Scripts\FileNamer\Inbound\
for /f "tokens=*" %%f in ('dir /b *.pdf') do (
c:\Oscar\Scripts\FileNamer\pdftotext.exe -q %%f
set InPdfVer=%%f
call set InTxtVer=!InPdfVer:~0,-3!txt
for /f "tokens=*" %%x in ('findstr !MatchStr! !InTxtVer!') do set InAddLine=%%x
call:getpos
echo !pos!
pause
call set StoreNo=!InAddLine:~!pos!,-25!
call:getvalue C:\Oscar\Scripts\FileNamer\StoreList.inf !StoreNum! StoreName
echo OutPerf !OutPref!
echo StoreNo !StoreNo!
echo OutSuf1 !OutSuf1!
echo StoreName !StoreName!
echo Outsuf2 !OutSuf2!
set OutFile=!OutPerf!!StoreNo!!OutSuf1!!StoreName!!OutSuf2!
echo %%f !OutFile!
pause
REM move %%f c:\Oscar\Scripts\FileNamer\Outbound\!OutFile!
)
cd c:\Oscar\Scripts\FileNamer\
exit /b
:getpos
set stemp=!InAddLine!
set pos=0
:loop
set /a pos+=1
echo !stemp!|findstr /b /c:"!MatchStr!" >NUL
if errorlevel 1 (
set stemp=!stemp:~1!
if defined stemp GOTO loop
set pos=0
)
set /a pos-=3
goto:eof
:getvalue
rem This function reads a value from a file and stored it in a variable
rem %1 = name of file to search in
rem %2 = search term to look for
rem %3 = variable to place search result
FOR /F "tokens=1,2* delims==" %%i in ('findstr /b /l /i %~2= %1') DO set %~3=%%~j
goto:eof
Thanks all for your inputs, here's the finished script with a few more updates.
Makes use of pdftotext.exe as part of the freeware xpdf suite (please donate as it's a great utility) and in this case some lookup files that help resolve a site number to its description. In the format of
001=My Town
I totally failed to get a workable way to pad 2 digit site codes with a leading 0 to make all sites 3 digits but ended up doing the same thing with another lookup file!
I hope this is of some use to someone else!
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET OutPref=373137#
SET OutSuf1=#My Text
SET OutSuf2=#1520755.pdf
SET MatchStr=#mydomain.com
SET BaseDir=C:\myfolder\
SET LogFile=C:\myfolder\FileNamer.log
SET InFolder=C:\myfolder\Inbound\
SET OutFolder=C:\myfolder\Outbound\
SET StoreList=C:\myfolder\StoreList.inf
SET StoreNoConv=C:\myfolder\StoreNoConv.inf
echo Starting Run %TIME% %DATE% >> %LogFile%
echo Starting Run %TIME% %DATE%
cd %InFolder%
for /f "tokens=*" %%f in ('dir /b *.pdf') do (
%BaseDir%pdftotext.exe -q %%f
set "InTxtVer=%%~nf.txt"
for /f "tokens=*" %%x in ('findstr !MatchStr! !InTxtVer!') do set InAddLine=%%x
call:getpos
call set StoreNo=%%InAddLine:~!pos!,-25%%
echo Now Renaming Store No !StoreNo!
call:getvalue %StoreList% !StoreNo! StoreName
call:getvalue %StoreNoConv% !StoreNo! ThreeDigitNo
set OutFile=!OutPref!Store!ThreeDigitNo!!OutSuf1!!StoreName!!OutSuf2!
echo %%f moved to !OutFile! >> %LogFile%
move "%%f" "%OutFolder%!OutFile!" >> %LogFile%
del !InTxtVer!
)
cd %BaseDir%
exit /b
:getpos
set stemp=!InAddLine!
set pos=0
:loop
set /a pos+=1
echo !stemp!|findstr /b /c:"!MatchStr!" >NUL
if errorlevel 1 (
set stemp=!stemp:~1!
if defined stemp GOTO loop
set pos=0
)
set /a pos-=4
goto:eof
:getvalue
rem This function reads a value from a file and stores it in a variable
rem %1 = name of file to search in
rem %2 = search term to look for
rem %3 = variable to set search result under
FOR /F "tokens=1,2* delims==" %%i in ('findstr /b /l /i %~2= %1') DO set %~3=%%~j
goto:eof

Batch Script Substring from string

Ok, I have been playing around with this for a while and am getting no where. I need to pull the KB number out from a line.
The issue i am having is that some of the KB numbers are 6 characters and some are 7, and can't seem to find a way that will work to error detect the two.
The Two types of errors this makes is as follows
The First one should only have been displayed 6 characts so it added the extra "-" at the end.
x64 KB890830- 2012\MS12-000\WinSec-KB890830-006-P58310-Windows-KB890830-x64-V4.9.exe
While the second error shows the random "_" because it uses the first KB shown not the second.
ia64 KB_942288 2012\MS12-000\WinSec-KB_942288-007-P58312-WindowsServer2003-KB942288-v4-ia64.exe
EDIT
Batch File So Far
#ECHO OFF
SETLOCAL enableDelayedExpansion
IF EXIST Export.csv DEL Export.csv
FOR /F "tokens=*" %%I in ('dir /s /b *.*') DO CALl:Generate "%%I"
pause
:Generate
SETLOCAL
IF "%~x1" NEQ ".exe" (
If "%~x3" NEQ ".msu" (
GOTO:EOF
)
)
CALL:FindArchitecture %1
CALL:FindKB %1
CALL:PathFix %1
ECHO %Architecture%,%KB%,%FilePath%>>Export.csv
CALL:Cleanup
ENDLOCAL
GOTO:EOF
:FindArchitecture
ECHO %1 | FINDSTR "x64"
IF "%ERRORLEVEL%"=="0" (
SET Architecture=x64
SET Count+=1
)
ECHO %1 | FINDSTR "x86"
IF "%ERRORLEVEL%"=="0" (
SET Architecture=x86
SET Count+=1
)
ECHO %1 | FINDSTR "ia64"
IF "%ERRORLEVEL%"=="0" (
SET Architecture=ia64
SET Count+=1
)
IF "%Count%" GTR "1" (
SET Architecture=Error
)
SET Count=0
GOTO:EOF
:FindKB
set KBNum="%~1"
set "KBNum=!KBNum:*-KB=!"
ECHO !KBNum!|findstr /I /E /R /c:"-KB[0-9][0-9][0-9][0-9][0-9][0-9][0-9]" >nul
SET "KB=KB!KBNum:~0,7!"
IF "%KB%" NEQ "" GOTO:EOF
ECHO !KBNum!|findstr /I /E /r /c:"-KB[0-9][0-9][0-9][0-9][0-9][0-9]" >nul
SET "KB=KB!KBNum:~0,6!"
GOTO:EOF
:PathFix
set Path_to_convert=%~1
set Reference_path=%~dp0
set FilePath=!Path_to_convert:*%Reference_path%=!
GOTO:EOF
:Cleanup
SET KBNum=
SET KB=
SET Count=
SET Architecture=
set InstallerPath=
set PathRemoval=
set Path=
GOTO:EOF
OK - siginificant edit after seeing comments from Ken White and the OP.
I'm not sure if you need this, but FINDSTR with a regular expression can validate that the line has the pattern: "-KB" followed by 7 digits, followed by "-".
echo somestring|findstr /r /c:"-KB[0-9][0-9][0-9][0-9][0-9][0-9][0-9]-" >nul
Then use substitution to remove everything from the beginning through "KB-".
Then use FINDSTR to verify that the first 7 remaining characters are digits. If not, then loop back and replace up to the next "-KB", etc.
Then you just need to take the first remaining 7 characters.
#echo off
:parseVal
setlocal enableDelayedExpansion
set val="%~1"
echo !val!|findstr /r /c:"-KB[0-9][0-9][0-9][0-9][0-9][0-9][0-9]-" >nul || (
echo Invalid format - KB value not found
exit /b
)
:parseVal2
set "val=!val:*-KB=!"
echo !val!|findstr /brc:"[0-9][0-9][0-9][0-9][0-9][0-9][0-9]-" >nul || goto :parseVal2
set "val=KB!val:~0,7!"
echo val=!val!
exit /b
EDIT
I'm not sure what you did to accept the 6 digit numbers, but the following regex expressions will work with numbers of any length.
For the 1st regex: findstr /rc:"-KB[0-9]*-"
For the 2nd regex: findstr /brc:"[0-9]*-"
Then you can use either of the following to extract out the number when you don't know the length:
for /f "delims=-" %%A in ("!val!") do set "val=KB%%A"
or
set val=KB%val:-=&REM %"

Resources