Batch file: Escape questionmark in for loop

2019-02-19 08:20发布

问题:

This for loop (reduced minimal example);

@echo off
for %%a in (help -help --help /help ? /?) do ( 
   echo %%a 
)

chokes on the 2 elements with a '?' character. It outputs

C:\Temp>test.bat
help
-help
--help
/help

C:\Temp>

So it just quits the loop when it hits the first '?'.

What is the proper escape sequence for this set? Tried a bunch of stuff, double quotes, carets, backslash, etc. but nothing seems to work.

回答1:

Another option is to use linefeeds within a FOR /F string. FOR /F will treat each line as an independent string. Below I show four ways to do the same thing.

@echo off
setlocal enableDelayedExpansion

:: Define LF to contain a linefeed character
set ^"LF=^

^" The above empty line is critical. DO NOT REMOVE

:: Option 1
:: Embed linefeeds directly in the string literal
for /f %%A in ("help!LF!-help!LF!--help!LF!/help!LF!?!LF!/?") do (
  echo(%%A
)


echo(
:: Option 2
:: Define a variable with spaces and use search and replace
:: to substitue linefeeds
set "help=help -help --help /help ? /?"
for %%L in ("!LF!") do for /f %%A in ("!help: =%%~L!") do (
  echo(%%A
)


echo(
:: Option 3
:: Embed linefeed directly in string without LF variable
for /f %%A in (^"help^

-help^

--help^

/help^

?^

/?^") do (
  echo(%%A
)


echo(
:: Option 4
:: Embed linefeed directly in search and replace without LF variable
for /f %%A in (^"!help:^ ^=^

!^") do (
  echo(%%A
)

I prefer option 2. I find it to be the easiest to read, yet still be compact.

Note that MC ND and I both use echo(%%A. This is necessary to prevent echo /? from displaying the help for the ECHO command.



回答2:

Nothing seems to work because nothing will work.

In its simplest form (for %%x in (set) do ...), when the for command iterates over the elements in the set, it will test if the element contains a * or a ?. In this case, it is considered a file wildcard and will search for files matching the indicated expression. You can test it in your case placing a file with a single character name and no extension in the folder containing the batch file, and the ? will match it and you will see it in the output.

And as far as i know there is no way to avoid it. This is the way for command is intended to work.

You can construct something similar to what you are trying but will need another approach

@echo off
    setlocal 

    set "opt.a=help" 
    set "opt.b=-help"
    set "opt.c=--help"
    set "opt.d=/help"
    set "opt.e=?"
    set "opt.f=/?"

    for /f "tokens=2 delims==" %%a in ('set opt.') do ( 
       echo(%%a 
    )

    endlocal

In this sample each value is in a environment variable and the set is retrieved via a set command and output processed with the options of for command. It is just one option. You need the values as line separated strings to be processed with a for /f.



回答3:

While I personally think dbenham has provided the best answer, I want to add that in some cases FOR /F loop should be provided with options:

  1. tokens=* - this would disable tokenization of lines (or, in case you need it, you could set delims to whatever you want), allowing you to have spaces in strings;
  2. usebackq - this would allow you to have double quotes in strings. However, in this case, single quotes need to be escaped with caret - ^', and surrounding double quotes should be replaced with single quotes:
(set RN=^
%= \r\n =%
)
for /f "usebackq tokens=*" %%A in ('^'quotes^'!RN!"double quotes"!RN!`backquotes`') do (
  echo:%%A
)

As for the original question, there are few other options available. Alex was onto something with his answer, but it didn't cover how to actually iterate through the list. To do it you need to chain echo commands. You start with echo: and use ^&echo: to echo all subsequent strings/variables:

set "var=line1"
for /F "usebackq delims=" %%G in (`
echo:%var%^&
echo:   line2 * ? ^^^^ ^^^& ^^^< ^^^> ^^^| ^^^" ' ^` ^)   ^&
echo: ^ ^ line3 ^ ^ ^&echo:li

ne4^&echo  ^&echo: ^&echo:/?
`) do (
    echo "%%G"
)

This will give you:

"line1"
" line2 * ? ^ & < > | " ' ` ) "
"   line3   "
"li ne4"
"ECHO is on."  # System language specific
" "
"/? "
  • As shown, you can break lines in any way, as long as you keep ^& and echo: intact (be wary of spaces).
  • Using colon instead of space after echo will sanitize values like ON, OFF or /?. You can also use some other characters, like (, but i find colon more readable.
  • Line-breaks are allowed, but all successive whitespaces (spaces and line-breaks) are replaced with a single space (additional spaces can be enforced by escaping them with single caret ^). echo ^& will count as echo with no parameters and will display current echo setting, echo:^& will give an empty line (FOR /F ignores empty lines), and echo: ^& will give you .
  • ^ & < > | " need to be escaped with three carets. ` ) need to be escaped with a single caret.
  • Without usebackq option you don't have to escape `, but you'll need to escape ' with three carets. You'll also need to use single quotes instead of surrounding backquotes.

You can avoid excessive escaping if you put the expression inside double quotes like this:

for /f "usebackq delims=" %%G in (`"echo:%var%&echo:   line2 * ? ^^ ^& ^< ^> ^| ' ` "abc" ^)   &echo &echo: &echo:/?"`) do (
    echo "%%G"
)

This will give you:

"line1"
"   line2 * ? ^ & < > | ' ` "abc" )   "
"ECHO is on."  # System language specific
" "
"/?"
  • To chain echo commands you use &echo: (note, no caret).
  • Line-breaks are not allowed. Spaces are preserved.
  • " cannot be escaped, but could be used if there is an even number of them. You don't need to escape ' and `.
  • Again, without usebackq option you'll need to use single quotes instead of surrounding backquotes.


回答4:

If you use a for /f loop it works without escaping the ? characters, although the loop doesn't work the same way:

C:\Users\Alex\Downloads\test>for /f "tokens=* delims=" %a in ('echo help -help --help /help ? /?') do echo %a

C:\Users\Alex\Downloads\test>echo help -help --help /help ? /?
help -help --help /help ? /?