How can I escape an exclamation mark ! in cmd scripts? - batch-file

When I have setlocal ENABLEDELAYEDEXPANSION set in a cmd script is there any way I can escape a ! that I want to use as a parameter to a command?
#echo off
setlocal ENABLEDELAYEDEXPANSION
echo I want to go out with a bang!
echo I still want to go out with a bang^!

That's what I found (^^)
#echo off
setlocal ENABLEDELAYEDEXPANSION
echo I want to go out with a bang^^!

An additional remark to the answer of FrVaBe.
Normally the ^^! works, but in quotes you only need ^! instead.
echo I want to go out with a bang^^!
echo He said "Bang^!"
This is a result of the escape mechanism of the batch parser.
First the parser parses a line and the caret escapes the next character, in this case it has an effect for &|<>()"<linefeed>, but only outside of quotes, as inside of the quotes all characters are "normal" and the caret itself has no effect.
With delayed expansion an extra parse step follows, there is the caret also an escape character for the next character, but only affects the ! and ^, and quotes are ignored in this parsing step.
This extra step will be executed only, if there is at least one ! in the line.
setlocal DisableDelayedExpansion
echo DisableDelayedExpansion
echo one caret^^
echo one caret^^ bang! "boom^!"
echo(
setlocal EnableDelayedExpansion
echo EnableDelayedExpansion
echo one caret^^
echo none caret^^ bang^^! "boom^!"
---- OUTPUT ------
DisableDelayedExpansion
one caret^
one caret^ bang! "boom^!"
EnableDelayedExpansion
one caret^
none caret bang! "boom!"
EDIT
Here is a slightly modified example that better illustrates the various escape permutations that are required, depending on the context. The only case that requires unusual escaping is the last example when delayed expansion is on and there exists at least one ! on the line.
#echo off
setlocal DisableDelayedExpansion
echo DisableDelayedExpansion
echo caret^^ "caret^"
echo caret^^ bang! "caret^ bang!"
echo(
setlocal EnableDelayedExpansion
echo EnableDelayedExpansion
echo caret^^ "caret^"
echo caret^^^^ bang^^! "caret^^ bang^!"
-- OUTPUT --
DisableDelayedExpansion
caret^ "caret^"
caret^ bang! "caret^ bang!"
EnableDelayedExpansion
caret^ "caret^"
caret^ bang! "caret^ bang!"

To use an explanation point in batch with Delayed Expansion enabled, you must first add the explanation point to a variable with it disabled. See the below example with both DISABLEDELAYEDEXPANSION and ENABLEDELAYEDEXPANSION state.
#echo off
setlocal DISABLEDELAYEDEXPANSION
set DB_password=encrypt!Pws
echo %DB_password%
SETLOCAL ENABLEDELAYEDEXPANSION
echo !DB_password!

Thanks. To add to this valuable point, if one's script contains a variable whose value contains an "!", then the following approach will render that value as-is:
#echo off
SETLOCAL EnableDelayedExpansion
set /P omg=Enter a value that contains an exclamation-point:
echo Traditional: %omg%
echo Alternative: !omg!
pause

Related

How to replace the % character with something else with the SET command

So I know how to do simple string replacing with the SET command.
Example:
set /p a=
set a=%a:<=^<%
echo %a%
(This example will change the prompted variable to the same thing but with the < character to be ^< to be be properly echoed if needed to)
I want to do the same thing but with the % character but I can't get it to work.
#ECHO OFF
SETLOCAL
SET "something=%%he%%llo%%"
ECHO %something%
ECHO ============
SETLOCAL ENABLEDELAYEDEXPANSION
SET "anotherthing=!something:%%=x!"
endlocal&SET "anotherthing=%anotherthing%"
ECHO %something%
ECHO %anotherthing%
GOTO :EOF
Like this, you mean?
There was nothing wrong with your method, short of using doublequotes to protect from poison characters.
The main issue now, is in trying to see the content, because you'd be Echoing poison characters. To see it actually in place I included two methods, a Set command to list all variables beginning with a, (with a findstr thrown in to isolate only the one named a), and by using delayed expansion, which prevents the variable from being expanded when the command line is read/parsed. At the very bottom I included a method of showing it with the Echo command without using delayed expansion or doublequotes, as you can see to escape a caret, ^, you'll need in this case to use two more carets
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "a="
Set /P "a="
Set "a=%a:<=^<%"
Echo(%a%
Pause
(Set a) 2> NUL | %SystemRoot%\System32\findstr.exe /B /L /I "a="
Pause
SetLocal EnableDelayedExpansion
Echo(!a!
EndLocal
Pause
Echo(%a:^<=^^^<%
Pause
The full (known) rules for variable replacement can be found at SO:
Percent Expansion Rules
In short:
The search expression in a percent replacement expression can not start with a percent.
The search expression in a delayed replacement expression can not start with a exclamation mark.
But you can replace percent signs with delayed expression
setlocal EnableDelayedExpansion
set /p var=
set "var=!var:%%=REPLACE!"
echo(!var!
The percent sign itself has to be doubled here, because it collapse to a single percent in phase2 (percent expansion)

Can't escape semicolon inside quotes in batch file using enableDelayedExpansion

I'm manipulating some HTML via Batch. I'm using delayed expansion but when a semicolon is encountered inside double quotes, the script fails to copy anything after and including the semicolon.
It's probably due to the fact that I use double quotes when I pass the variable to the putLineInHTMLFile label (I need to keep things separated in labels).
#echo off
setlocal enableDelayedExpansion
del output.html
for /f "delims=" %%x in (file.html) do call :putLineInHTMLFile "%%x"
goto :EOF
:putLineInHTMLFile
set "line=%~1"
echo !line!>> output.html
file.html contains:
<tag1>
<tag"bla;2">
After running the script, output.html contains:
<tag1>
<tag"bla
I've tried escaping the semicolon with ^ or ^^. Didn't work. I've tried escaping the double quotes too. That didn't work either.
I can change the contents of file.html anyway I please just as long as I can include that semicolon in the output file.
This seems to work for the test-case given; no guarantees for wider use:
#echo off
setlocal enableDelayedExpansion
del output.html
for /f "delims=# tokens=*" %%x in (file.html) do (
set "safe=%%x"
set "safe=!safe:"=""!"
call :putLineInHTMLFile "!safe!"
)
goto :eof
:putLineInHTMLFile
set "line=%~1"
set "line=%line:""="%"
echo !line!>> output.html
:eof
Within the "body" of the for command, the %%x has not been split, it's only when processed by the call command that this happens. To protect that, I've used safe to double-up all double-quotes in the string, and then added a line in the subroutine to strip them out again.
This doesn't work properly if the double-quotes aren't matched, but in those cases, neither does the echoing of the trailing >, even when there are no semi-colons present.
This method works in all cases, as long as the quotes be matched (even number) in the input lines:
#echo off
setlocal enableDelayedExpansion
del output.html
for /f "delims=" %%x in (file.html) do call :putLineInHTMLFile "%%x"
goto :EOF
:putLineInHTMLFile
set line=%*
echo !line:~1,-1!>> output.html
PS - Please, do not include additional parts that not appears in the original code, like the :eof label... The label is written in uppercase letters in goto :EOF command to indicate that it is special. Type goto /? for further details.
Certainly. The problem is that the subroutine receives
"<tag"bla;2">"
for that line. The parser sees that as
Token1 : "<tag"bla
Token2 : 2">"
because ; like Space is a separator.
Sadly, without knowing what you intend to do within the subroutine, it's difficult to advise on how to circumvent the problem.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q43391363.txt"
SET "outfile=%destdir%\outfile.txt"
SET "outfile2=%destdir%\outfile2.txt"
del "%outfile%"
del "%outfile2%"
(for /f "delims=" %%x in (%filename1%) do set "line=%%x"&call :putLineInHTMLFile "%%x")>"%outfile2%"
goto :eof
:putLineInHTMLFile
ECHO %*
echo !line!>>"%outfile%"
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances.
I used a file named q43391363.txt containing your data for my testing.
Produces files defined as %outfile% and %outfile2%
So - here's two different ways, one using conventional output direct to outfile1 and the other using redirection from a subroutine into outfile2.

Passing exclamation marks as parameters in batch subroutine call

Thanks to this community I have finally learned how to escape exlamation marks for immediate use in a batch delayedExpansion block.
(use two escape carets not just one, awesome)
But I can't seem to find or figure out how to pass the contents of a variable containing an exclamation mark as parameter to a batch subroutine.
example:
#echo off
setLocal EnableDelayedExpansion
set variable=Hello^^!
echo "!variable!"
call :subroutine "!variable:^^!=^^!!"
pause
exit
:subroutine
echo "%~1"
exit/b
Output:
"Hello!"
"Hello"
Press any key to continue . . .
I want the second "Hello" to include an exclamation mark.
I have tried various permutations of substring replacement on line 5 to no avail.
help
You need a different way for the variable replacing, and much more carets.
#echo off
setLocal EnableDelayedExpansion
set variable=Hello^^!
echo "!variable!"
call :subroutine %variable:!=^^^^^^^^^^!%
exit /b
:subroutine
echo %~1
exit /b
Or with quotes:
call :subroutine "%variable:!=^^^!%"
In your function you need to expand %1 without any quotes, as the number of carets are always odd in a CALL parameter.
But at all it's a bad idea to try such things.
I agree with Aacini, that you should use pass by reference instead.
This is the only way to handle any possible content.
#echo off
setLocal EnableDelayedExpansion
set variable=Hello^^!
echo "!variable!"
call :subroutine variable
exit /b
:subroutine
echo !%1!
exit /b
Maybe the problem is not how to pass the data to the subroutine, but how to get the data inside it
#echo off
setlocal enabledelayedexpansion
set "var=Hello^!"
setlocal disabledelayedexpansion
echo %var%
call :echo1 %var%
call :echo2 var
endlocal
setlocal enabledelayedexpansion
echo !var!
call :echo1 !var!
call :echo2 var
endlocal
endlocal
exit /b
:echo1
setlocal disabledelayedexpansion
echo %~1
endlocal
goto :eof
:echo2
setlocal enabledelayedexpansion
echo !%~1!
endlocal
goto :eof

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"

Temporarily interrupt SETLOCAL

I have a script that has a lot of use of the SETLOCAL ENABLEDELAYEDEXPANSION command, so I start the script off that way (less headaches). However, it does not allow you to use the ! character without escaping each instance of it (and I want to create a long line of !s for an error logging section =D ) and I don't want to escape every one of them.
Is there a way to temporarily break out of SETLOCAL, then reenter it keeping all previously created variables within the original SETLOCAL?
For example:
SETLOCAL ENABLEDELAYEDEXPANSION
set var=HELLO
ECHO %var%
ENDLOCAL
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO %var%
The 2nd ECHO will not give you the previous value of var
EDIT: ^ will not allow you to escape the ! inside SETLOCAL ENABLEDELAYEDEXPANSION
setlocal DisableDelayedExpansion
set var=Value! with! many! Bangs!
setlocal EnableDelayedExpansion
echo !var!
You can nest it like Aacini shows it.
Or you can use the return technic or escape ! inside a EnableDelayed block with ^^!
Setlocal EnableDelayedExpansion
echo 1^^!^^!^^!^^!^^!^^!^^!
REM *** Or use a self removing quote
echo !="! 2^!^!^!^!^!^!
Setlocal DisableDelayedExpansion
echo 3 !!!!!!!!
set var=Hello
(
endlocal
rem Bring the value behind the endlocal barrier
set var=%var%
)
echo var is still there, %var%
The return technic can also be used for exclamation marks, but then it is a bit more complex.
It can be found at Make an environment variable survive ENDLOCAL

Resources