Search code examples
batch-fileregistryenvironment-variablesfindstr

Batch file - How to use FindStr with percent


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

Solution

  • 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