In the following script i pass a qouted string to a subroutine and unquote it (with a tilde when accessing the parameter) to output it unquoted. Unfortunately my attempt to escape greater then sign(s) doesn't work:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
set foo="Argument -> Not provided^!"
call :output_actual_error !foo!
ENDLOCAL
EXIT /B 0
:output_actual_error
SETLOCAL DISABLEDELAYEDEXPANSION
set bar=%~1
set bar=%bar:>=^>%
echo %bar%
ENDLOCAL
EXIT /B 0
Output:
Argument - provided!
Expected:
Argument -> Not provided!
Please be aware that the code is just for illustration and DOES NOT represent the actual implementation.
So, how can escape the greater-than sign and echo the string without double?
Use
set "bar=%~1"
set "bar=%bar:>=^>%"
A solution is already provided by this answer. However, I want to show you how I would write the code to make it as safe as possible against all combinations of all kinds of special characters, by using proper quotation and enabling and applying delayed expansion only where it is really necessary and disabling it where it is disturbing or unsafe:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem /* No delayed expansion during immediate variable assignment,
rem so escaping of exclamation marks is not required;
rem quotation in a way not to become part of the value: */
set "foo=Argument -> Not provided!"
setlocal EnableDelayedExpansion
rem /* Delayed expansion during reading of variable;
rem quote argument value here to protect white-spaces: */
call :output_actual_error "!foo!"
endlocal
endlocal
exit /B 0
:output_actual_error
rem /* No delayed expansion during argument expansion (%);
rem quotation in a way not to become part of the value: */
setlocal DisableDelayedExpansion
set "bar=%~1"
setlocal EnableDelayedExpansion
rem // Delayed expansion during reading of variable:
echo(!bar!
endlocal
endlocal
exit /B 0
This basically avoids delayed expansion during variable assignment and %-expansion and uses delayed expansion whenever a variable is read.
There still remain risks of failure:
One is introduced by call, because it doubles quoted carets (^).
Another one is represented by the argument expansion (%~1), which may cause trouble with quotes, particularly when they appear unbalanced.
Related
Can someone give me an example of where a batch script would act differently with or without delayed expansion? Are there any situations where you would NOT want to use delayed expansion? Thanks.
Look at the following examples...
Example 1: The following code DOESN'T use delayed expansion, so the variables in the for loop are expanded only one time. This means that %Count% will always expand to 0 in each iteration of the loop, no matter what we do to it with the set command:
#echo off
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=%COUNT% + 1
echo Count = %COUNT%
)
pause
So this script will output:
Count = 0
Count = 0
Count = 0
Count = 0
This is not how this loop is supposed to work.
Example 2: On the other hand, if we use delayed expansion, we have the following script, which will run as expected.
setlocal ENABLEDELAYEDEXPANSION
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=!COUNT! + 1
echo Count = !COUNT!
)
pause
and, as expected, it will output:
Count = 1
Count = 2
Count = 3
Count = 4
When you use the ENABLEDELAYEDEXPANSION, and expand a variable using ! instead of %, the variable is re-expanded each time, and everything works as it's supposed to.
I wanted to add a great example on how "EnableDelayedExpansion" (EDE) can be useful outside of the ubiquitous FOR loop examples.
Here is a line of earthquake data that I wish to parse (I call it it 1line.txt)
ak_11574812 2015.04.29.193822 62.9525 -148.8849 1.0 9.5 1 49km S of Cantwell, Alaska
The problem I ran into was that last segment of this line does not always start at the same column number. So I needed to create a flexible SET command that will accurately pluck out the last segment of this line.
ECHO OFF
setlocal enableDelayedExpansion
set where=72
set /p line=<1line.txt
set locate=!line:~%where%,28!
echo %locate%
EDE allows me to place a variable (where) inside another variable (line). EDE will translate the variable bracketed by % first, then process the variable bracketed by ! and (in this case) push out the results into the "locate" variable.
Max's answer gives an example of where a batch script would act differently with or without delayed expansion.
For the sake of completeness, let's answer another part of the question and show a situation where you would NOT want to use delayed expansion when your data contain an exclamation mark ! (and show two ways of processing such data):
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_auxFile=%temp%\%~n0.txt"
rem create multiline sample file
>"%_auxFile%" ( for /L %%G in (1,1,3) do echo line %%G is 100%% valid! Sure! Hurrah!)
rem create one-line sample file
>"%_auxFile%" echo this line is 100%% valid! Sure! Hurrah!
echo(
echo --- file content
type "%_auxFile%"
echo(
SETLOCAL EnableDelayedExpansion
echo --- enabled delayed expansion chokes down unescaped exclamation marks^^^! "^!"
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%~G"
echo loop var=%%~G
echo _auxLine=!_auxLine!
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- toggled delayed expansion works although might be laborious!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
SETLOCAL EnableDelayedExpansion
echo _auxLine=!_auxLine!
ENDLOCAL
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- keep delayed expansion DISABLED: use CALL command!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
call :ProcessVar
)
ENDLOCAL
rem delete the sample file
del "%_auxFile%"
ENDLOCAL
goto :eof
:ProcessVar
echo _auxLine=%_auxLine%
echo WARNING: neither !_auxLine! nor %%G loop variable is available here!
goto :eof
Note that above script shows proper ways of escaping
% percent sign by %% doubling it (delayed expansion does not matter), and
! exclamation mark if delayed expansion is enabled:
"^!" if enclosed in a pair of double quotes, then use the cmd and batch-script general escape character ^ caret;
^^^! otherwise, use three ^ carets.
Output:
==> D:\bat\SO\10558316.bat
--- file content
this line is 100% valid! Sure! Hurrah!
--- enabled delayed expansion chokes down unescaped exclamation marks! "!"
loop var=this line is 100% valid Hurrah
_auxLine=this line is 100% valid Hurrah
--- toggled delayed expansion works although might be laborious!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
--- keep delayed expansion DISABLED: use CALL command!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
WARNING: !_auxLine! as well as %G loop variables are not available here!
==>
As pointed in the answer the main usage of the delayed expansion is the setting and accessing variables in brackets context.
Though it can be useful in another situations too.
Parametrizing substring and string substitution:
#echo off
setlocal enableDelayedExpansion
set "string=test string value"
set start=5
set get_next=6
echo #!string:~%start%,%get_next%!#
set "search_for=string"
set "replace_with=text"
echo #!string:%search_for%=%replace_with%!#
the output will be:
#string#
#test text value#
though this can be achieved with additional call this way is more performant
Using shift command within brackets parameterized argument access
#echo off
echo first attempt:
(
echo "%~1"
shift
echo "%~1"
)
::now the shift command will take effect
setlocal enableDelayedExpansion
echo second attempt:
(
set /a argument=1
call echo %%!argument!
shift
call echo %%!argument!
)
the output will be:
first attempt:
"first argument"
"first argument"
second attempt:
"second argument"
"third argument"
As you can see parameterized argument access can be done only with delayed expansion.
Using for tokens (or function arguments) for parameterization
One more approach for mixing !s and %s this could be useful for nested loops:
#echo off
setlocal enabledelayedexpansion
set begin=2
set end=2
set string=12345
for /f "tokens=1,2" %%A in ("!begin! !end!") do set "string2=!string:~%%A,%%B!"
echo !string2!
endlocal
as you can see now the for command tokens are used as parameters.
Several answers here answer the "How to use delayed expansion?" question or what happen if you don't use delayed expansion. However, the second question is "Are there any situations where you would NOT want to use delayed expansion?" and a couple answers take this question as "how to avoid the problems caused by using delayed expansion?"
My answer answers the question as I understand it: "In which situations is better to NOT use delayed expansion (instead of use it)?"
If you want to exchange the contents of two variables, the simplest way to perform this is using the %standard% variable expansion:
set "var1=%var2%" & set "var2=%var1%"
The way that the %standard% expansion works makes possible to achieve this replacemenmt without using any auxiliary variable. As far as I know, the Batch-file "programming language" is the only one that allows to perform this exchange in this way, that is, making good use of a language "feature" (not via a specialized "exchange" instruction/statement).
I need to do a very simple thing: substitute one frase with another. This must be done by CMD batch file (for Windows 7). The frase to be subsitituted can be in any position in txt file line (and in many lines).
The problem is the frase to be substituted contains ":" and "!" characters. I am not very skilled in batch files (to put it mildly), though I spend some hours especially to learn about this specific problem. It looks very complicated for me. At last, by chance, I overided the issue, but... I feel it is a barbarity how I did it.
The real line with the frase which should be substituted is e.g.:
"21:12:45 WARNING: No video signal present!"
The frase which should be substituted is:
"WARNING: No video signal present!"
The frase, which it should be substituted with is:
"Recognition suspended"
I have found this code: https://www.computerhope.com/forum/index.php?topic=41188.0
It works fine, except cannot work with "!" as I see, and escape char "^" never works. But I noticed that although it doesn't work properly - it trims the exclamation mark. Here are real strings before (b) and after (a):
(b)20:42:18 WARNING: No video signal present!
(a)20:42:18 WARNING: No video signal present
So I add 2 other lines to the code and this does the thing. The whole code is now:
#echo off
setlocal enabledelayedexpansion
set txtfile=D:\wfc\testlib\test.txt
set newfile=D:\wfc\testlib\new_test.txt
if exist "%newfile%" del /f /q "%newfile%"
for /f "tokens=*" %%a in (%txtfile%) do (
set newline=%%a
set newline=!newline:No video signal present!=!
set newline=!newline:No video signal present=!
set newline=!newline:WARNING:=Suspend recognition!
echo !newline! >> %newfile%
)
First crucial line cuts "!",
second line substitutes "No video signal present" with nothing (trims it),
third line substitutes the rest "Warning:" with desirable "Suspend recognition".
And at the end I have:
(b)20:42:18 WARNING: No video signal present!
(a)20:42:18 Suspend recognition
I feel this could be done elegantly. Besides I am not sure, if my way is not dangerous for some reason (data damage etc.). Please help.
Yes, this can be done easier.
First of all, it is not the line set newline=!newline:No video signal present!=! that removes the ! character, but it is the line set newline=%%a, because there is delayed variable expansion enabled when the for variable reference %%a is expanded, which happens before delayed expansion, hence an orphaned exclamation mark appears that is simply removed.
The key to success is to disable delayed expansion during expansion of %%a and to enable it afterwards. In the sub-string replacement syntax the : does not cause any problems; neither does the ! in the search string, given it is not the first character. So the following code should work:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "txtfile=D:\wfc\testlib\test.txt"
set "newfile=D:\wfc\testlib\new_test.txt"
> "%newfile%" (
for /F usebackq^ delims^=^ eol^= %%a in ("%txtfile%") do (
set "newline=%%a"
setlocal EnableDelayedExpansion
echo(!newline:WARNING: No video signal present!=Recognition suspended!
endlocal
)
)
endlocal
exit /B
I also changed the following items:
improved the syntax of set by using quotes to avoid trouble with some special characters;
avoided interim variable newline, so did the string manipulation directly in the echo line;
quoted file path/name to avoid problems with white-spaces, hence using usebackq option;
replaced the tokens=* by the delims= option in order to keep leading white-spaces; to avoid loss of lines that begin with ; due to the default eol option, I used the odd-looking unquoted option string syntax as this is the only way (!) to have no delims and eol defined at the same time (you cannot write "delims= eol=" as this would define " as the eol character; "eol= delims=" would define the SPACE, and "eol=delims=" the d);
redirected whole for loop, so no initial file deletion necessary, and the performance is a lot better, because the output file has to be opened and closed once only, not in every iteration;
echo Text > "file.ext" returns a trailing SPACE (actually the one in front of >); this is avoided by the above item; in general, this can be avoided when using the reversed redirection syntax > "file.ext" echo Text;
echo !VAR! returns ECHO is on|off. in case variable VAR is empty; to avoid this or other unexpected output, the odd-looking but safe syntax echo(!VAR! is used;
avoided immediate (%) expansion when delayed expansion is enabled to avoid loss of !;
To keep empty lines that appear in the original file, change the code to this:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "txtfile=D:\wfc\testlib\test.txt"
set "newfile=D:\wfc\testlib\new_test.txt"
> "%newfile%" (
for /F "delims=" %%a in ('findstr /N /R "^" "%txtfile%"') do (
set "newline=%%a"
setlocal EnableDelayedExpansion
set "newline=!newline:WARNING: No video signal present!=Recognition suspended!"
echo(!newline:*:=!
endlocal
)
)
endlocal
exit /B
The findstr command is used to prefix every line of the input file by a line number plus a colon (/N option). Then the sub-string replacement is done. Finally, everything up to and including the first :, hence the line number prefix becomes removed.
Substituting a phrase is pretty easy to do in a .bat file script.
powershell -NoLogo -NoProfile -Command ^
"Get-Content -Path '.\subfrase-in.txt' |" ^
"ForEach-Object { $_ -replace 'WARNING: No video signal present!', 'Recognition suspended' }"
#echo off
setlocal EnableDelayedExpansion
set "folder=C:\Test\Test01\Test02\Test03\Test04\!!Test05\Test06"
md %folder%\Final
echo %folder%
pause
This will create in C:\Test\Test01\Test02\Test03\Test04\Test05\Test06\Final.
Result should be like C:\Test\Test01\Test02\Test03\Test04\!!Test05\Test06\Final.
How can I use special characters (!!Test05) for making folder in batch file?
When delayed expansion is enabled, exclamation marks are lost when stating them literally or when expanding normal %-variables. Toggle delayed expansion and read affected variables with surrounding ! only to overcome this:
#echo off
setlocal DisableDelayedExpansion
rem // Delayed Expansion is disabled, so literal exclamation marks are maintained:
set "folder=C:\Test\Test01\Test02\Test03\Test04\!!Test05\Test06"
setlocal EnableDelayedExpansion
rem /* Delayed Expansion is enabled, so do not expand variables with `%`,
rem or you will lose exclamation marks: */
md "!folder!\Final"
endlocal
rem /* Delayed Expansion is disabled, variables can be expanded with `%`,
rem without losing exclamation marks: */
echo "%folder%"
endlocal
for this, since windows treats ! as a special character you are going to need to escape it. the ^ symbol is an escape character so something like:
#echo off
setlocal EnableDelayedExpansion
set "folder=C:\Test\Test01\Test02\Test03\Test04\^!^!Test05\Test06"
md %folder%\Final
echo %folder%
pause
Should work fine. Take a look here: https://ss64.com/nt/syntax-esc.html
Your command works fine if you remove the unused and unwanted second line. ! is not a special character in Windows but it is interpreted in a special way when you have enabled delayed expansion.
#Echo Off
Set "folder=C:\Test\Test01\Test02\Test03\Test04\!!Test05\Test06"
MD "%folder%\Final"
Echo=%folder%
Timeout -1
Delayed Expansion is off by default although this can be changed. If you need to ensure that your ! are seen correctly then the safest option would be to add the line SetLocal DisableDelayedExpansion prior to your specific code section.
As far as I know, I need to escape every escape characters when echoing them. The ^ method works fine for a few echoes. (which should be something like:)
#echo ^|
#echo ^> ^>^>
However, when there are a lot of characters to escape, the ^ method won't work anymore. So, my question is:
Are there any ways escape all special characters without "spamming" the caret?
As mentioned by Mofi, you could use variables with delayed expansion to echo any content.
There are some more possible ways.
1) Disappearing quotes
for /F %%^" in ("""") do (
echo %%~"Hello^you
echo %%~"Line2 |&<>
)
This works as the %~" will quote the rest of the line, but after expansion it will disappear
2) When you want to echo multiple lines you should read about the different variants of heredoc
3) The MagicEcho can display any content without escaping or doubling percent signs like
%magicEcho% "^!%path%<>" ^%!%<> ^
Well, there is no need to escape redirection operators and other special characters listed in last paragraph in help output by running cmd /? in a command prompt window on last help page when the string to output is enclosed in double quotes.
But using " on line with ECHO command results in having also the double quote output.
There are several solutions.
The first one is assigning the string to output to an environment variable and output the value of the environment variable using delayed expansion.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "Line=pipe = | and percent sign = %% and exclamation mark ^!"
echo !Line!
set "Line=redirection operators: < and > and >>"
echo !Line!
endlocal
Or a little bit shorter, but not so good readable:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "Line=pipe = | and percent sign = %% and exclamation mark ^!" & echo !Line!
set "Line=redirection operators: < and > and >>" & echo !Line!
endlocal
Note: % and ! must be nevertheless escaped with another % and with ^ to be interpreted as literal character in string assigned to environment variable Line.
Another solution using a subroutine PrintLine:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
call :PrintLine "pipe = | and percent sign = %%%% and exclamation mark !"
call :PrintLine "redirection operators: < and > and >>"
endlocal
goto :EOF
:PrintLine
set "Line=%~1"
setlocal EnableDelayedExpansion
echo !Line!
endlocal
goto :EOF
The disadvantages of this solution are:
A percent sign must be defined with 4 percent signs to be finally printed as literal character.
It is slower because of usage of SETLOCAL and ENDLOCAL on printing each line.
Read this answer for details about the commands SETLOCAL and ENDLOCAL.
One more solution according to comment by JosefZ uses command FOR for an implicit delayed expansion:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%I in (
"pipe = | and percent sign = %% and exclamation mark !",
"redirection operators: < and > and >>"
) do echo %%~I
endlocal
The lines to output are specified in a comma separated list of double quoted strings for being processed by FOR.
It has the big advantage that just the percent sign must be escaped with an additional percent sign on delayed expansion being disabled. But the string to output can't contain a double quote with exception of "" within string.
Thanks JosefZ for this contribution.
Other great solutions are provided by jeb in his answer.
I want to execute some program passing an argument. The argument changes depending of the day of week and it is an url
Code:
#echo off
setlocal enableDelayedExpansion
for /f %%a in ('wmic path win32_localtime get dayofweek /format:list ^| findstr "="') do (set %%a)
if %dayofweek% == 7(
EXIT
)
set link="http://backlog.telecom.pt/maps/selector/download?map_name=workline_fornecedores&organization_id=1"
if %dayofweek%==5 (
set link="http://backlog.telecom.pt/maps/selector/download?map_name=all_fornecedores&organization_id=1"
)
REM start /wait D:\Planview\Services\BackLog_Integration_Client_Service\Backlog_Integration_Client_Exe\Backlog_Integration_Client_Exe.exe %link%
REM timeout /t 600 /nobreak > NUL
REM start D:\Planview\Services\PV_Backlog_ProcessData_Service\PV_Backlof_ProcessData_Exe\PV_Backlog_ProcessData_Exe.exe
I read that ^ before & would work to escape the & char, but for me it never did and the only way i managed to do it was enableDelayedExpansion and encapsulate the url in ", but this brought me a problem.. my variable instead of having the url has "url".
I tried to do set link=%link:"% but it did not worked.
I'll try to give you some advice via simple examples:
#setlocal enableDelayedExpansion
rem This fails because & is a "poison" character (an instruction or operator)
echo abc&xyz
rem This succeeds because & is escaped
echo abc^&xyz
rem This succeeds because & is quoted
echo "abc&xyz"
rem This succeeds, but it consumes the escape: stored value = abc&xyz
set test=abc^&xyz
rem So this fails because & is not escaped
echo %test%
rem You could "fix" above by double escaping when doing SET so stored value = abc^&xyz
rem But I don't like this - I pretty much never do it
set test=abc^^^&xyz
rem Now this works
echo %test%
rem Better to single escape and use delayed expansion.
set test=abc^&xyz
rem This works because poison characters don't bother delayed expansion
echo !test!
rem You could use quotes instead of escape, but now quotes are in value
set test="abc&xyz"
rem Delayed expansion is not needed because value is quoted
echo %test%
rem Putting the leading quote before the variable still quotes everything
rem But now the quotes are not in the value, as desired. Value = abc&xyz
set "test=abc&xyz"
rem Now you must use delayed expansion because value is not quoted
echo !test!
So the general rules of thumb that I like to use when poison characters are involved:
Use quotes around the entire assignment when SETting a variable: set "var=value"
Use delayed expansion when expanding a variable: !var!
Wouldn't it be nice if those rules solved everything. But of course batch is not that simple. There are situations where these simple rules will fail, but it should be enough to get you started.