简体   繁体   中英

How to quote spaces in the command of a “for /F” loop on Windows

I have the following for command

for /F %s in ('"mas ter\gradlew.bat" -q -p mass ter') do echo %s

And I don't get it to work properly. It is not the actual use-case, but a much simplified example.

Executing

"mas ter\gradlew.bat" -q -p "mas ter"

works fine,

"mas ter\gradlew.bat" -q -p mass ter

says that the project direcotry "mass" does not exist (extra "s" for distinguisihing purpose only),

mas ter\gradlew.bat -q -p mass ter

says command "mas" was not found.

So far so expected, but now I want to get it working in the for loop.

for /F %s in ('mas ter\gradlew.bat -q -p mass ter') do echo %s

of course says again that command "mas" was not found,

for /F %s in ('"mas ter\gradlew.bat" -q -p mass ter') do echo %s

of course says again that project direcotry "mass" does not exist but now it comes,

for /F %s in ('"mas ter\gradlew.bat" -q -p "mass ter"') do echo %s

again says that command "mas" was not found. o_O

And now I'm at wtf.
Why is this happening and how do I make it work?
It also does not work to escape the spaces with ^ characters.
Besides that, this wouldn't help, because the two paths are stored in parameters and are used like

for /F %s in ('"%script_path%\gradlew.bat" -q -p "%script_path%"') do echo %s

This was really a frustrating one because the parsing of command is weird

('"mas ter\gradlew.bat" -q -p mass ter')

But you can't have double quotes in both first and any other param

('"mas ter\gradlew.bat" -q -p "mass ter"')

Something of below would work fine

('master\gradlew.bat -q -p mass ter')

So I came with few things for this to work

par ams.bat

A file with space in name and echo each parameter. I also create copy of same as params.bat

@echo off
setlocal enabledelayedexpansion
echo "You sent '%*'"
set argC=0
for %%x in (%*) do (
    Set /A argC+=1
    echo !argC! is %%x
)
echo %argC%

F:\params.sh>"par ams.bat" 1 2 "ab c"
"You sent '1 2 "ab c"'"
1 is 1
2 is 2
3 is "ab c"
3

This shows what I need to do.

Now a file to test the setup

@echo off

for /F "tokens=* delims=" %%s in (params.bat " -q" "  -p " "abc"`) do echo %%s

this worked great

"You sent '" -q" "  -p " "abc"'"
0 is " -q"
0 is "  -p "
0 is "abc"
3

Next was to try par ams.bat . I tried different variations

for /F "tokens=* delims=" %%s in ('par ams.bat " -q" "  -p " "abc"') do echo %%s
for /F "tokens=* delims=" %%s in ('"par ams.bat" " -q" "  -p " "abc"') do echo %%s
for /F "tokens=* delims=" %%s in ('par ams.bat' " -q" "  -p " "abc"') do echo %%s
for /F "tokens=* delims= usebackq" %%s in (`'par ams.bat' " -q" "  -p " "abc"`) do echo %%s
for /F "tokens=* delims= usebackq" %%s in (`"par ams.bat" " -q" "  -p " "abc"`) do echo %%s

And none of the above worked. So the conclusion was that first param without space and then any param with space or quotes, no issues. So I came up with a additional batch file

escape.bat

@echo off
SET COMMAND=%~1
SHIFT
%COMMAND %%*

The file does nothing but takes first parameter as what to execute and then command line arguments for the same. And now I updated the test to use

for /F "tokens=* delims=" %%s in ('escape.bat "par ams.bat" " -q" "  -p " "abc"') do echo %%s

And then it worked

F:\params.sh>test
"You sent '" -q" "  -p " "abc"'"
0 is " -q"
0 is "  -p "
0 is "abc"
3

Edit-1: Using call instead of escape.bat

Thanks for the feedback on this @Vampire. So a better way is to use call instead of a escape.bat . Also you can get the script directory using set script_dir=%~dp0 and then use the same in your batch file. So we will change

for /F "tokens=* delims=" %%s in ('escape.bat "par ams.bat" " -q" "  -p " "abc"') do echo %%s

to

set script_name=%~dp0
for /F "tokens=* delims=" %%s in ('call "%script_name%\par ams.bat" " -q" "  -p " "abc"') do echo %%s

Executive summary:

Enclose the command string in another set of double-quotes regardless of whether or not quotes are used in the command string (just leave those alone):

for /F %s in ('""%script_path%\gradlew.bat" -q -p "%script_path%""') do echo %s

tl;dr details:

It appears that the for /f ... ('command') variant just passes the command string to cmd /c (or some equivalent internal code) to actually run the command.

In general, cmd.exe has complicated, haphazard, and buggy quote handling rules. The particular rules for cmd /c includes these copied from cmd /? help text:

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.

(who came up with that stuff?)

What this boils down to for me is that it's best just to enclose the command string in a set of double-quotes regardless of whether or not quotes are used in the command string (just leave those alone).

Here's a batch file that is in a path containing a space - mas ter\\gradlew.bat :

@echo off
echo "Running '%~dpnx0'"

:echo_args
    if "" == "%1" goto :eof
    echo arg: %1
    shift

Here's a run of that batch file without the for loop:

C:\temp>"mas ter\gradlew.bat" -q -p "mass ter"
"Running 'C:\temp\mas ter\gradlew.bat'"
arg: -q
arg: -p
arg: "mass ter"

And here is it used in a for loop with an enclosing set of double-quotes::

C:\temp>for /F "tokens=*" %s in ('""mas ter\gradlew.bat" -q -p "mass ter""') do @echo from the for loop: "%s"
from the for loop: ""Running 'C:\temp\mas ter\gradlew.bat'""
from the for loop: "arg: -q"
from the for loop: "arg: -p"
from the for loop: "arg: "mass ter""

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM