Why does a specific windows batch parameter cause a crash? - windows

Contents of test.bat are:
setlocal EnableExtensions EnableDelayedExpansion
set param1=%~1
echo %param1%
Can someone explain why test.bat "^^!^^^&^^^^" makes the cmd window crash but test.bat "^^^&^^^^" has an expected result of setting &^ to variable param1?
I can do test.bat "pass^^!word" and I get the expected result of pass!word.
Update: test.bat "^^!^^^^^&^^^^^^^^" works. But I'm not completely sure why. This gets interpreted to set param1=^!^^&^^^^. Why does ^ need ^^^ in front of it?

You got many problems, as the special characters will be evaluated mulitple times in your case.
First in the set command, the special character phase will reduce your string "^^!^^^&^^^^" to
^!^&^^
But as delayed expansion is enabled and your string contains an exclamation mark,
another phase will be enabled to reduce the carets again to.
!&^
At this point param1 contains !&^, you can test it with set param1
But as you try to echo the value with echo %param1% another expansion will be executed.
And now you get a problem, as %param1% will expand to !&^,
The exclamation mark will be removed, as the second exlamation mark is missing for expanding a variable,
the ampersand will be treated as new command separator and
the ending caret will be treated as multiline character.
echo ! & ^<next line>
It's much safer to use the delayed expansion here, as this never change the content, as this phase is the last one of the parser.
setlocal EnableDelayedExpansion
set param1=%~1
set param1
echo !param1!
And all these explanations can be found at How does CMD.EXE parse scripts?

It is because the escape character for the Windows shell is ^, so:
"^^!^^^^^&^^^^^^^^"
Each ^^ becomes ^
Each ^& becomes &
So finally you will get:
"^!^^&^^^^"

Related

How to output special characters like angle brackets into an HTML/XHTML/XML file?

I want to put the following content into the file: <ScriptFile Make="3">
It fails for the reason of the string containing the angle brackets < and > and the double quote character ".
I have tried escaping the characters following way: ^<ScriptFile Make=""3""^>
It worked, but the output in the file was exactly the same as the escaped string.
The code snippet:
#echo off
set TEMP="^<ScriptFile Make=""3""^>"
echo %TEMP% > gen.xml
pause
How can I output the string value of TEMP variable into file gen.xml without loosing the double quotes and the angle brackets?
You can extract the angle brackets out of the variable, like this:
#echo off
set TEMP1=ScriptFile Make="3"
echo ^<%TEMP1%^> > gen.xml
pause
This way, the brackets can be escaped properly, you do not need any special escaping for the string put in the variable and the gen.xml looks like expected:
D:\temp>type gen.xml
<ScriptFile Make="3">
This worked for me:
#echo off
set "TEMP=^<ScriptFile Make="3"^>"
echo %TEMP% > gen.xml
pause
Another method would be to use delayed expansion:
#echo off
set "TEMP=<ScriptFile Make="3">"
setlocal EnableDelayedExpansion
echo !TEMP! > gen.xml
endlocal
pause
TEMP should not be used as environment variable name because TEMP is an important environment variable predefined by Windows. It has as value the name of the directory for temporary files of current user account with complete path. For details see Wikipedia article Windows Environment Variables.
One method is using delayed expansion as suggested also by Andriy M.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "TempVar=<ScriptFile Make="3">"
echo !TempVar!>gen.xml
endlocal
First a local environment is created for the next two command lines with command extensions and delayed expansion of environment variables enabled which are both needed here. Command extensions are enabled by default, but delayed expansion is disabled by default. See this answer explaining in detail what the commands SETLOCAL and ENDLOCAL do.
The string <ScriptFile Make="3"> is assigned next to environment variable TempVar. There is no need to escape the angle brackets or the double quotes. For a detailed explanation why there is no need to escape anything in this string and why first double quote character is left of variable name and not after equal sign read this answer.
The value of the environment variable is output by command ECHO with redirecting this output with the redirection operator > into the file gen.xml using delayed expansion.
There is no space character between second exclamation mark ! and redirection operator >. This avoids writing also a trailing space after <ScriptFile Make="3"> into the file. 1 or more space characters between > and file name gen.xml are ignored on parsing this command line. But any whitespace character left of redirection operator > is also output by command ECHO and for that reason also written into the file.
Another method is not using an environment variable at all and escape the angle brackets with character caret ^ as demonstrated below:
#echo off
echo ^<ScriptFile Make="3"^>>gen.xml
endlocal
Double quotes must not be escaped on using command ECHO as in this special case the double quote characters are interpreted as literal characters.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
endlocal /?
set /?
setlocal /?
And read also the Microsoft TechNet article Using command redirection operators.

Escape percent signs in given variables

My first post, most questions already solved using this friendly provided knowldge here. But now I run out of ideas, again with a question about handling of poison characters in cmd.exe.
Let's assume there is a given string variable enclosed in double quotes. Most poison characters has already been replaced by common chars before, the left ones disturbing the script are "&", "(", ")" and "%". The string must be echoed to a file without quotes afterwards. So I had the idea to escape the poison characters tripled:
#echo off & setlocal ENABLEEXTENSIONS
SET AlbumArtist=%1
CALL :EscapePoisonChars %AlbumArtist% AlbumArtist_VDN
SET "FlacHyperLink==hyperlink^("file://%AlbumArtist_VDN%"^;"LossLess"^)")
echo %FlacHyperLink%
echo %AlbumArtist_VDN%
endlocal &GOTO:EOF
:EscapePoisonChars
#echo off & setlocal ENABLEEXTENSIONS
SET TmpString=%1
SET TmpString=%TmpString:&=^^^&%
SET TmpString=%TmpString:(=^^^(%
SET TmpString=%TmpString:)=^^^)%
endlocal&SET %2=%TmpString:~1,-1%&GOTO :EOF
When I call my script above I get the expected output - apart from the missing percent sign:
G:\YAET\20130204_Work>TryAmper.bat "100% Rock & Roll (7' UpMix)"
=hyperlink("file://100 Rock & Roll (7' UpMix)";"LossLess")
100 Rock & Roll (7' UpMix)
G:\YAET\20130204_Work>
I know that the percent can be escaped by itself. So "%%" will normally lead to a single literal "%". But it was not possible for me to find a working replace procedure for percent signs because cmd always interprets it as a variable and tries to expand it. Is this the complete wrong direction to handle this issue or just misunderstanding of variable expansion? Any hints welcome! Thanks!
Cheers, Martin
Edit
Removed own code, see below Jeb's answer for clean solution.
Thanks for help, Martin
Nice question!
At first, yes you can replace even percent signs, but not within a percent expansion, you need a delayed expansion here.
Setlocal EnableDelayedExpansion
set tmpstr=!tmpstr:%=%%!
But if you use the delayed expansion, you don't need the escapes anymore, as the delayed expansion is the last phase of the batch parser and all characters lose any special meaning.
You only need to echo with delayed expansion.
Echo !tmpvar!
EDIT: Clean solution
#echo off
setlocal DisableDelayedExpansion
REM * More or less secure getting the parameter
SET "AlbumArtist=%~1"
setlocal EnableDelayedExpansion
SET "FlacHyperLink==hyperlink("file://!AlbumArtist!";"LossLess")"
echo !FlacHyperLink!
echo !FlacHyperLink!> hugo.txt
You need disableDelayedExpansion first, to get even exclamation marks from %1.
After that, you should switch to delayed expansion and use it anywhere.

Difference between %variable% and !variable! in batch file

I am writing a batch file where I need to output a string containing '!' to another file. But when I echo that string to another file, it removes "!" from the output.
Eg:
Input:
set LINE=Hi this is! output
echo !LINE!>>new_file.txt
Output in new_file.txt is:
Hi this is output
Also, if input is
set LINE=Hello!! this is output!!
echo !LINE!>>new_file.txt
Output in new_file.txt:
Hello
Hence, it skips the ! (Exclamation mark) from the output to the new_file.
If I use %LINE%, then it simply displays "echo is on" to the output file.
Please suggest a way to overcome this problem.
If you have delayed expansion enabled and want to output an exclamation mark, you need to escape it.
Escaping of exclamation marks needs none, one or two carets, depending on the placement.
#echo off
REM No escaping required, if delayed expansion is disabled
set test1=Test1!
setlocal EnableDelayedExpansion
REM One caret required
REM Delayed expansion uses carets independent of quotes to escape the exclamation mark
set "test2=Test2^!"
REM Two carets required
REM The first caret escapes the second caret in phase2 of the parser
REM Later in the delayed expansion phase, the remaining caret escapes the exclamation mark
set test3=Test3^^!
echo !test1!
echo !test2!
echo !test3!
The difference between !var! and %var% in blocks is explained at DOS batch: Why are my set commands resulting in nothing getting stored?
An explanation of the batch parser can be found at How does the Windows Command Interpreter (CMD.EXE) parse scripts?
It seems you have called SETLOCAL EnableDelayedExpansion somewhere higher in the code. Take a look here to see what the effects from that are.

Batch String Concatenation

I am trying to create a batch string like this: >abcd_
I have a variable called soeid, with value as abcd. So this is what i am doing, but it does not work.
set soeid=abcd
set "val1=>"
set "val2=_"
set "str=%val1%%soeid%%val2%"
echo %str%
I'm sure it is working just fine. To prove it, add SET STR after you define the value, and you will see the correct value.
The problem you are having is when you try to echo the value, the line that is executing becomes: echo >abcd_. The > is not quoted or escaped, so it is simply taking the ouput of ECHO with no arguments and redirecting it to a file named "abcd_"
If you don't mind seeing quotes, then change your line to echo "%str%" and it will work.
The other option is to enable and use delayed expansion (I'm assuming this is a batch script code, and not executing on the command line)
setlocal enableDelayedExpansion
set soeid=abcd
set "val1=>"
set "val2=_"
set "str=%val1%%soeid%%val2%"
echo !str!
Normal %var% expansion occurs early on while the interpreter is parsing the line. Delayed !var! expansion occurs at the end just before it is executed. The redirection is detected somewhere in the middle. That is why the normal expansion doesn't work - the interpreter sees the expanded > character and interprets it as the output redirection operator. The delayed expansion hides the > character from the interpreter until after redirection is parsed.
For more info about delayed expansion, type SET /? from the command line and read starting with the paragraph that starts with "Finally, support for delayed environment variable expansion...".

Why is delayed expansion in a batch file not working in this case?

This code
#echo off
setlocal EnableDelayedExpansion
set myvar=first
set first=second
echo myvar:!myvar!
set myvar=!myvar!
echo myvar:!myvar!
gives
myvar:first
myvar:first
on Windows Vista SP2.
The output I had expected is
myvar:first
myvar:second
Why the difference and how to obtain desired effect?
The problem is that set myvar=!myvar! expands to set myvar=first,
you set it with the same content, and then you ask echo myvar:!myvar! to show the content of myvar.
I will try to add some more explanations, even if Aacini and shf301 already answered the question.
Both showed the double expansion with the !%var%! construct, and Aacini explained why it can work, and why the reversed version %!var!% can't work.
IMHO there are four different expansions.
Delayed Expansion:
As Aacini explained the delayed expansion is safe against any special characters in the content (it can handle ALL characters from 0x01 to 0xFF).
Percent Expansion:
The percent expansion can't handle or removes some characters (even with escaping).
It can be useful for simple content, as it can expand variables after an endlocal barrier.
setlocal
set "myVar=simple content"
(
endlocal
set result=%myVar%
)
FOR-Loop-Parameters expansion:
It is safe, if the delayed expansion is disabled, else the delayed expansion phase is executed after the expansion of the %%a variables.
It can be useful, as it can expand variables after an endlocal barrier
setlocal EnableDelayedExpansion
set "var=complex content &<>!"
for /F "delims=" %%A in ("!var!") DO (
endlocal
set "result=%%A"
)
SET Expansion:
set var expands also a variable, and it is always safe and works independent of the delayed expansion mode.
Aacini just explained how the call %%%var%%% construct work, I only want to give some additional remarks.
call is stackable, you can use many of them and each restarts the parser.
set "var=%%var%%#"
call call call call call echo %var%
results to %var%######
But call have many disadvantages/side effects!
Each call double all carets ^
You can say: "Hey I've tested it and I can't see any doubling"
call call call call echo ^^
result ^
Nevertheless it's true, but it's mostly hidden, as each restart also have a special character phase where carets escapes the next character, but you can see the doubling effect with
call call call call echo "^^"^^
result "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"^
Even if a call expansion restarts the parser, you can never use the delayed expansion in any phase (only in the first one).
call stops working if it detects unescaped special characters.
echo you ^& me
call echo you & me
call echo you ^& me
call echo you ^^& me
call echo you ^^^& me
Only the first results to the output you & me, all the others fails.
Another problem is that call is extremly slow, a call set var=content is ~50times slower than set var=content, the cause is that call try to start an external program.
#echo off
setlocal
(
echo echo *** External batch, parameters are '%%*'
) > set.bat
set "var="
call set var=hello
set var
I hope it was interesting a bit ...
And if you want to go more in depth you can read CALL me, or better avoid call
and How does the Windows Command Interpreter (CMD.EXE) parse scripts?
This problem is not directly related to Delayed variable Expansion, but to the fact that two value expansions are required: the first one give the variable name and the second one must replace this name by its value. The direct way to do that is via two expansions in the same line as shown in the previous answer: set myvar=!%myvar%! that works because %var% expansion is done before the command-line is analyzed for execution whereas !var! expansion is done later, just before the command is executed (hence the "delayed" name). This mean that %var% expansion may provide parts of the command and may cause syntax errors, but !var! not. For example if %var%==value ... cause an error if var is empty or have spaces, but if !var!==value ... never cause a syntax error.
The double expansion of values may be achieved in other ways that does not involve Delayed variable Expansion. For example, we may create an auxiliary Batch file that do the second expansion:
echo myvar:%myvar%
echo set myvar=%%%myvar%%%> auxiliary.bat
call auxiliary
echo myvar:%myvar%
Previous method may be used to do a third or even deeper level expansions, and even be combined with Delayed Expansions to create very complex value managements. This matter is not just a curiosity, but the key to access array elements or linked lists. For example:
set month[1]=January
set month[2]=February
. . .
set month[12]=December
for /f "tokens=1-3 delims=/" %%a in ("%date%") do echo Today is !month[%%a]! %%b, %%c
What you're trying to do won't work - delayed expansion only changes the variable expansion behavior of a variable inside of a block. It doesn't allow you the aliasing/nesting (for a lack of a better word) that you are attempting.
set myvar=first sets the variable myvar to the text "first". set first=second sets the variable first to the text "second. There is no link between those two lines. myvar will never evaluate to something that it wasn't explicitly set to.
I don't believe there is anyway to accomplish what you are trying to do here.
* Edit *
OK after taking a look at your answer I seeing how that works, you can get your desired output with this:
#echo off
setlocal EnableDelayedExpansion
set myvar=first
set first=second
echo myvar:%myvar%
set myvar=!%myvar%!
echo myvar:%myvar%
So the magic seems to happen because of the way that standard and delayed expansion occur. The line set myvar=!%myvar%! is seems be expanded first by the standard expander to set myvar=!first! (you'll see this if you run the script with echo on). Then the delayed expander runs and expands !first to "second" and set's myvar to that.
I have no idea if this is documented behavior as to how standard and delayed expansion should work or just an implementation detail (which means it could break in the future)

Resources