【问题标题】:How does "FOR" work in cmd batch file?“FOR”如何在 cmd 批处理文件中工作?
【发布时间】:2009-05-03 15:09:51
【问题描述】:

我已经用几十种语言编程了 20 年,但无论我多么努力,我都无法理解“FOR”在 windows cmd shell 批处理文件中的工作原理。我读了

http://www.amazon.com/Windows-Administration-Command-Line-Vista/dp/0470046163/ref=sr_1_1?ie=UTF8&s=books&qid=1241362727&sr=8-1

http://www.ss64.com/nt/for.html

和互联网上的其他几篇文章,但仍然感到困惑,无法完成任何事情。

谁能简要解释一下“FOR”的一般工作原理吗?

对于一个更具体的问题,我如何遍历 %PATH% 变量中的每个路径? 我试过了

rem showpathenv.bat
for /f "delims=;" %%g in ("%PATH%") do echo %%g

这只会显示第一条路径,而不是所有路径。为什么 ?我做错了什么?

【问题讨论】:

  • “接受”的答案确实错过了for /f 循环可用的一些重要功能。看看我自己的答案......我认为它比马克的更接近你的要求。但是,我不能 100% 确定你要的是什么 ;-)
  • 没有人回答你的问题:'为什么?我做错了什么?' -- 所以我现在试试:它只向你显示第一条路径,因为你只是要求它。它按设计工作(我不是说设计很好)。如果你想要更多,你必须要求更多:在字母表中的 'g' 之后是 'h' -- 所以添加 echo %%h & echo %%i & echo %%j 以查看另外 3 个路径目录。
  • @pipitas and tokens=... C:\>for /f "tokens=1,2,3,4 delims=;" %g in ("%PATH%") 做回显 %g %h %i
  • @pipitas 清酒的答案看起来很灵活,不需要很长的令牌列表,使用替换(参见 set /?)来处理路径。

标签: batch-file iteration cmd


【解决方案1】:

没有一个答案真正有效。我自己设法找到了解决方案。 这有点骇人听闻,但它为我解决了问题:

echo off
setlocal enableextensions
setlocal enabledelayedexpansion
set MAX_TRIES=100
set P=%PATH%
for /L %%a in (1, 1, %MAX_TRIES%) do (
  for /F "delims=;" %%g in ("!P!") do (
    echo %%g
    set P=!P:%%g;=!
    if "!P!" == "%%g" goto :eof
  )
)

哦!我讨厌批处理文件编程!!

更新

Mark 的解决方案更简单,但不适用于包含空格的路径。这是马克解决方案的一个小修改版本

echo off
setlocal enabledelayedexpansion
set NonBlankPath=%PATH: =#%
set TabbedPath=%NonBlankPath:;= %
for %%g in (%TabbedPath%) do (
  set GG=%%g
  echo !GG:#= !
)

【讨论】:

  • 这太棒了.. 它似乎被忽视了。你应该接受你自己的答案。非常创新,使用 2 个循环,一个作为计数器,第二个使用替换来遍历路径,将已处理的位替换为空,作为 tokens=1,2,3,4.... 的替代方案......非常好。跨度>
  • 谢谢,巴洛普。是的,这是最好的答案,但我为接受自己的答案感到羞耻:)。你能看懂代码真是太神奇了。我再也无法理解我自己做了什么。 :)
  • 接受你自己的答案,因为它是最好的,并且数据库比任何人都更能接受的答案。
  • 有趣的是,我只需要制作一个蝙蝠来回显路径中的每个 DIR .. 不知道如何但检查了你的。这样做是为了让我看看是否有重复。我利用了你的技术。我这样做了 (set a=%PATH: =#%) & (set b=%a:;= %) & for %f in (%b%) do @echo %f 不介意每个 Dir 都有对于我来说,空间有一个#。然后我做了|排序。
  • 其实翻阅有几个不错的答案。从杰伊的回答看这个可以在一行中完成 C:\>for %p in ("%path:;=" "%") do @echo %~p
【解决方案2】:

Mark 的想法不错,但可能忘记了某些路径中有空格。替换';'使用 '" "' 代替会将所有路径切割成带引号的字符串。

set _path="%PATH:;=" "%"
for %%p in (%_path%) do if not "%%~p"=="" echo %%~p

所以在这里,你已经显示了你的路径。

cmd 中的 FOR 命令有一条乏味的学习曲线,特别是因为变量在 () 语句中的反应...您可以分配任何变量,但不能在 () 语句中读回,除非您使用“setlocal ENABLEDELAYEDEXPANSION”语句,因此也使用带有 !!'s 的变量而不是 %%'s (!_var!)

我目前专门用 cmd 编写脚本,为了工作,必须学习所有这些:)

【讨论】:

  • 在do 之后添加@ 将确保命令不会回显到命令行。如果将echo %%~p 更改为echo.%%~p,则可以消除“如果不是”部分。因此,第二个命令可以缩短为for %%p in (%_path%) do @echo.%%~p
  • 你是对的,但是“@”应该只在命令行上有用,因为脚本应该以关闭@echo 开头。我的循环使用两个“%%”是一个标志,它打算作为一个脚本。我从没想过使用“回声”。但仅适用于空行;但是在当前情况下,它会回显空白行,这是我不想要的。赞成这个想法。
【解决方案3】:

您的想法是对的,但 for /f 旨在处理多行文件或命令,而不是单个字符串。

在最简单的形式中,for 类似于 Perl 的 for,或其他所有语言的 foreach。你向它传递一个令牌列表,它会遍历它们,每次都调用相同的命令。

for %a in (hello world) do @echo %a

这些扩展仅提供构建令牌列表的自动方式。您当前的代码一无所获的原因是 ';' 是默认的行尾(注释)符号。但即使您更改了它,您也必须使用 %%g, %%h, %%i, ... 来访问各个令牌,这将严重限制您的批处理文件。

你能得到的最接近你要求的是:

set TabbedPath=%PATH:;= %
for %%g in (%TabbedPath%) do echo %%g

但是对于包含分号的引用路径,这将失败。

根据我的经验,for /l 和 for /r 非常适合扩展现有命令,但除此之外 for 非常有限。您可以通过延迟变量扩展 (cmd /v:on) 使其更强大(并且令人困惑),但它实际上只适用于文件名列表。

如果您需要执行字符串操作,我建议您使用 WSH 或 PowerShell。如果您尝试为 Windows 编写 whereis,请尝试 where /?。

【讨论】:

  • 可能是我对批处理文件的期望太高了。感谢你的回答。我可能对 WSH 或 PowerShell 不感兴趣,因为我已经在使用 Python。
  • 我根据您的指南提出了解决方案。请参阅我下面的答案
  • 你说的是 '[...] for /f [...] 而不是单个字符串'。我今天的示例展示了如何剖析单个字符串,只要它内部有可用的分隔符。
  • pipitas:当然,这就是我的 TabbedPath 位的意思。但是你不能依赖有可用的分隔符。
  • “在最简单的形式中,for 就像 Perl 的 for 或其他所有语言的 foreach。你向它传递一个标记列表,它会遍历它们,每次调用相同的命令。”你确定吗?看起来它不会迭代。 for /F "tokens=1,2,3,4 delims=." %f in ("127.0.0.1") do (echo asdf %f %g %h %i) 它只回显 asdf 一次。所以它不像一个foreach
【解决方案4】:

忍不住把它扔在那里,因为这个线程很旧......通常当需要遍历 PATH 中的每个文件时,你真正想要做的就是找到一个特定的文件......如果就是这样,这个单行程序将吐出它在其中找到您的文件的第一个目录:

(例如:寻找 java.exe)

for %%x in (java.exe) do echo %%~dp$PATH:x

