Code Excerpt of my batch file:
set stringOne=ABCDEF
echo %stringOne:~2,3%
This output is CDE
How can I dynamically echo the output for my start index and desired output length?
set stringOne=ABCDEF
set start=2
set len=3
you need two layers of variable expansion. That can be done by delayed expansion or by call:
#echo off
setlocal enabledelayedexpansion
set "string=ABCDEFGH"
set "start=2"
set "len=3"
echo A with delayed expansion: !string:~%start%,%len%!
call echo A with using 'call': %%string:~%start%,%len%%%
FOR /F %%G IN ('dir /b "%~f0"') DO (
set /A "newStart=!Start!+2"
call echo B with 'call' and delayed : %%string:~!newStart!,!len!%%
call call echo B with double-'call': %%%%string:~%%newStart%%,%len%%%%%
)
FOR /F %%G IN ('dir /b "%~f0"') DO call :output
goto :eof
:output
set /A "newStart=Start+2"
echo C with subroutine and delayed expansion: !string:~%newStart%,%len%!
call echo C with subroutine andusing 'call': %%string:~%newStart%,%len%%%
goto :eof
EDITED to match your comment. You need a third layer of expansion. I expanded the code with some different methods.
(btw: please don't post code in comments, it's nearly impossible to read. And if your question changes, better ask a follow-up question next time)
Related
I have whittled down a more complex CMD script to the essentials. It reads an input file line by line, unquotes it (if quoted) and writes it out to another CMD file.
The problem is that if the input file contains exclamation marks (! or bang) the character gets stripped out somewhere along the line.
Here is the CMD script, BANG1.CMD:
#echo off
setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
if exist bang2.cmd del bang2.cmd
for /f "tokens=*" %%a in (bang1.txt) do call :doit1 %%a
exit /b
:doit1
set P1=%1
if %P1%. EQU . exit /b
call :unquotex P1 %P1%
echo>>bang2.cmd echo P1:[%P1%]
exit /b
:unquotex
set X=%2
set Q=%X:~0,1%
if "!Q!" EQU ^""" SET X=!X:~1,-1!
set %1=%X%
exit /b
Here is the input file BANG1.TXT:
HelloWorld
"Hello World"
Hello!World
"Hello!World"
The resulting file BANG2.CMD ends up containing this:
echo P1:[HelloWorld]
echo P1:[Hello World]
echo P1:[HelloWorld]
echo P1:[HelloWorld]
The question is, what happened to the embedded bangs? I have tried with and without ENABLEDELAYEDEXPANSION. I have even tried escaping (^) the bangs in the input file, still with no luck.
Is there any way to preserve them?
Thanks.
The problem at all is delayed expansion here.
With delayed expansion, exclamation marks are used to expand variables, but when there is only one exclamation mark in a line it will be removed.
Specially in FOR /F loops delayed expansion is tricky to handle, as the expansion of the FOR parameter is directly affected by the delayed expansion. The only solution is to disable it temporarily.
The next problem is the CALL, you can't transfer content with CALL (without destroying it).
It's better to transfer the variable by reference (only the variable name) and then get the content in the called function.
The last problem in your code are the percent expansions, do not use them
when delayed expansion is enabled, as the delayed expansion is evaluated after the percent expansion an expanded line will be expanded a second time by the delayed expansion.
Sample.
Assume the content of var is Bang!
echo %var% expands to Bang! but then the delayed expansion will evaluate Bang! to Bang.
With echo !var! you simply get Bang!
#echo off
setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
if exist bang2.cmd del bang2.cmd
for /f "tokens=*" %%a in (bang1.txt) do (
setlocal DisableDelayedExpansion
set "line=%%a"
setlocal EnableDelayedExpansion
call :doit1 line
endlocal
endlocal
)
exit /b
:doit1
set "P1=!%1!"
if "!P1!" EQU "" exit /b
call :unquotex P1
echo>>bang2.cmd echo P1:[!P1!]
exit /b
:unquotex
set "param=!%~1!"
if "!param:~0,1!" == ^""" (
set "param=!param:~1,-1!"
)
set "%1=!param!"
exit /b
Like this :
#echo off
(for /f "delims=" %%a in ('type bang1.txt') do echo echo P1:[%%~a])>bang2.cmd
Try this:
#echo off
if exist bang2.cmd del bang2.cmd
for /f "tokens=*" %%a in (bang1.txt) do call :doit1 %%a
exit /b
:doit1
set "P1=%1"
if %P1%.==. exit /b
call :unquotex P1 %P1%
echo>>bang2.cmd echo P1:[%P1%]
exit /b
:unquotex
set "%1=%~2"
exit /b
Using parameters, you can get the version without quotes using %~1 instead of %1. If %1 contains "hello world" for example, then %~1 contains hello world. This allows for an easier unquoting mechanism, removing the need for delayed expansion.
I am trying to double my delayed expansion if that makes any sense. Here is what I want.
set var1=hello
set var2=var1
set var3=var2
echo %!%var3%!%
and then have hello be displayed. This is not my actual code but an example of how I need it to work.
even possible without delayed expansion (although even uglier than rojo's answer) Just a matter of the number of "layers" of parsing and correct escaping the %:
#echo off
set var1=hello
set var2=var1
set var3=var2
call call echo %%%%%%%var3%%%%%%%
Another one without call (faster):
#echo off
setlocal enabledelayedexpansion
set "var1=hello"
set "var2=var1"
set "var3=var2"
for %%v in (!%var3%!) do echo !%%v!
EDIT: Reply to rojo's challenge
Your code have an error in the creation of the variables; all variables contain the string: "var!X!" and the final result is "var!X!X". Below is your code with the variables creation part fixed:
#echo off
setlocal EnableDelayedExpansion
set "var1=hello"
for /L %%I in (2,1,1000) do (
set /a X = %%I - 1
set "var%%I=var!x!"
)
call :follow var1000
goto :EOF
:follow <varname>
setlocal enabledelayedexpansion
set "var=!%~1!"
:follow_loop
if defined !%var%! (
set "var=!%var%!" & goto follow_loop
) else (
echo !%var%!
)
This program correctly show "hello" at end; it takes about 2.51 seconds when run in my computer.
The method is pretty short, so there is not too much chance to improve it; the obvious modification is to change the goto loop by a for (while) one. Here it is:
#echo off
setlocal EnableDelayedExpansion
set "var1=hello"
for /L %%I in (2,1,1000) do (
set /a X = %%I - 1
set "var%%I=var!x!"
)
call :follow var1000
goto :EOF
:follow <varname>
set "var=%~1"
cmd /V:ON /C for /L %%? in () do #for %%v in (^^!var^^!) do #if defined %%v (set "var=^!%%v^!") else echo %%v ^& exit
This code takes about 1.22 seconds to run, that is, just the 48.6% of your method (2 times faster) ;)
You could add a call and surround your first delayed expansion with double percents like this.
#echo off
setlocal enabledelayedexpansion
set "var1=hello"
set "var2=var1"
set "var3=var2"
call echo %%!%var3%!%%
Seems horribly convoluted to me, though. I'd probably rewrite the script to make such trickery not needed if I were me.
Edit: Since there are so many solutions being added here, I'll propose another one. Here's a subroutine that will follow the line of variables from beginning to end, even if it's 1000 levels deep. Just as an academic exercise.
#echo off
setlocal
set "var1=hello"
for /L %%I in (2,1,1000) do (
set /a X = %%I - 1
setlocal enabledelayedexpansion
for %%x in (!X!) do endlocal & set "var%%I=var%%x"
)
call :follow var1000
goto :EOF
:follow <varname>
setlocal enabledelayedexpansion
set "var=!%~1!"
:follow_loop
if defined !%var%! (
set "var=!%var%!" & goto follow_loop
) else (
echo !%var%!
)
And here's another using a batch + JScript hybrid (because the JScript while loop is faster than a batch goto loop).
#if (#CodeSection == #Batch) #then
#echo off
setlocal
set "var1=hello"
for /L %%I in (2,1,1000) do (
set /a X = %%I - 1
setlocal enabledelayedexpansion
for %%x in (!X!) do endlocal & set "var%%I=var%%x"
)
cscript /nologo /e:JScript "%~f0" "var1000"
goto :EOF
#end // end batch / begin JScript hybrid chimera
var env = WSH.CreateObject('Wscript.Shell').Environment('Process'),
itm = WSH.Arguments(0);
while (env(itm)) itm = env(itm);
WSH.Echo(itm);
Your move, Aacini.
I made this code
dir /B /S %RepToRead% > %FileName%
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo %z%
echo %%a
)
echo %%a is working fine but echo %z% returns "echo disabled".
I need to set a %z% because I want to split the variable like %z:~7%
Any ideas?
There are two methods to setting and using variables within for loops and parentheses scope.
setlocal enabledelayedexpansion see setlocal /? for help. This only works on XP/2000 or newer versions of Windows.
then use !variable! instead of %variable% inside the loop...
Create a batch function using batch goto labels :Label.
Example:
for /F "tokens=*" %%a in ('type %FileName%') do call :Foo %%a
goto End
:Foo
set z=%1
echo %z%
echo %1
goto :eof
:End
Batch functions are very useful mechanism.
You probably want SETLOCAL ENABLEDELAYEDEXPANSION. See https://devblogs.microsoft.com/oldnewthing/20060823-00/?p=29993 for details.
Basically: Normal %variables% are expanded right aftercmd.exe reads the command. In your case the "command" is the whole
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo %z%
echo %%a
)
loop. At that point z has no value yet, so echo %z% turns into echo. Then the loop is executed and z is set, but its value isn't used anymore.
SETLOCAL ENABLEDELAYEDEXPANSION enables an additional syntax, !variable!. This also expands variables but it only does so right before each (sub-)command is executed.
SETLOCAL ENABLEDELAYEDEXPANSION
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo !z!
echo %%a
)
This gives you the current value of z each time the echo runs.
I struggeld for many hours on this.
This is my loop to register command line vars.
Example : Register.bat /param1:value1 /param2:value2
What is does, is loop all the commandline params,
and that set the variable with the proper name to the value.
After that, you can just use
set value=!param1!
set value2=!param2!
regardless the sequence the params are given. (so called named parameters).
Note the !<>!, instead of the %<>%.
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%P IN (%*) DO (
call :processParam %%P
)
goto:End
:processParam [%1 - param]
#echo "processparam : %1"
FOR /F "tokens=1,2 delims=:" %%G IN ("%1") DO (
#echo a,b %%G %%H
set nameWithSlash=%%G
set name=!nameWithSlash:~1!
#echo n=!name!
set value=%%H
set !name!=!value!
)
goto :eof
:End
Simple example of batch code using %var%, !var!, and %%.
In this example code, focus here is that we want to capture a start time using the built in variable TIME (using time because it always changes automatically):
Code:
#echo off
setlocal enabledelayedexpansion
SET "SERVICES_LIST=MMS ARSM MMS2"
SET START=%TIME%
SET "LAST_SERVICE="
for %%A in (%SERVICES_LIST%) do (
SET START=!TIME!
CALL :SOME_FUNCTION %%A
SET "LAST_SERVICE=%%A"
ping -n 5 127.0.0.1 > NUL
SET OTHER=!START!
if !OTHER! EQU !START! (
echo !OTHER! is equal to !START! as expected
) ELSE (
echo NOTHING
)
)
ECHO Last service run was %LAST_SERVICE%
:: Function declared like this
:SOME_FUNCTION
echo Running: %1
EXIT /B 0
Comments on code:
Use enabledelayedexpansion
The first three SET lines are typical
uses of the SET command, use this most of the time.
The next line is a for loop, must use %%A for iteration, then %%B if a loop inside it
etc.. You can not use long variable names.
To access a changed variable such as the time variable, you must use !! or set with !! (have enableddelayexpansion enabled).
When looping in for loop each iteration is accessed as the %%A variable.
The code in the for loop is point out the various ways to set a variable. Looking at 'SET OTHER=!START!', if you were to change to SET OTHER=%START% you will see why !! is needed. (hint: you will see NOTHING) output.
In short !! is more likely needed inside of loops, %var% in general, %% always a for loop.
Further reading
Use the following links to determine why in more detail:
Difference between %variable% and !variable! in batch file
Variable usage in batch file
To expand on the answer I came here to get a better understanding so I wrote this that can explain it and helped me too.
It has the setlocal DisableDelayedExpansion in there so you can locally set this as you wish between the setlocal EnableDelayedExpansion and it.
#echo off
title %~nx0
for /f "tokens=*" %%A in ("Some Thing") do (
setlocal EnableDelayedExpansion
set z=%%A
echo !z! Echoing the assigned variable in setlocal scope.
echo %%A Echoing the variable in local scope.
setlocal DisableDelayedExpansion
echo !z! &rem !z! Neither of these now work, which makes sense.
echo %z% &rem ECHO is off. Neither of these now work, which makes sense.
echo %%A Echoing the variable in its local scope, will always work.
)
set list = a1-2019 a3-2018 a4-2017
setlocal enabledelayedexpansion
set backup=
set bb1=
for /d %%d in (%list%) do (
set td=%%d
set x=!td!
set y=!td!
set y=!y:~-4!
if !y! gtr !bb1! (
set bb1=!y!
set backup=!x!
)
)
rem: backup will be 2019
echo %backup%
Try this:
setlocal EnableDelayedExpansion
...
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo !z!
echo %%a
)
You can use a macro if you access a variable outside the scope
#echo off
::Define macro
set "sset=set"
for /l %%a in (1,1,4) do (
::set in loop
%sset% /a "x[%%a]=%%a*%%a"
if %%a equ 4 (
:: set in condition
%sset% "x[%%a]=x Condition"
%sset% "y=y Condition"
)
)
echo x1=%x[1]% x2=%x[2]% x3=%x[3]% x4=%x[4]% y=%y%
:: Bonus. enableDelayedExpansion used to access massive from the loop
setlocal enableDelayedExpansion
echo Echo from the loop
for /l %%a in (1,1,4) do (
::echo in one line - echo|set /p =
echo|set /p "=x%%a=!x[%%a]! "
if %%a equ 4 echo y=%y%
)
pause
I know this isn't what's asked but I benefited from this method, when trying to set a variable within a "loop". Uses an array. Alternative implementation option.
SETLOCAL ENABLEDELAYEDEXPANSION
...
set Services[0]=SERVICE1
set Services[1]=SERVICE2
set Services[2]=SERVICE3
set "i=0"
:ServicesLoop
if defined Services[%i%] (
set SERVICE=!Services[%i%]!
echo CurrentService: !SERVICE!
set /a "i+=1"
GOTO :ServicesLoop
)
The following should work:
setlocal EnableDelayedExpansion
for /F "tokens=*" %%a in ('type %FileName%') do (
set "z=%%a"
echo %z%
echo %%a
)
In for loop i have checked condition, if condition is true i have set the count value and skipped some lines in file,
#echo off
SetLocal EnableDelayedExpansion
set /a count=0
set for_parameters="skip=!count! delims="
for /f %for_parameters% %%a in ('list.txt') do (
echo %%a
if %%a==Exception: (
set /a count+=2
)
)
Endlocal
Its shows delims=" was unexpected at this time. Error
Can anyone help me to fix this problem .....
What jeb said about delayed expansion and FOR options is true - you can't use it - but that has nothing to do with the problem in your code.
You are using delayed expansion before you use the expression in your FOR statement, so there is no problem there.
Your problem is that you are attempting to set SKIP=0. The SKIP value must be >0 to be valid. The fix is simple: don't include the SKIP option if you don't want to skip any lines :-)
#echo off
SetLocal EnableDelayedExpansion
set /a count=0
set "skip="
if !count! gtr 0 set "skip=skip=!count!"
set for_parameters="!skip! delims="
for /f %for_parameters% %%a in ('list.txt') do (
echo %%a
if %%a==Exception: (
set /a count+=2
)
)
Endlocal
Expanding on jeb's point: you cannot do the following
for /f !for_parameters! %%a in ...
because FOR has special parsing rules. Most commands parse their options after delayed expansion. But FOR and IF parse their options before FOR variable expansion and delayed expansion take place. Neither FOR variables nor delayed expansion can be used to specify FOR or IF options.
In the for-options you can only use percent expansion, but not delayed expansion.
If your skip value itself is calculated in a block, then you need to extract the for loop into a function
You can use the More command to skip lines.
#echo off
:: By Elektro H#cker
SetLocal EnableDelayedExpansion
set /a count=2
for /F %%a in ('Type "list.txt" ^| MORE +!COUNT!') do (echo %%a)
Pause&exit
When I was testing a script I came across this issue when trying to extract characters from a string using batch. I have simplified it into a simple example. t.txt just contains the word hello.
#echo off
setlocal enabledelayedexpansion
set a=0
set b=1
for /f %%a in (t.txt) do (
set x=%%a
echo !x:~!a!,!b!!
set /a x+=1
)
pause >nul
The problem is, the variable x needs to be accessed using delayed expansion, and because I am updating the values of a and b through the loop these also need to be accessed using delayed expansion.
When trying to use the variables a and b to split the string they all need delayed expansion, but the order of the ! marks means that it is not parsed the way I intended!
CMD will expand my command as !x:~!, !,! and !!, instead of expanding the inner ones first. Obviously I can't use %'s either.
The only way I have found to get around this is to call an external function that isn't in the loop, so I can use %'s.
#echo off
setlocal enabledelayedexpansion
set a=0
set b=1
set v=
for /f %%a in (t.txt) do (
set x=%%a
call :RETURN x
set /a x+=1
)
pause >nul
:RETURN
set v=%1
echo %v:~!a!,!b!%
Is there any way of getting cmd to parse my command how I need it to, or this just a limitation I will have to use call for?
Simply transfer variables a and b to FOR variables.
#echo off
setlocal enabledelayedexpansion
set a=0
set b=1
for /f %%a in (t.txt) do (
set "x=%%a"
for /f "tokens=1,2" %%A in ("%a% %b%") do echo !x:~%%A,%%B!
REM this line makes no sense if x=hello: set /a x+=1
)
pause >nul
Mixing delayed and normal expansion will work.
#echo off
setlocal EnableDelayedExpansion
set a=0
set b=1
for /f %%L in (t.txt) do (
set "x=%%L"
echo !x:~%A%,%B%!
)