如何在批处理中循环遍历逗号分隔的字符串?

29

Windows

根据这篇帖子(dos批处理迭代遍历分隔字符串), 我编写了下面的脚本,但是它并不像预期的那样工作。

目标: 给定字符串 "Sun,Granite,Twilight",我想要循环获取每个主题值,以便我可以对该值进行一些处理。

当前输出不正确:

list = "Sun,Granite,Twilight"
file name is "Sun Granite Twilight"

第一次迭代应该是:

list = "Sun,Granite,Twilight"
file name is "Sun"
第二次迭代应该是“文件名是“Granite”,依此类推。我错在哪里了? 代码:
set themes=Sun,Granite,Twilight

call :parse "%themes%"
goto :end

:parse
setlocal
set list=%1
echo list = %list%
for /F "delims=," %%f in ("%list%") do (
    rem if the item exist
    if not "%%f" == "" call :getLineNumber %%f
    rem if next item exist
    if not "%%g" == "" call :parse "%%g"
)
endlocal

:getLineNumber
setlocal
echo file name is %1
set filename=%1
endlocal

:end
4个回答

53

这是我会做的方式:

@echo off
set themes=Sun,Granite,Twilight
echo list = "%themes%"
for %%a in ("%themes:,=" "%") do (
   echo file name is %%a
)

也就是说,将Sun,Granite,Twilight更改为"Sun" "Granite" "Twilight",然后在常规(无/F选项)的for命令中处理每个用引号括起来的部分。这种方法比基于"delims=,"的迭代for /F循环要简单得多。


1
它是否也处理主题名称中有空格的情况?(我现在被这个问题卡住了..) - Meow
1
我认为你甚至不需要用逗号进行字符串替换。请参考:https://dev59.com/HnE85IYBdhLWcg3w9onw#8086103 - Justin
@Justin:是的,但在这种情况下,解决方案不会“处理主题名称之一带有空格”的情况。 - Aacini
4
这真的很棒!我花了一点时间才理解,但基本上“for”命令默认会 1)使用空格作为分隔符,并且 2)将双引号包含的内容视为单个项。这意味着for %%a in ("one" "two" "three and four") do echo %%a将输出one\ntwo\three and four - rocketmonkeys
真是糟糕透了,微软根本不费心在需要的地方记录相关信息,总是把它藏在某个网页或者晦涩的地方,你得“知道”如何/在哪里找到它,而这些信息都埋在他们庞大得令人发指的MSDN(咳咳,曾经的MSDN)之中(他们甚至连产品名称都无法保持一致,更别提文档了)。我敢打赌,微软的大部分会议主要就是改名字和移动资产(而不是改进产品,只是把它们搬到新的地方)。 - osirisgothra

28

我采用了 Aacini 的回答,并稍微修改以删除引号,因此可以根据所需的命令添加或删除引号。

@echo off
set themes=Hot Sun,Hard Granite,Shimmering Bright Twilight
for %%a in ("%themes:,=" "%") do (
    echo %%~a
)

12

我对你的代码进行了一些修改。

  1. 在子程序结尾和主程序结尾需要添加“goto :eof”,以防陷入子程序中。
  2. tokens=1* (%%f是第一个标记;%%g是行的其余部分)
  3. 在集合列表中使用~,将list=%~1,以便去除引号,以免引号累积。

@echo off
set themes=Sun,Granite,Twilight

call :parse "%themes%"
pause
goto :eof

:parse
setlocal
set list=%~1
echo list = %list%
for /F "tokens=1* delims=," %%f in ("%list%") do (
    rem if the item exist
    if not "%%f" == "" call :getLineNumber %%f
    rem if next item exist
    if not "%%g" == "" call :parse "%%g"
)
endlocal
goto :eof

:getLineNumber
setlocal
echo file name is %1
set filename=%1
goto :eof

谢谢你的提示!你能解释一下这行代码的意思吗:set list=%1?%1代表什么? - Meow
只是为了我的理解::eof 是一个“内置”标签吗?因为我没有看到它被定义。在子程序中,它充当“返回”吗?虽然控制流很奇怪 :) - Krischu
:getLineNumber被调用时使用了%%f,而:parse则使用了"%%g"(带引号),这是有特定原因的吗? - Krischu
没有其他原因,只是因为这是原始帖子中的方式,所以我没有改变它。如果我从头开始做,我也会引用%%f,以避免将来出现字符串包含空格等问题。 - RGuggisberg
为了优化您的代码,请在调用返回点处将“goto :eof”替换为“exit / b”。这样做更加简洁,避免在每个批处理文件中编写“:eof”标签(如果您有很多批处理文件),还可以通过放置“exit / b ErrorValue”(“ErrorValue”必须是整数)来带有错误返回。以这种方式,在调用之后,您可以像捕获异常一样处理错误,其中您可以捕获并使主程序决定如何处理它们。 - Noir

0
看起来需要使用"tokens"关键字...
@echo off
set themes=Sun,Granite,Twilight

call :parse "%themes%"
goto :end

:parse
setlocal
set list=%1

for /F "delims=, tokens=1*" %%f in (%list%) do (
    rem if the item exist
    if not "%%f" == "" call :getLineNumber %%f
    rem if next item exist
    if not "%%g" == "" call :parse "%%g"
)
endlocal
goto :end

:getLineNumber
setlocal
echo file name is %1
set filename=%1
endlocal 

:end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接