Ben Philipp
Ben Philipp

Reputation: 1877

Get result of command with quoted arguments within single quote command evaluation in Windows batch

How do you pass quoted arguments to an executable in a single-quoted evaluation such as FOR /F "delims=" %%i IN ('"executable path" arg1 "arg2 which contains spaces"') do ...?

As per many of the answers here, I'm trying to use the output of a console app in a Windows batch script, using the single quotes to get the console to evaluate it.
However, I need to quote some of the arguments I want to pass to that executable, which also needs to be quoted, as the path contains spaces as well.
But when I do that, the quoting around the executable path breaks.

Here is the would-be line:

FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^="!album!"') DO set "falbum=%%i"

(Both !PathToExe! and !album! contain spaces. It seems like I need to escape the equal signs here, hence the circumflexes. Delayed expansion is on)

The above results in "Outcome A": Quoting is broken for the exe path

'<Part of path to exe>' is not recognized as an internal or external command, operable program or batch file.

I've tried different combinations of different quote usages and escapings, but haven't found a way to make it work.

Here are some attempts and their results:

FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^=!album!') DO set "falbum=%%i")

No quotes around !album! results in "Outcome B": As expected, only the first word gets passed along with string=, all the other words get scattered as individual arguments.

FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^=^"!album!^"') DO set "falbum=%%i")
FOR /F "delims=" %%i IN ('^"!PathToExe!^" action^=sanitize string^=^"!album!^"') DO set "falbum=%%i")

Trying to escape the quotes for the string= argument or both exe path and string arg: Outcome A (still breaks the quoting for the exe path, gives:)

'<Part of path to exe>' is not recognized as an internal or external command, operable program or batch file.

FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^=^'!album!^'') DO set "falbum=%%i")

Using escaped single quotes: Outcome B again

FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^='"!album!") DO set "falbum=%%i")

Ending the single quote before the string= value and simply having it in quotes after that seems to result in everything being taken as a single first argument (command/path):

The system cannot find the file '"<path to exe>" action=sanitize string='"<Album var with spaces and whatnot>".

It does not matter whether the quotes are part of the variables or literally typed in the IN ('...') line.

Simple testing:

You could test this behavior if you copied %SystemRoot%\System32\cmd.exe to a directory with spaces, e.g. C:\folder with spaces\ and pasted the following script there:

@echo off
setlocal EnableDelayedExpansion

set PathToExe="%~dp0cmd.exe"
REM Toggle the next line to compare between quoted path with spaces and path without quotes or spaces:
REM set PathToExe=cmd.exe
set string=%~dp0

FOR /F "delims=" %%i IN ('!PathToExe! /C CD C:\windows\system32 ^& dir !string!') DO set "fstring=%%i"
echo !fstring!
pause

This should illustrate the challenge of having two quoted sections in one statement.
If the !string! variable remains unquoted, you'll get "The system cannot find the file specified.".
If the quotes of the !PathToExe! variable break, you'll see something like "'C:\folder' is not recognized as an internal or external command, operable program or batch file.".

Upvotes: 2

Views: 429

Answers (1)

aschipfl
aschipfl

Reputation: 34899

The for /F command, when used to capture and parse command output, actually uses cmd /C to execute that command, which handles quotation marks in a particular way. From its help (cmd /?):

If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:

    1.  If all of the following conditions are met, then quote characters
        on the command line are preserved:

        - no /S switch
        - exactly two quote characters
        - no special characters between the two quote characters,
          where special is one of: &<>()@^|
        - there are one or more whitespace characters between the
          two quote characters
        - the string between the two quote characters is the name
          of an executable file.

    2.  Otherwise, old behavior is to see if the first character is
        a quote character and if so, strip the leading character and
        remove the last quote character on the command line, preserving
        any text after the last quote character.

This means that:

for /F "delims=" %%I in ('"executable path" arg1 "arg2 which contains spaces"') do (…)

actually tries to execute the command line:

cmd /C "executable path" arg1 "arg2 which contains spaces"

leading to:

executable path" arg1 "arg2 which contains spaces

which is obviously invalid syntax.

To overcome this issue provide an additional pair of quotes:

for /F "delims=" %%I in ('^""executable path" arg1 "arg2 which contains spaces"^"') do (…)

It is a good idea to escape these additional quotes (by ^"), so there is no need to alter any potential escape sequences.

Upvotes: 2

Related Questions