Windows batch: delayed expansion in a for loop - windows

I want to modify a few specific lines of numbers of text files, and I wrote a batch file as follows:
#echo off
set n=0
set n1=10
set n2=40
cd.>output.txt
for /f "delims=" %%i in ('findstr /n .* test.txt') do (
set "var=%%i"
setlocal enabledelayedexpansion
set /a n=!n!+1
echo.!n!
set var=!var:*:=!
rem if !n!=%n1% ...
rem if !n!=%n2% ...
(echo.!var!)>>output.txt
endlocal
)
start output.txt
However, this doesn't work as expected.
After some tests, I think the !n! expansion is not normally delayed. That is very strange, because !var! expansion is normally delayed.
By the way, the setlocal enabledelayedexpansion and endlocal commands are put in the for loop, because otherwise the special character ! will be abandoned.

I suppose the problem what you see is that n will never increase.
But that isn't a problem of the delayed expansion, it's an effefct of the setlocal/endlocal block inside the loop.
As #panda-34 mentioned you should use the extended syntax of set/a and move the statement outside the setlocal/endlocal block.
#echo off
set n=0
set n1=10
set n2=40
(
for /f "delims=" %%i in ('findstr /n .* test.txt') do (
set "var=%%i"
set /a n+=1
setlocal enabledelayedexpansion
echo !n!
set var=!var:*:=!
rem if !n!=%n1% ...
rem if !n!=%n2% ...
(echo(!var!)
endlocal
)
) >output.txt
start output.txt

Related

Set An Unknown Number of Variables That areParsed From Each Line of a Text File

Apologies for my poorly worded question and my scatterbrain workings. Essentially I want to set an unknown number of variables that are parsed from each line of a text file.
I have written batch file to create symbolic links for network shares to a C:\Volumes folder.
#echo off
echo:
set /p dest=ENTER FOLDER PATH:
set dest="%dest%"
net use %dest%
if not exist "C:\Volumes" MD "C:\Volumes"
for %%i in (%dest%) do (set "fold=%%~ni")
mklink /d "c:\VOLUMES\%fold%" "%dest%"
pause
What I want to try is the same theory but have the script point at a text file mounts.txt with a list of folder paths and for a for loop to cycle through the list make a symbolic link for each path in the list. I have toyed with counters and cannot get it working correctly. I don't think I am going about it the right way at all.
Contents of mounts.txt
\\10.19.10.238\Masters\Removed bin\Work here
\\10.19.10.241\Scanning\WIP\to process
This does not work:
#echo off
setlocal enableDelayedExpansion
set i=1
:add
Set /a "i+=1"
for /F "tokens=*" %%A in (mounts.txt) do (set dest%i%=%%A)
if exist %dest%%i% goto:add
echo %dest%
echo %dest%%i%
echo !dest!
echo !dest!%i%
pause
Nor this:
#echo off
setlocal enableDelayedExpansion
set i=0
For /F "Tokens=1* Delims=] EOL=" %%A In ('Find /N /V ""^<"mounts.txt"') Do (
set /a i=i+1
set "dest!i!=%%B"
)
For /l %%a in (1,1,4) do echo _dest%%a is !dest%%a!
For /l %%a in (1,1,4) do set dest%%a=!dest%%a!
echo !dest!
pause
I did get something like this to work to an extent but cannot figure out how to use the dest[1], dest[2] as variables in other processes further down in the script.
#echo off
setlocal enabledelayedexpansion
set counter=0
for /f "tokens=*" %%a In (mounts.txt) do (
set /a counter+=1
set "dest[!counter!]=%%a"
)
set dest[
And the list could be added to with many more. If %dest%n variables can be set, the use the same theory to set different %fold% variables based on each %dest%n then maybe the links can be set using the same process as the original script.
Any help is appreciated. Thank you.
If your question actually describes what you want to do, you don't need to set variables. Just use the lines from the text file with a for /f loop:
for /f "delims=" %%i in (mount.txt) do mklink /d "C:\VOLUMES\%%~ni" "%%i"
It's safer to quote the filename, especially if you want/need to use the FQFN. Therefore you need the usebackq option, else a quoted string will be processed as a string, not a filename:
for /f "usebackq delims=" %%i in ("C:\full path\mount.txt") do mklink /d "C:\VOLUMES\%%~ni" "%%i"
You seemingly want to dynamically set the variables as dummy array's and return results based on the line numbers. If so, use the counter you created to become the max of the for /L loop, here is an example by just echoing the results:
#echo off & set cnt=0
setlocal enabledelayedexpansion
for /F "usebackq delims=" %%i in ("mounts.txt") do (
set /a cnt+=1
set "var[!cnt!]=%%i"
)
for /L %%a in (1,1,!cnt!) do echo !var[%%a]!
Edit
As highlighted by #Compo in the comments; after the initial code block where we increment the %cnt% variable, we no longer require using delayedexpansion on the %cnt% variable specifically and can therefore use %cnt% instead of !cnt!:
#echo off & set cnt=0
setlocal enabledelayedexpansion
for /F "usebackq delims=" %%i in ("mounts.txt") do (
set /a cnt+=1
set "var[!cnt!]=%%i"
)
for /L %%a in (1,1,%cnt%) do echo !var[%%a]!
This is a courtesy example, just to show you how you could have still used find.exe, as in one of your examples, and additionally includes a different method of iterating the variables with unknown increments:
#Echo Off
SetLocal EnableExtensions
Rem The next line ensures that there are no variables defined with names beginning _
For /F "Delims==" %%G In ('"(Set _) 2>NUL"') Do Set "%%G="
Rem The next lines parse the directory paths content file and defines the incrementing variables
For /F "Tokens=1,* Delims=]" %%G In (
'"%SystemRoot%\System32\find.exe /N "\" 0< "C:\Users\roar\My Directory\mounts.txt" 2>NUL"'
) Do (
Set "_Path%%G]=%%H"
Set "_Name%%G]=%%~nxH"
)
Rem The next lines iterate the defined variables beginning _Path[ and runs commands against them
For /F "Tokens=2 Delims=[]" %%G In ('"(Set _Path[) 2>NUL"') Do (
SetLocal EnableDelayedExpansion
MkLink /D "C:\Volumes\!_Name[%%G]!" "!_Path[%%G]!"
EndLocal
)
Rem The next line undefines any previously defined variables named beginning with _
For /F "Delims==" %%G In ('"(Set _) 2>NUL"') Do Set "%%G="
Please note that is likely you may need to run this script elevated, in order for the MkLink command to create the symbolic links.

Variable substring Edit/Replace inside a for loop

I want to replace a variable's substring, previously stored on a variable inside a for loop, I tried to do it like this but it didn't work:
setlocal EnableDelayedExpansion
set checkVar=abcd
FOR %%Y IN (*.pdf) DO (
SET meu=%%Y
CALL SET meuWithoutChar=!meu:%%%checkVar%%%=!
ECHO meuWithoutChar=!meuWithoutChar!
)
For example here if %%Y==blepabcdnnnn.pdf; I want to have meuWithoutChar=blepnnnn.pdf on the output
Thank you in advance
You are bit confused on the concept of delayed expansion and the use of CALL to get an extra phase of expansion. Here are the examples. I am just using your single file example. You can change it back to using the wildcard.
CALL example
#echo off
set checkVar=abcd
FOR %%Y IN (blepabcdnnnn.pdf) DO (
SET "meu=%%Y"
CALL SET "meuWithoutChar=%%meu:%checkVar%=%%"
CALL ECHO meuWithoutChar=%%meuWithoutChar%%
)
pause
Delayed Expansion
#echo off
setlocal Enabledelayedexpansion
set checkVar=abcd
FOR %%Y IN (blepabcdnnnn.pdf) DO (
SET "meu=%%Y"
SET "meuWithoutChar=!meu:%checkVar%=!"
ECHO meuWithoutChar=!meuWithoutChar!
)
pause
As a supplement/extension to Squashmans answer.Only cycles through necessary files and ignores the file's extension.
Without delayed expansion:
#Echo Off
SetLocal DisableDelayedExpansion
Set "strChr=abcd"
For %%A In ("*%strChr%*.pdf") Do (Set "objFileName=%%~nA"
Call Set "objNewFile=%%objFileName:%strChr%=%%%%~xA"
Call Echo %%%%objNewFile%%%%=%%objNewFile%%)
Pause
With full script delayed expansion, (will have issues with filenames containing !'s):
#Echo Off
SetLocal EnableDelayedExpansion
Set "strChr=abcd"
For %%A In ("*%strChr%*.pdf") Do (Set "objFileName=%%~nA"
Set "objNewFile=!objFileName:%strChr%=!%%~xA"
Echo %%objNewFile%%=!objNewFile!)
Pause
With toggled delayed expansion, (protects filenames containing !'s):
#Echo Off
SetLocal DisableDelayedExpansion
Set "strChr=abcd"
For %%A In ("*%strChr%*.pdf") Do (Set "objFileName=%%~nA"
SetLocal EnableDelayedExpansion
Set "objNewFile=!objFileName:%strChr%=!%%~xA"
Echo %%objNewFile%%=!objNewFile!
EndLocal)
Pause

Batch to Print specific lines of text to a file

Let me start off by saying I am very new to this, and what little code I have cobbled together I found on this site.
In the end I need a batch that when ran will grab each folder name in a parent dir. and copy it to a text file named label1, label2, ect.
I started with pulling the lines from a directory list in a text file. I can get it to echo the last line to a file using Seth's code from this post
Windows Batch file to echo a specific line number
I made some modifications to try to put it in a loop and now I get nothing out.
If anyone can help me it would be much appreciated. Here is my code so far.
set /a "x=1"
set /a "lines=91"
:while1
if %x% leq %lines% (
for /f "tokens=*" %%a in ('findstr /n .* "Y:\Test\foldernametest.txt"') do (
set "FullLine=%%a"
for /f "tokens=1* delims=:" %%b in ("%%a") do (
setlocal enabledelayedexpansion
set "LineData=!FullLine:*:=!"
if "%%b" equ "%1" echo(!LineData!
echo title=!linedata! > Lable%x%.dat
set /a "x= x+1"
endlocal
goto :while1
)
)
setlocal enabledelayedexpansion
set /a x=1
set /a lines=91
:while1
if %x% leq %lines% (
for /f "tokens=*" %%a in ('findstr /n .* "Y:\Test\foldernametest.txt"') do (
for /f "tokens=1* delims=:" %%b in ("%%a") do (
if "%%b" equ "%x%" (
echo(%%c
echo title=%%c > Lable%x%.dat
set /a x= x+1
goto while1
)
)
)
endlocal
I'm reasonably sure this will work, as would
setlocal enabledelayedexpansion
set /a x=1
set /a lines=91
:while1
if %x% leq %lines% (
for /f "tokens=1* delims=:" %%a in ('findstr /n .* "Y:\Test\foldernametest.txt"') do if %%a==%x% (
echo(%%b
echo title=%%b > Lable%x%.dat
set /a x= x+1
goto while1
)
)
endlocal
The issue with your code is that endlocal terminates a setlocal and all of the environment changes that have taken place since the setlocal are backed out - the environment is restored to what it was when the setlocal was executed.
The consequence is that with your code, you are incrementing x (a grand name for a variable) and then the increment is backed out when the endlocal is executed.
So - put the entire routine in a setlocal/endlocal bracket. This has other advanteages - like if you execute a setlocal immediately after #echo off, then when the routine terminates, the environment is returned to its original state - it does not accumulate changes (normally additions of variables) as more and more batches are run.
Some of the other changes I've made are cosmetic. the quotes in a set /a are superfluous and so is the colon in a goto (with the sole exception of goto :eof)
Another problem you have was %1 (meaning "the first parameter to the routine") where you probably meant "%x%".
In the first code fragment, the output of the findstr is assigned to %%a and the inner for assigns that part of the findstr before the delimiter to %%b and that after to %%c. You evidently want to pick the line %%b equal to %x% so the code makes the comparison and if equal, outputs %%c (rest of line) and title=%%c to the file made from Lable and the line number. (You've spelled label incorrectly); then increments x and tries again.
The second piece of code is a simplification of the first. The line is read from the file and numbered, then split directly on the colon; %%a gets the number, %%b the rest of the line, so if %%a is the same as the number %x% then we want to do something (no quotes required, since %%a is a simple numeric string and x will also be numeric because it's never assigned to a string containing separators or empty).
The thing-to-be-done is to echo the line from the file (in %%b, bump the line number and start again...

Find and replace string with a portion of the filename in multiple files within a folder using windows Batch script

I have a batch file to Find and replace text string in a file with a portion of the its own file-name in multiple files within a folder using windows Batch script but it does not work and simply replace YYY with null or nothing. Any help appreciated. thank you
#echo off
SETLOCAL
SET stringtofindreplace=YYY
for %%f in (*.fmw) do (
#echo Processing %%f...
fOR /F "delims=" %%l IN (%%f) DO (
SET "line=%%l"
SET fname=%%~nf
SET fname=!fname:~6,3!
SETLOCAL ENABLEDELAYEDEXPANSION
set "x=!line:%stringtofindreplace%=%fname%!"
echo(!x!
ENDLOCAL)
)>%%~nf.txt
)
GOTO:EOF
here is updated code that still does not work
#echo off
SETLOCAL
SET stringtofindreplace=YYY
for %%f in (*.fmw) do (
#echo Processing %%f...
(
fOR /F "delims=" %%l IN (%%f) DO (
SET "line=%%l"
SET fname=%%~nf
SETLOCAL ENABLEDELAYEDEXPANSION
SET fname=!fname:~6,3!
SET "x=!line:%stringtofindreplace%=%fname%!"
echo(!x!
ENDLOCAL
)
)>%%~nf.txt
)
GOTO:EOF
You have a number of problems.
1) Your name substring computation attempts to use delayed expansion within your loop before you enable it.
2) The computed replacement string cannot be expanded using normal expansion. You need delayed expansion there as well. But you cannot use delayed expansion within delayed expansion. The trick is to transfer the inner value to a FOR variable.
3) You have got an extra, unbalanced ) after ENDLOCAL. I don't think it is causing any problems with the code you have posted, but you probably should remove it.
You are also computing the name substring once for every line, when it really only need be done once per file. This isn't a bug, but it is inefficient.
Here is corrected code.
#echo off
setlocal
set stringtofindreplace=YYY
for %%f in (*.fmw) do (
#echo Processing %%f...
setlocal enableDelayedExpansion
set "fname=%%~nf"
for /f "eol=: delims=" %%A in ("!fname:~6,3!") do (
endlocal
for /f "delims=" %%l IN (%%f) do (
set "line=%%l"
setlocal enableDelayedExpansion
set "x=!line:%stringtofindreplace%=%%A!"
echo(!x!
endlocal
)
)>"%%~nf.txt"
)
You still could have problems if any lines begin with ;. Also, empty lines will be stripped. Both limitations could be solved with a bit more code.
But even if you fix the limitations, it will be quite slow. It could be painfully slow if any files are large.
The code is much simpler, faster, and more reliable if you use my hybrid JScript/batch REPL.BAT utility:
#echo off
setlocal
set stringtofindreplace=YYY
for %%f in (*.fmw) do (
#echo Processing %%f...
setlocal enableDelayedExpansion
set "fname=%%~nf"
type "!fname!.fmw"|repl "%stringtofindreplace%" "!fname:~6,3!" li >"!fname!.txt"
endlocal
)

return variable from subroutine, not working... why?

In order to store multiple lines inside a variable from a text file, I am using the answer from this question: https://stackoverflow.com/a/10624240/1513458
Using this script inline and parsing large files gave me a maximum setlocal error. So to counter this, I put the script in a subroutine, and called it from the line(s) i needed it in.
An echo at the bottom of the subroutine before it terminates would output the expected result fine... but back at the top where it was originally called, the variable is empty. Why is that?
Here's a shortened version of the script:
setlocal EnableDelayedExpansion
set sourcefile=cc1
call :readfile %sourcefile%
echo !insert!
goto :EOF
:readfile
SETLOCAL DisableDelayedExpansion
set "insert="
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ %SOURCELOC%\%1.txt"`) do (
set "line=%%a"
SETLOCAL EnableDelayedExpansion
set "line=!line:#=#S!"
set "line=!line:*:=!"
for /F "delims=" %%p in ("!insert!#L!line!") do (
ENDLOCAL
set "insert=%%p"
)
)
SETLOCAL EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^
!"
set "insert=!insert:#S=#!"
)
rem A echo !insert! here would output the expected result
goto :EOF
Thanks so much for all your help. Batch is clearly not meant for me, but i need to tough this one through.
Your :readfile routine has SETLOCAL at the top that localizes environment changes. When a routine ends, there is an implicit ENDLOCAL executed for every active SETLOCAL that was instantiated within the routine. So the environment is restored to the state that existed prior to the call.
There is no need for a called subroutine. Your earlier code must have had SETLOCAL without an ENDLOCAL within the FOR loop. That would cause the max SETLOCAL error. Now your FOR loop has paired SETLOCAL/ENDLOCAL, so no more problem.
It is possible to transport any value accross the ENDLOCAL barrier, but it uses an advanced technique.
Some simple restructuring avoids the need to do that. But be careful - no batch variable can contain more than 8192 characters.
setlocal disableDelayedExpansion
set sourcefile=cc1
set "insert="
for /f "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%~1.txt"') do (
set "line=%%a"
setlocal EnableDelayedExpansion
set "line=!line:#=#S!"
set "line=!line:*:=!"
for /f "delims=" %%p in ("!insert!#L!line!") do (
endlocal
set "insert=%%p"
)
)
setlocal EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^
!"
set "insert=!insert:#S=#!"
)
echo !insert!
EDIT
Here is a solution that demonstrates returning any value across the ENDLOCAL border (except it does not support carriage return. There is a simple extension that supports carriage return). The routine will give the correct result, regardless whether it was called with delayed expansion enabled or disabled. The technique was developed by DosTips user jeb here:http://www.dostips.com/forum/viewtopic.php?f=3&t=1839, and here: http://www.dostips.com/forum/viewtopic.php?p=6930#p6930. There you will find some explanation as to how it works, but it is not for the faint of heart.
#echo off
setlocal enableDelayedExpansion
set sourceloc=.
set sourcefile=test
set ^"LF=^
^"
call :readFile "%sourcefile%"
echo(!insert!
exit /b
:readFile
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
set "insert="
for /f "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%~1.txt"') do (
set "line=%%a"
setlocal EnableDelayedExpansion
set "line=!line:%%=%%J!"
set "line=!line:*:=!"
for /f "delims=" %%p in ("!insert!%%~L!line!") do (
endlocal
set "insert=%%p"
)
)
setlocal enableDelayedExpansion
if defined insert (
set "insert=!insert:"=%%~K!"
if not defined notDelayed set "insert=!insert:^=^^^^!"
)
if defined insert if not defined notDelayed set "insert=%insert:!=^^^!%" Do not remove!
set "replace=%% """"
for %%L in ("!LF!") do for /f "tokens=1,2" %%J in ("!replace!") do (
endlocal
endlocal
endlocal
set "insert=%insert%" Do not remove!
)
exit /b
try this:
setlocal EnableDelayedExpansion
set sourcefile=cc1
call :readfile insert
echo %insert%
goto :EOF
:readfile
SETLOCAL DisableDelayedExpansion
set "insert="
FOR /F "delims=" %%a in ('findstr /n "^" "%SOURCELOC%\%1.txt"') do (
set "line=%%a"
SETLOCAL EnableDelayedExpansion
set "line=!line:#=#S!"
set "line=!line:*:=!"
for /F "delims=" %%p in ("!insert!#L!line!") do (
ENDLOCAL
set "insert=%%p"
)
)
SETLOCAL EnableDelayedExpansion
if defined insert (
set "insert=!insert:~2!"
set ^"insert=!insert:#L=^
!"
set "insert=!insert:#S=#!"
)
rem A echo !insert! here would output the expected result
SET "%1=!insert!"
ENDLOCAL
goto :EOF

Resources