Escaping characters in a for loop in batch - batch-file

Alright, so let's say we have a file called start.cmd
#echo off
set value=1
for /D %%a in (%*) do (
echo %%a
)
start.cmd is called by
start.cmd ^%value^% ^%anothervalue^%
The for loop would, of course, take %1, and %2, and so forth.
My question is:
EDIT: How can I escape a value within another value?

I make a guess:
You want to expand the variables just in the for loop, not before.
With your case you only need to add a CALL, as it starts the parser a second time, so it can expand the %value% before it echo it.
#echo off
set value=1
for /D %%a in (%*) do (
call echo %%a
)
But better is to use delayed expansion here
#echo off
setlocal EnableDelayedExpansion
set value=1
for /D %%a in (%*) do (
echo %%a
)
Then you can start your batch with
start.cmd !value! !anothervalue!
This works as delayed expansion is done after the percent expansion by the parser, so %* is expanded to !value! !anothervalue! and then it's expanded to 1 xyz

Related

Batch search and replace in file removes the character "!" [duplicate]

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.

BATCH, summarize variables inside FOR loop

I have a batch file with a FOR loop inside like this:
set /a target=5
FOR /L %%G IN (1,1,%target%) DO (
echo %%G)
It works like a charm %%G will 1,2,3,4,5.
Now I want a new wariable like test=%%G+1 and it will be: 2,3,4,5,6
But with this code it didn't works.
set /a target=5
FOR /L %%G IN (1,1,%target%) DO (
echo %%G
set /a test=%%G+1
echo %test%
)
test variable will be every time: 6
What should I do?
Thanks
Roberto
You need to use EnabledDelayedExpansion when evaluating variables which are set inside a FOR loop:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
set /a target=5
FOR /L %%G IN (1,1,%target%) DO (
echo %%G
set /a test=%%G+1
REM Note the exclamation marks.
REM This is delayed expansion notation.
echo !test!
)
ENDLOCAL
If you do not use delayed expansion, then all variables are evaluated on the first pass of the FOR loop, so %test% will not actually have a value at this time.
By turning on delayed expansion (and using the !test! notation), the script will evaluate the value of !test! on each pass.

Windows CMD Batch: FOR /R with DelayedExpansion

On my desktop, there is a folder named "test". Inside this folder is two files, "file1.txt" and "file2.txt".
Take a look at this simple batch script:
#ECHO OFF
SET test="C:\Users\Tyler\Desktop\test"
ECHO %test%
FOR /R %test% %%F IN (*) DO (
ECHO %%F
)
As you might expect, it outputs the following:
"C:\Users\Tyler\Desktop\test"
C:\Users\Tyler\Desktop\test\file1.txt
C:\Users\Tyler\Desktop\test\file2.txt
Now take a look at this variation:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET test="C:\Users\Tyler\Desktop\test"
ECHO !test!
FOR /R !test! %%F IN (*) DO (
ECHO %%F
)
ENDLOCAL
I would expect its output to be no different. However, here it is:
"C:\Users\Tyler\Desktop\test"
It appears that !test! gets expanded in the ECHO !test! line, but not in the FOR /R !test! line, becoming just !test!. Since that is, of course, not a valid path, the FOR /R loop never iterates.
Why is this? What am I missing?
Why FOR works different than ECHO is because the batch parser (cmd.exe) has special parsing rules for FOR, IF and REM.
Therefore delayed expansion doesn't work for the parameters here, only for the arguments inside the parenthesis.
Only percent expansion works for the parameters, as the parser executes the percent expansion phase just before it switches to the special FOR parser rules.
If you can't use percent expansion, as you are inside of a block you can move the code to an own function and call it.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET test="C:\Users\Tyler\Desktop\test"
ECHO !test!
call :doMyLoop test
exit /b
:doMyLoop
set "arg=!%1!"
FOR /R %arg% %%F IN (*) DO (
ECHO %%F
)

Batch file basics: Adding a hardcoded integer to a variable in a for loop

