How to use delayedexpansion to handle value in batch script - batch-file

I am facing issue with reading contents of CSV files in batch script. I have a series of files say My_A_File.csv, My_B_File.csv ... My_Z_File.csv. The issue I was facing is reading special characters in them. Hence, wanted to read the values with delayedexpansion turned off.
When I read the values in the block with disabledelayedexpansion, they are empty! How can I handle this?
Script:
#echo off
setlocal enabledelayedexpansion
for /L %%g in (65,1,90) do (
cmd /c exit /b %%g
set codeval=!=ExitCodeAscii!
set fileToReadFrom=My_!codeval!_File.csv
if exist My_!codeval!_File.csv (
echo Outer-!fileToReadFrom!
echo Outer-!codeval!
setlocal disabledelayedexpansion
echo Inner-%fileToReadFrom%
echo Inner-%codeval%
endlocal
)
)
Output:
Outer-My_A_File.csv
Outer-A
Inner-
Inner-

This how the delayed expansion is supposed to work.However you can access the variables with CALL but this will the performance (mind that you cant CALL FOR ):
#echo off
setlocal enabledelayedexpansion
for /L %%g in (65,1,90) do (
cmd /c exit /b %%g
set codeval=!=ExitCodeAscii!
set fileToReadFrom=My_!codeval!_File.csv
if exist My_!codeval!_File.csv (
echo Outer-!fileToReadFrom!
echo Outer-!codeval!
setlocal disabledelayedexpansion
call echo Inner-%%fileToReadFrom%%
call echo Inner-%%codeval%%
endlocal
)
)
or you can use pipes.Which also will hit the performance (now you can use
break|for "usebackq" %%a in ("Inner-%%fileToReadFrom%%") do #echo %%~a):
#echo off
setlocal enabledelayedexpansion
for /L %%g in (65,1,90) do (
cmd /c exit /b %%g
set codeval=!=ExitCodeAscii!
set fileToReadFrom=My_!codeval!_File.csv
if exist My_!codeval!_File.csv (
echo Outer-!fileToReadFrom!
echo Outer-!codeval!
setlocal disabledelayedexpansion
break|echo Inner-%%fileToReadFrom%%
break|echo Inner-%%codeval%%
endlocal
)
)

Use a subroutine to process code with delayed expansion disabled as follows:
#echo off
rem skip subroutine code
goto :toMain
:toProcessDDE
rem subroutine to process delayed expansion disabled
setlocal disabledelayedexpansion
echo Inner-%fileToReadFrom%
echo Inner-%codeval%
endlocal
exit /B
:toMain
setlocal enabledelayedexpansion
for /L %%g in (65,1,90) do (
cmd /c exit /b %%g
set codeval=!=ExitCodeAscii!
set fileToReadFrom=My_!codeval!_File.csv
if exist My_!codeval!_File.csv (
echo Outer-!fileToReadFrom!
echo Outer-!codeval!
call :toProcessDDE
)
)
Read
CALL: Call one batch program from another, or call a subroutine and
EXIT: … quit the current subroutine …

Related

Why deleting a substring in a string doesn't work. Batch

#echo off
#chcp 65001
setlocal enableextensions enabledelayedexpansion
set test=qwert
goto start
:IsStrInStrFunc
setlocal
call set check=%%1:%2=%
echo %check%
endlocal
exit /b
:start
call :IsStrInStrFunc %test%, q
pause
The "check" variable must contain "wert". What's wrong?.......
if you want to set %check% to be "wert"
set check=%%1:%2=%
should be
set check=%test:~1%
For more info check command help set in Command Prompt
Perhaps this example will assist you:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
For /F "Delims==" %%G In ('"(Set _cp) 2> NUL"') Do Set "%%G="
For /F Tokens^=* %%G In ('"%SystemRoot%\System32\chcp.com"'
) Do For %%H In (%%G) Do Set "_cp=%%~nH"
If Not %_cp% Equ 65001 (Set "_cpc=TRUE"
"%SystemRoot%\System32\chcp.com" 65001 >NUL)
Set "test=qwert"
GoTo Start
:IsStrInStrFunc
Set "check=%~1"
Echo Before: %check%
SetLocal EnabledelayedExpansion
Set "check=!check:%~2=!"
EndLocal & Echo After: %check%
Exit /B
:Start
Call :IsStrInStrFunc "%test%" "q"
Pause
If Defined _cpc "%SystemRoot%\System32\chcp.com" %_cp% >NUL
GoTo :EOF
To do substring substitution on a variable, you need the variablename, not its value (see set /?), so your parameter in the call can't be %test% (which would pass the string quert), but must be test.
And as you are using delayed expansion anyway, why not using it?
#echo off
#chcp 65001
setlocal enableextensions enabledelayedexpansion
set "test=qwert"
goto start
:IsStrInStrFunc
setlocal
REM call set "check=%%%~1:%~2=%%"
set "check=!%~1:%~2=!"
echo %check%
endlocal
exit /b
:start
call :IsStrInStrFunc test q
PS: you don't need the ~ chars with your simple example, but they don't disturb either. Imagine you want to replace a string, you can simply do that with call :IsStrInStrFunc test "a string" (that's where the ~ are necessary. Good practice to include them anyway (just in case))

How to get the name of calling bat script

I need to have my 2.bat script behaviour depending on the name of calling script.
Scenario:
2.bat is invoked from many other external scripts, which I am not entitled to change. Only 2.bat is under my thumb.
1.bat:
...
call 2.bat
2.bat:
...here place something extracting "1.bat"...
As you cant change the calling bat there will be almost impossible to get its name if it is triggered through the cmd console (may be a memory dump could help?) as then the ProcessId will hold information only for the cmd.exe. The command prompt history could give you some information but it would be unreliable (and requires a dump to a temporary file)
If the calling bat is double clicked you can use this:
setlocal enableDelayedExpansion
for /f "tokens=2* delims= " %%a in ("%cmdcmdline%") do (
if /i "%%~a" equ "/c" (
for %%# in (%%~b) do (
echo calling bat : %%~#
)
) else (
doskey /history >"%tmp%\cmd.history"
for /f "usebackq tokens=* delims=" %%# in ("%tmp%\cmd.history") do (
set "last_command=%%#"
)
echo probably this is the calling bat: !last_command!
del /q /f "%tmp%\cmd.history"
)
)
pause
You can get the name of a calling batch with a trick.
Assuming you have a first.bat(you can't controll) it could look like this
#echo off
set caller=empty
echo This is %~0
for /L %%n in (1 1 3) do (
echo(
echo #1 before calling, n=%%n
call second %%n
)
echo Back to %~0
And your second.bat detects the caller
#echo off
setlocal DisableDelayedExpansion
set "func=%~0"
for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
if ":" == "%func:~0,1%" (
goto %func%
)
REM *** Get the name of the caller
(
(goto) 2>nul
setlocal DisableDelayedExpansion
call set "caller=%%~f0"
call set _caller=%%caller:*%%~f0=%%
if defined _caller (
set "callType=batch"
call "%~d0\:mainFunc\..%~pnx0" %*
) ELSE (
set "callType=cmd-line"
cmd /c "call "%~d0\:mainFunc\..%~pnx0" %*"
)
echo BACK
endlocal
)
echo NEVER REACHED
exit /b
:mainFunc
echo :mainFunc of %~nx0 arg1=%1 is called from '%caller%'/%callType%
exit /b
I've adjusted jikou's and jeb's code a little bit so it can also detect the caller function from the caller script.
detectCallerScript.bat:
#echo off
setlocal DisableDelayedExpansion
set "func=%~0"
for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
if ":" == "%func:~0,1%" (
goto %func%
)
REM *** Get the name of the caller
(
(goto) 2>nul
setlocal DisableDelayedExpansion
call set "caller=%%~f0"
call set _caller=%%caller:*%%~f0=%%
if defined _caller (
set "callType=batch"
call "%~d0\:mainFunc\..%~pnx0" %%0 %*
) else (
set "callType=cmd-line"
cmd /c "call "%~d0\:mainFunc\..%~pnx0" %%0 %*"
)
endlocal
)
echo(NEVER REACHED
exit /b
:mainFunc
set "source=%~1"
shift /1
:mainFuncLoop
set args=%args%%1
if "%~2" neq "" shift /1&goto:mainFuncLoop
if defined args set "args=%args:~0,-1%"
echo(:mainFunc of %~nx0 source=%source% with args="%args%" is called from '%caller%'/%callType%
exit /b
scriptCaller.bat:
#echo off
set caller=empty
echo(%~0: call detectCallerScript hi there
call detectCallerScript hi there
echo(Back to %~0
echo(
call:someFunc
exit /b
:someFunc
set caller=empty
echo(%~n0 %~0: call detectCallerScript hi there
call detectCallerScript hi there
echo(Back to %~0
echo(
Output:
scriptCaller.bat: call detectCallerScript hi there
:mainFunc of detectCallerScript.bat source=scriptCaller.bat with args="hi there" is called from '{path}\scriptCaller.bat'/batch
Back to scriptCaller.bat
scriptCaller :someFunc: call detectCallerScript hi there
:mainFunc of detectCallerScript.bat source=:someFunc with args="hi there" is called from '{path}\scriptCaller.bat'/batch
Back to :someFunc

The syntax of the command is incorrect : for loop batch

I'm making a batch script to stop several services , however i get the syntax is incorrect error at second FOR which loops in serviceList.TEMP
setlocal EnableDelayedExpansion
setlocal enableExtensions
::queryex output file
sc queryex>services.TEMP
find /i /N "DISPLAY_NAME: Hotspot" services.TEMP>tmp1.TEMP
FOR /F "skip=2" %%G in (tmp1.TEMP) do (
set num=%%G
set num=!num:~1,3!
echo !num!>serviceList.TEMP
)
FOR %%X in (serviceList.TEMP) do (
set /a "SKIP_LINES=%%G+7"
set secondForFilter="skip=%SKIP_LINES%"
FOR /F %secondForFilter% %%Z in (services.TEMP) do (
call debug.cmd REM debug.cmd -> echo debug pause>nul
set serv=%%Z
set "serv=!serv: =!" REM Extract PID
set "serv=!serv::=!" REM Extract PID
set procID=!serv!
taskkill /pid %procID% /f >>debug.txt 2>>debug.txt
goto secondLoopEnd
)
:secondLoopEnd
)
del /S *.TEMP >>debug.txt 2>>debug.txt
the problem is here:
FOR %%X in (serviceList.TEMP) do (
set /a "SKIP_LINES=%%G+7"
set secondForFilter="skip=%SKIP_LINES%"
FOR /F %secondForFilter% %%Z in (services.TEMP) do (
call debug.cmd REM debug.cmd -> echo debug pause>nul
the usual approach when you set value in brackets context is to use delayed expansion but it wont work for parametrized for options.
Here you'll need a subroutine.
And you have GOTO in the for loop. GOTO breaks for context and the loops will be not called after goto is executed.
And rem cannot be used on the same line as the code without "&"
Consider something like this (though I cannot check the logic of the bat):
setlocal EnableDelayedExpansion
setlocal enableExtensions
::queryex output file
sc queryex>services.TEMP
find /i /N "DISPLAY_NAME: Hotspot" services.TEMP>tmp1.TEMP
FOR /F "skip=2" %%G in (tmp1.TEMP) do (
set num=%%G
set num=!num:~1,3!
echo !num!>serviceList.TEMP
)
FOR %%X in (serviceList.TEMP) do (
call :subroutine %%G
)
del /S *.TEMP >>debug.txt 2>>debug.txt
exit /b %errorlevel%
:subroutine
setlocal enableDelayedExpansion
set /a skiplines=%~1+7
set "filter="skip=%skiplines%""
FOR /F %filter% %%Z in (services.TEMP) do (
call debug.cmd
set serv=%%Z
rem extract PID
set "serv=!serv: =!"
set "serv=!serv::=!"
set procID=!serv!
taskkill /pid !procID! /f >>debug.txt 2>>debug.txt
goto :break_for
)
:break_for
endlocal
exit /b
Should the > in your echo to serviceList.TEMP be a >> so that you append to the file?
echo !num!>>serviceList.TEMP
In which case, you should also ensure that the file is deleted prior to the appending operations.
Also, I assume you missed the /F from your FOR loop, as you're trying to read the lines of the serviceList.TEMP file, yes?
FOR %%X in (serviceList.TEMP) do (
Should be...
FOR /F %%X in (serviceList.TEMP) do (
?
Also, you can append to the same file with both std out and err by doing this...
someprocesshere 1> out.log 2>&1

How to set a variable inside a loop for /F

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
)

MS-DOS batch script: assignment operation

I am referring to below threat Batch files: How to read a file?. For retrieving the line by line from a text file. I am using the below script:
#echo off
SETLOCAL DisableDelayedExpansion
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ paths.txt"`) do (
set "var=%%a"
SETLOCAL EnableDelayedExpansion
set "var=!var:*:=!"
echo !var!
ENDLOCAL
)
Code is working fine! The values in !var! can not assign to any variable. :( I struct there, Please anyone help to read line by line and I wish to assign to some variable and want to manipulate that variable. Please help to solve this situation.
Update:
#ECHO off
CLS
SET PROJ_DIR=D:\workspace\proj
SET PROMO_DIR=D:\TEST
SET SOURCE_CODE=\Source Code
SETLOCAL DisableDelayedExpansion
for /f %%a in (paths.txt) do (
SET "var=%%a"
SETLOCAL EnableDelayedExpansion
set "var=!var:*:=!"
set FILE_PATH=!var://www.domain.com/path/dir=!
SET FILE_PATH=!FILE_PATH:/=\!
SET PROMO_FILE_PATH=!PROMO_DIR!!SOURCE_CODE!!FILE_PATH!
FOR %%i IN ("!PROMO_FILE_PATH!") DO SET FOLDER_PATH=%%~dpi
FOR %%i IN ("!PROMO_FILE_PATH!") DO SET FILE_NAME=%%~nxi
IF EXIST "!FOLDER_PATH!" GOTO F3
MKDIR "!FOLDER_PATH!"
:F3
IF NOT EXIST "!PROJ_DIR!!FILE_PATH!" GOTO F4
COPY "!PROJ_DIR!!FILE_PATH!" "!FOLDER_PATH!"
:F4
ECHO Cannot find the file under "!PROJ_DIR!!FILE_PATH!"
ENDLOCAL
)
SET CLOSE_CONFIRM=
SET /P CLOSE_CONFIRM=Press any key to close the window...%=%
paths.txt
//www.domain.com/path/dir/dir1/dir2/file1.txt
//www.domain.com/path/dir/dir1/dir2/file2.txt
//www.domain.com/path/dir/dir1/dir2/file3.txt
//www.domain.com/path/dir/dir1/dir2/file4.txt
//www.domain.com/path/dir/dir1/dir3/file1.txt
Command Output
1 file(s) copied.
Cannot find the file under "D:\workspace\proj\dir1\dir2\file1.txt"
1 file(s) copied.
Cannot find the file under "D:\workspace\proj\dir1\dir2\file2.txt"
Press any key to close the window...
thanks..
The key is the delayed expansion, expand your variables inside of parenthesis always with ! not with %.
A sample that changes X with Y
#echo off
SETLOCAL DisableDelayedExpansion
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ paths.txt"`) do (
set "var=%%a"
SETLOCAL EnableDelayedExpansion
set "var=!var:*:=!"
set "myVar=!var!"
set "myVar=!myVar:X=Y!"
echo X replaced with Y =!myVar!
ENDLOCAL
)
In your updated version the goto :label stops the for-loop immediatly
Better rewrite it to IF-Blocks
IF NOT EXIST "!FOLDER_PATH!" (
MKDIR "!FOLDER_PATH!"
)
IF EXIST "!PROJ_DIR!!FILE_PATH!" (
COPY "!PROJ_DIR!!FILE_PATH!" "!FOLDER_PATH!"
) ELSE
(
ECHO Cannot find the file under "!PROJ_DIR!!FILE_PATH!"
)
The other answers here cover the tricky bits of delayed expansion in this code. I want to add that you can often avoid most delayed expansion problems by rolling the parentheses out into a subroutine.
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ paths.txt"`) do call :HandlePath %%a
goto :eof
================
:HandlePath
set "var=%*"
set "var=%var:*:=%"
echo %var%
goto :eof
I find this code easier to maintain because each line is parsed an executed exactly when you would expect.
If you want to read each line and manipulate it:
SETLOCAL EnableDelayedExpansion
FOR /F "tokens=*" %%a IN (paths.txt) DO (
set var=%%a
ECHO %var%
PAUSE
)
ENDLOCAL
If you are trying to search a string from a file and manipulate it:
SETLOCAL EnableDelayedExpansion
FOR /F "tokens=* usebackq" %%a IN (`FIND /I 'string to search for' "C:\folder\paths.txt"`) DO (
set var=%%a
ECHO %var%
PAUSE
)
ENDLOCAL
If you are trying to search a string and manipulate each word in the string:
SETLOCAL EnableDelayedExpansion
FOR /F "usebackq tokens=1-999 delims= " %%a IN (`FIND /I 'string to search for' "C:\folder\paths.txt"`) DO (
REM %%a = first word %%b = second word etc. through the alphabet
set var1=%%a%%b%%c
set var2=%%d
ser var3=%%e
ECHO %var1% %var2% %var3%
PAUSE
)
ENDLOCAL
This works for me:
#echo off
SETLOCAL EnableDelayedExpansion
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ paths.txt"`) do (
set var=%%a
set var=!var:*:=!
echo !var!
)
ENDLOCAL

Resources