3

I have a batch file with the following header:

@Echo off
SETLOCAL EnableDelayedExpansion EnableExtensions

Within a If statement, enclosed in parenthesis (), I have the following code:

Echo SOME_VAR|FindStr /r /C:"^.*SOME.*$"
Echo Error: !errorlevel!
Echo %%SOME_VAR%%|FindStr /r /C:"^.*SOME.*$"
Echo Error: !errorlevel!

This will print:

SOME_VAR
Error: 0
Error: 1

If SOME_VAR is an existing environment variable, this is what I get. If I delete the variable, I get the expected success.

What happens here? Do I need to escape something more? How can I get a successful find on the 2nd one if the variable exists? I'm only interested in doing a text search where the searched text contains the % character and happens to match an existing variable name.

By the way, the source for the comparison will eventually be a variable too, in which I've loaded the PATH as read from the Windows Registry. So eventually, the string I am searching for will become /C:"^.%%SOME_VAR%%;.*$"

My PATH variable looks like this:

%SOME_VAR%;C:\Windows\system32...................etc
jeremfg
  • 323
  • 2
  • 8

1 Answers1

3

Yes, there is another layer of escape required because each side of the pipe is executed via CMD /C with a command line context, not a batch context. The initial batch parser transforms %%SOME_VAR%% to %SOME_VAR%. The command line parser then leaves %SOME_VAR% as is if the variable is undefined. We need to prevent the expansion if the variable is defined. Doubling the percents does not work in a command line context. The solution is to insert a disappearing caret somewhere between the percents, like %^SOME_VAR%. The caret is treated as part of the variable name, so it prevents expansion of the variable (unless you have a variable named ^SOME_VAR). After failed expansion, the caret is consumed by the normal escape process. The caret must be escaped so that the batch parser passes the caret to the CMD /C command line context.

The final batch line becomes:

Echo %%^^SOME_VAR%% | FindStr SOME_VAR

Note: I simplified the FINDSTR command into a much simpler, but equivalent search.

When you modify the search on the right to include the percents, you will need to insert the escaped caret as well.

Update in response to question in comment
The following code demonstrates some possible ways to work with variables:

@echo off
setlocal disableDelayedExpansion

:: Put both the text you want to search, as well as the search itself, in variables
set "text=%%SOME_VAR%%;C:\Windows\system32...................etc"
set "search=%%SOME_VAR%%"
echo text=%text%
echo search=%search%
echo(

echo(
echo Starting with delayed expansion disabled
echo -----------------------------------------
echo(

:: Use delayed expansion to safely expand any variable without worrying about content.
:: Both sides of the pipe are executed in a command line context with delayed expansion disabled.
:: You must use CMD /V:ON to enable delayed expansion within the pipe.

echo Test when SOME_VAR is undefined:
set "SOME_VAR="
if 1==1 (
  cmd /v:on /c echo !text!|cmd /v:on /c findstr "!search!"
)
echo(

echo Test when SOME_VAR is defined:
set "SOME_VAR=DEFINED"
if 1==1 (
  cmd /v:on /c echo !text!|cmd /v:on /c findstr "!search!"
)
echo(


setlocal enableDelayedExpansion
echo(
echo(
echo Now try with delayed expansion enabled
echo -----------------------------------------
echo(

:: Even though delayed expansion is enabled within the batch script, it is still disabled
:: within the pipe, so we still have to explicitly enable it via CMD /V:ON.
:: But now we must prevent expansion of the variables while in the batch context.
:: You have two options:

:: 1) Escape the !. The escape syntax changes depending on whether it is inside quotes or not:
echo Escape test:
if 1==1 (
  cmd /v:on /c echo ^^!text^^!|cmd /v:on /c findstr "^!search^!"
)
echo(

:: 2) Enclose both sides of the pipe within parentheses (very weird, but it works)
echo Parentheses test:
if 1==1 (
  (cmd /v:on /c echo !text!)|(cmd /v:on /c findstr "!search!")
)

--OUTPUT--

text=%SOME_VAR%;C:\Windows\system32...................etc
search=%SOME_VAR%


Starting with delayed expansion disabled
-----------------------------------------

Test when SOME_VAR is undefined:
%SOME_VAR%;C:\Windows\system32...................etc

Test when SOME_VAR is defined:
%SOME_VAR%;C:\Windows\system32...................etc



Now try with delayed expansion enabled
-----------------------------------------

Escape test:
%SOME_VAR%;C:\Windows\system32...................etc

Parentheses test:
%SOME_VAR%;C:\Windows\system32...................etc
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Thanks, that's a very clear explanation and it works... But how can I do this when it's a variable I'm echoing, with a value such as `%SOME_VAR%;`. I cannot build the string I'm searching into and add carets to it. – jeremfg Jun 02 '15 at 13:28
  • Obviously a good answer, but I can't see the reason to use cmd /v:on here. Why not simply use `echo %%text%% | findstr "%%search%%"`? As it seems that's all about normal variable names with percents. Then the percent expansion is also safe – jeb Jun 02 '15 at 21:43
  • @jeb - Doh! You are correct about the right side. I needlessly complicated the search bit. But the left side deals with the registry PATH value, and it may have content that is unsafe with normal expansion. – dbenham Jun 02 '15 at 22:05
  • @dbenham - Yep, I haven't used/needed `cmd /v:on` on the right side when I commented above. My final line is `cmd /v:on /c Echo ^^!text^^!|FindStr /r /C:"^.*%%SOME_VAR%%;.*$" >NUL` – jeremfg Jun 03 '15 at 23:18