Escape angle brackets in a Windows command prompt

Solution 1:

The Windows escape character is ^, for some reason.

echo some string ^< with angle ^> brackets >>myfile.txt

Solution 2:

True, the official escape character is ^, but be careful because sometimes you need three ^ characters. This is just sometimes:

C:\WINDOWS> echo ^<html^>
<html>

C:\WINDOWS> echo ^<html^> | sort
The syntax of the command is incorrect.

C:\WINDOWS> echo ^^^<html^^^> | sort
<html>

C:\WINDOWS> echo ^^^<html^^^>
^<html^>

One trick out of this nonsense is to use a command other than echo to do the output and quote with double quotes:

C:\WINDOWS> set/p _="<html>" <nul
<html>
C:\WINDOWS> set/p _="<html>" <nul | sort
<html>

Note that this will not preserve leading spaces on the prompt text.

Solution 3:

There are methods that avoid ^ escape sequences.

You could use variables with delayed expansion. Below is a small batch script demonstration

@echo off
setlocal enableDelayedExpansion
set "line=<html>"
echo !line!

Or you could use a FOR /F loop. From the command line:

for /f "delims=" %A in ("<html>") do @echo %~A

Or from a batch script:

@echo off
for /f "delims=" %%A in ("<html>") do echo %%~A

The reason these methods work is because both delayed expansion and FOR variable expansion occur after special operators like <, >, &, |, &&, || are parsed. See How does the Windows Command Interpreter (CMD.EXE) parse scripts? for more info.


sin3.14 points out that pipes may require multiple escapes. For example:

echo ^^^<html^^^>|findstr .

The reason pipes require multiple escapes is because each side of the pipe is executed in a new CMD process, so the line gets parsed multiple times. See Why does delayed expansion fail when inside a piped block of code? for an explanation of many awkward consequences of Window's pipe implementation.

There is another method to avoid multiple escapes when using pipes. You can explicitly instantiate your own CMD process, and protect the single escape with quotes:

cmd /c "echo ^<html^>"|findstr .

If you want to use the delayed expansion technique to avoid escapes, then there are even more surprises (You might not be surprised if you are an expert on the design of CMD.EXE, but there is no official MicroSoft documentation that explains this stuff)

Remember that each side of the pipe gets executed in its own CMD.EXE process, but the process does not inherit the delayed expansion state - it defaults to OFF. So you must explicitly instantiate your own CMD.EXE process and use the /V:ON option to enable delayed expansion.

@echo off
setlocal disableDelayedExpansion
set "line=<html>"
cmd /v:on /c echo !test!|findstr .

Note that delayed expansion is OFF in the parent batch script.

But all hell breaks loose if delayed expansion is enabled in the parent script. The following does not work:

@echo off
setlocal enableDelayedExpansion
set "line=<html>"
REM - the following command fails
cmd /v:on /c echo !test!|findstr .

The problem is that !test! is expanded in the parent script, so the new CMD process is trying to parse unprotected < and >.

You could escape the !, but that can get tricky, because it depends on whether the ! is quoted or not.

If not quoted, then double escape is required:

@echo off
setlocal enableDelayedExpansion
set "line=<html>"
cmd /v:on /c echo ^^!test^^!|findstr .

If quoted, then a single escape is used:

@echo off
setlocal enableDelayedExpansion
set "line=<html>"
cmd /v:on /c "echo ^!test^!"|findstr .

But there is a surprising trick that avoids all escapes - enclosing the left side of the pipe prevents the parent script from expanding !test! prematurely:

@echo off
setlocal enableDelayedExpansion
set "line=<html>"
(cmd /v:on /c echo !test!)|findstr .

But I suppose even that is not a free lunch, because the batch parser introduces an extra (perhaps unwanted) space at the end when parentheses are used.

Aint batch scripting fun ;-)