【讨论】:

    【解决方案5】:

    我知道这是超级老...但只是为了好玩我决定试一试:

    @Echo OFF
    setlocal
    set testpath=%path: =#%
    FOR /F "tokens=* delims=;" %%P in ("%testpath%") do call :loop %%P
    :loop
    if '%1'=='' goto endloop
    set testpath=%1
    set testpath=%testpath:#= %
    echo %testpath%
    SHIFT
    goto :loop
    :endloop
    pause
    endlocal
    exit
    

    这不需要计数,直到完成。我对空格有同样的问题,但它通过了整个变量。关键是循环标签和SHIFT 函数。

    【讨论】:

      【解决方案6】:

      for /f 迭代每行输入,因此在您的程序中只会输出第一个路径。

      您的程序将 %PATH% 视为单行输入,并以; 分隔,将第一个结果放入 %%g,然后输出 %%g(第一个分隔路径)。

      【讨论】:

      • 但是请注意,如果第一个路径中有分号,这将失败。在这种情况下,for 完成的标记化将无法处理引用。
      【解决方案7】:

      FOR 本质上是对数据集中的“行”进行迭代。在这种情况下,只有一行包含路径。 "delims=;" 只是告诉它用分号分隔。如果将正文更改为echo %%g,%%h,%%i,%%j,%%k,您会看到它将输入视为单行并将其分解为多个标记。

      【讨论】:

        【解决方案8】:

        这对我有用:

        @ECHO OFF
        SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
        @REM insure path is terminated with a ;
        set tpath=%path%;
        echo.
        :again
        @REM This FOR statement grabs the first element in the path
        FOR /F "delims=;" %%I IN ("%TPATH%") DO (
          echo    %%I 
          @REM remove the current element of the path
          set TPATH=!TPATH:%%I;=!
        )
        @REM loop back if there is more to do. 
        IF DEFINED TPATH GOTO :again
        
        ENDLOCAL
        

        【讨论】:

        • 拼写错误。这是确保,第三行。
        【解决方案9】:

        您必须另外使用for 循环允许的选项的tokens=1,2,... 部分。这将做你可能想要的:

        for /f "tokens=1,2,3,4,5,6,7,8,9,10,11,12 delims=;" %a in ("%PATH%") ^
        do ( ^
             echo. %b ^
           & echo. %a ^
           & echo. %c ^
           & echo. %d ^
           & echo. %e ^
           & echo. %f ^
           & echo. %g ^
           & echo. %h ^
           & echo. %i ^
           & echo. %j ^
           & echo. %k ^
           & echo. ^
           & echo.   ...and now for some more... ^
           & echo. ^
           & echo. %a ^| %b ___ %c ... %d ^
           & dir "%e" ^
           & cd "%f" ^
           & dir /tw "%g" ^
           & echo. "%h  %i  %j  %k" ^
           & cacls "%f")
        

        此示例仅处理前 12 个标记(=来自 %path% 的目录)。它使用每个使用的标记的显式枚举。请注意,令牌名称区分大小写:%a 与 %A 不同。

        为了保存带有空格的路径,请在 all %x 周围加上像“%i”这样的引号。我没有在这里做,我只是在呼应令牌。

        你也可以做某事。像这样:

        for /f "tokens=1,3,5,7-26* delims=;" %a in ("%PATH%") ^
        do ( ^
             echo. %c ^
           & echo. %b ^
           & echo. %a ^
           & echo. %d ^
           & echo. %e ^
           & echo. %f ^
           & echo. %g ^
           & echo. %h ^
           & echo. %i ^
           & echo. %j ^
           & echo. %k )
        

        这个跳过了标记 2、4、6 并使用一个小快捷方式(“7-26”)来命名其余的标记。请注意,与第一个示例相比,这次 %c、%b、%a 是如何以相反的顺序处理的,以及它们现在如何“表示”不同的标记。

        所以这肯定不是您要求的简明解释。但也许这些例子现在有助于澄清一点......

        【讨论】:

        • 感谢您的更多解释。老实说,一年后,我现在几乎不懂批处理文件。我想知道如果我们必须为同一系列中的值处理不同的变量,那么 for 循环的意义何在?
        • @Sake:在这种情况下,for 循环将输入 ("%PATH%") 拆分为不同的行。标记将每一行分成不同的块(边界取决于给定的分隔符)。现在,在我们的例子中,我们只有 1 行输入。 (但循环同样适用于多行。)我们使用 for 循环,因为我们没有其他方法可以将行(无论是单行还是多行)分割成块。
        • 这个例子有点长。你可以用一个更短的例子来演示它,比如一行。而不是一个例子,一个人可能会在输入时睡着了,然后醒来,再输入一些,吃晚饭,在一行上打错字,生气,重新开始,然后退出。
        • @barlop:我认为对于一个了解十几种编程语言并说他已经花了 20 年徒劳地理解它的人来说,答案的长度正好合适...... ;-P
        • @barlop: ...另外,将它放在一行中会使它的可读性大大降低。我很高兴你这么容易就睡着了,尽管这个世界是如此邪恶。
        【解决方案10】:

        这是一个很好的指南:

        FOR /F - Loop command: against a set of files.

        FOR /F - Loop command: against the results of another command.

        FOR - Loop command: all options Files, Directory, List.

        [完整指南(Windows XP 命令):

        http://www.ss64.com/nt/index.html

        编辑:抱歉,没有看到该链接已经在 OP 中,因为它在我看来是亚马逊链接的一部分。

        【讨论】:

        • 我看不懂 /?帮助并寻找更多解释或示例。
        【解决方案11】:

        它对我有用,试试吧。

        for /f "delims=;" %g in ('echo %PATH%') do echo %g%
        

        【讨论】:

          【解决方案12】:

          它对我有用,试试吧。

          for /f "tokens=* delims=;" %g in ('echo %PATH%') 做 echo %g%

          【讨论】:

          • 欢迎来到 Stack Overflow!虽然欢迎使用此代码 sn-p,并且可能会提供一些帮助,但它将是 greatly improved if it included an explanation 的 如何 和 为什么 这可以解决问题。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人!请edit您的答案添加解释,并说明适用的限制和假设。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-07-12
          • 2010-09-14
          • 1970-01-01
          相关资源
          最近更新 更多