【问题标题】:Why can't you use a question mark in a batch for loop?为什么不能在批量 for 循环中使用问号?
【发布时间】:2018-08-02 01:01:25
【问题描述】:

前言

在编写一段单独的代码时,我遇到了 for 循环中的问号问题。如下图,for循环中没有访问到问号。

批处理文件:

@echo off

for %%x in (the, quick, ?, brown, fox) do (
    echo %%x
)

输出:

the
quick
brown
fox

这在 CMD 中也不起作用(使用 %x 而不是 %%x),或者在使用 ""[]^\% 或其他常用方法时字符转义。

使用计数器变量来确定括号内的代码被访问的次数只会导致总计数为 4,这意味着 echo 命令显然没有问题。


问题

为什么问号在标准 for 循环中不起作用,我将如何解决它?

【问题讨论】:

  • 许多字符对 cmd 解释器具有特殊意义。要将它们用作文字,您需要对它们进行转义。 ^ 是正常的转义字符。要转义百分比符号,请将百分比符号加倍。
  • @Squashman 感谢您的回复。正如我在前言末尾提到的那样,我曾尝试使用^% 甚至\,我以前知道它们适用于大多数字符转义。但是,它在 for 循环中没有与问号一起使用,因此引起了好奇心。

标签: windows batch-file for-loop cmd


【解决方案1】:

这是因为? 将扩展为一个长度为一个字符的文件名列表。 “裸”for 将该列表用作文件名列表。

如果您运行以下命令,您将看到它的实际效果:

c:\> echo xx >a
c:\> echo xx >b
c:\> for %i in (1, ?) do echo %x
1
a
b

如果您查看Rob van der Woude's excellent scripting pages,您会发现for page 具有处理命令输出、数字和文件的选项——它并不真正适合任意字符串。

解决此问题的一种方法是提供您自己的for-like 命令,如下例所示:

    @echo off
    setlocal enableextensions enabledelayedexpansion

    rem Call the callback function for each argument.
    set escapee=/
    call :doFor :processEach 1 2 ? 4 5
    echo.Escapee was %escapee%

    rem Execute simple command for each argument.
    call :doFor echo 6 7 ? 9 10

    endlocal
    goto :eof

:processEach
    set escapee=%escapee%%1/
    goto :eof

:doFor
    setlocal

    rem Get action.
    set cbAction=%1
    shift

:dfloop
    rem Process each argument with callback or command.
    if not "%1" == "" (
        call %cbAction% %1
        shift
        goto :dfloop
    )
    endlocal&&set escapee=%escapee%
    goto :eof

这提供了一个可以处理回调和简单命令的函数。对于更复杂的命令,提供一个回调函数,它会被每个参数依次调用。回调函数可以任意复杂,但请记住,因为它在 setlocal 内运行,所以对环境变量的更改无法转回给调用者。

作为一种解决方法,它允许 一个 变量 escapee 转义范围 - 如果需要,您还可以添加更多。

对于只需要将参数放在末尾的简单命令(如echo),您可以执行相同的操作。它不需要回调函数,但仅限于非常简单的场景。

另外请记住,虽然这看起来有很多代码,但其中绝大多数只需要存在于一个地方。要使用它,您只需要像示例这样的单线:

call :doFor echo my hovercraft is full of eels

还请记住,即使使用此方案,也可能有 other 个字符表现不佳。它解决了? 问题,但其他问题仍可能导致问题。我怀疑这将是一个将 PowerShell 添加到您的简历的理想机会,例如,一个几乎类似于 bash 的命令,其优雅和禅意:

PShell> foreach ($item in @("1", "?", "3", "4")) { echo $item }
1
?
3
4

【讨论】:

    【解决方案2】:

    您可以切换到FOR /F
    但是 FOR /F 用于处理多行以将它们拆分为标记。
    在您的情况下,您不需要多个令牌,每个项目需要一个循环。

    这可以通过使用换行符拆分项目来完成。
    我使用# 作为项目分隔符,但您可以随意使用任何其他字符

    @echo off
    setlocal EnableDelayedExpansion
    (set \n=^
    %=EMPTY=%
    )
    
    set "itemList=the#quick#?#brown#fox"
    for %%L in ("!\n!") DO (
        FOR /F "delims=" %%x in ("!itemList:#=%%~L!") DO echo - %%x -
    )
    

    输出:

    - the -
    - quick -
    - ? -
    - brown -
    - fox -
    

    【讨论】:

      【解决方案3】:

      多年来我一直在使用批处理进行编码,直到现在我才意识到这个问题!

      我找到了另一种方法来处理这个问题。可能有人喜欢它,比如我。

      在我的特殊情况下,我使用 FOR LOOP 来获取当前函数的一些命名参数。这就是我所做的:

      :SomeFunct
      rem Replace ?
      set "args=%*"
      set "args=%args:?=`%"
      
      rem Iterate args
      for %%p in (%args%) do (
          for /f "tokens=1,* delims=: " %%a in ("%%~p") do (
              rem Get and store values
              if /i "%%~a" equ "/a" set "argA=%%~b"
              if /i "%%~a" equ "/b" set "argB=%%~b"
              if /i "%%~a" equ "/c" set "argC=%%~b"
          )
      )
      
      rem Restore ?
      if defined argA set "argA=%argA:`=?%"
      if defined argB set "argB=%argB:`=?%"
      if defined argC set "argC=%argC:`=?%"
      
      rem I use the args
      rem ...
      
      rem Return
      goto:eof
      

      我这样调用函数:

      rem Calling example
      call:SomeFunct "/a:Is there" "/b:a question mark" "/c in the arguments?"
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-31
        • 2019-08-20
        • 1970-01-01
        相关资源
        最近更新 更多