I want to simply add 10 to the variable "%%i" in this batch file code and print it out to the screen. numbers.txt is a file that contains a single column of numbers.
FOR /F %%i IN (numbers.txt) DO (
set /a "T=%%i+10"
#echo %T%
)
For example, if %%i was 1 I would want T to be 11.
Thanks.
Without delayed expansion, you can use:
#echo off
set constant=10
FOR /F %%i IN (numbers.txt) DO (
set /a "T=%%i+%constant%"
call echo %%T%%
)
constant does not need delayed expansion, because it's constant throughout the loop.
Try the following code, it should work according to your description in the question :
#echo off
setlocal enabledelayedexpansion
set T=0
set K=10
for /f %%i in (numbers.txt) do (
set /A T=!K! + %%i
echo !T! )
endlocal
Save it as a .bat file extension and run from command prompt.
If file numbers.txt contain :
1
2
3
Output will be:
11
12
13
You can do this with delayed environment variable expansion. In the shell, you must first type
cmd /v
Then you can execute this batch script:
FOR /F %%i IN (numbers.txt) DO (
set /a T=%%i + 10
#echo !T!
)
If you don't run cmd /v first, then it will simply output !T! literally. See set /? for details on delayed environment variable expansion.
Start your batch file by turning on delayed expansion. With delayed expansion enabled, reference a variable inside a loop using ! instead of %.
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F %%i IN (numbers.txt) DO (
set /a "T=%%i+10"
#echo !T!
)

Batch file to handle random % character in FOR LOOP

I am trying to pass file names as FOR loop parameters to a separate batch file. The problem is, if a file name contains special characters (especially %), the parameter doesnt go to the called script. EG -
The FIRST_SCRIPT.bat is as follows -
cd "C:\theFolder"
for /R %%a in (*.*) do call SECOND_SCRIPT "%%~a"
The SECOND_SCRIPT.bat is as follows -
ECHO %1
If a file name contains % eg. "% of STATS.txt", the output ends up being
of STATS.txt
Which is wrong. I have tried using Setlocal DisableDelayedExpansion but with little success
Setlocal DisableDelayedExpansion
for /R %%a in (*.*) do (
SET "var=%%~a"
Setlocal EnableDelayedExpansion
call TEST_UPGRADE "%var%" "%%~a"
)
There are other stackoverflow answers, but they all need the % character to be known before hand. Since the file names are not in our control, these solutions won't work for us. Is there any way of handling this?
Thanks!
platform: cmd.exe for Windows XP
Aacini shows a solution that would work with % and also ! but it fails with carets ^.
But the solution is simple.
First it's necessary to disable the delayed expansion to handle the exclamation marks.
The filename is now exactly in the var variable.
The problems with carets and percents are caused by the CALL.
This can be solved with the CALL itself by use only the second percent expansion phase of the CALL by using %%var%%.
Setlocal DisableDelayedExpansion
for /R %%a in (*.*) do (
SET "var=%%~a"
call TEST_UPGRADE "%%var%%"
)
The next problem is in the second.bat to display the filename.
This should be done with delayed expansion enabled to avoid problems with special characters, or you need always quotes.
set "var=%~1"
setlocal EnableDelayedExpansion
echo Filename: !var!
solution with a temp file:
first.bat
#ECHO OFF &SETLOCAL
REM to escape the '%' use a second '%'
SET "var=40%% &off!.txt"
REM get a random temp file name
:loop
SET "tname=%temp%%random%%random%"
IF EXIST "%tname%" GOTO :loop
SETLOCAL ENABLEDELAYEDEXPANSION
REM save the variable in the file
>"%tname%" (ECHO(!var!)
CALL "second.bat" "%tname%"
ENDLOCAL
second.bat
#ECHO OFF &SETLOCAL
SET "tname=%~1"
<"%tname%" set/p"var="
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO !var!
DEL "%tname%" /F /Q
..output is:
40% &off!.txt
EDIT: I added the enable/disable delayed expansion technique to avoid problems with exclamation-mark character.
#echo off
setlocal DisableDelayedExpansion
for /F "delims=" %%a in ('dir /B *.txt') do echo %%a
echo/
for %%a in (*.txt) do (
SET "var=%%a"
setlocal EnableDelayedExpansion
call :TEST_UPGRADE "!var:%%=%%%%!" "%%~a"
endlocal
)
goto :EOF
:TEST_UPGRADE
ECHO First: %1 Second: %2
exit /B
Output example:
% of STATS.txt
Discount of 10% in all.txt
Normal file.txt
First: "% of STATS.txt" Second: " of STATS.txt"
First: "Discount of 10% in all.txt" Second: "Discount of 10 in all.txt"
First: "Normal file.txt" Second: "Normal file.txt"

Resources