为什么这个批处理脚本中的 FOR /f 循环会评估空白行?

8

我正在尝试编写一个批处理脚本,其中包括获取计算机上所有磁盘驱动器列表的功能。基本代码看起来像这样:

REM Build the list of disk drives to monitor
SETLOCAL enabledelayedexpansion
FOR /f "skip=1 tokens=1 delims=:" %%a in ('"WMIC logicaldisk WHERE drivetype=3 GET deviceid"') do (
    SET "DISK_DATABASES=!DISK_DATABASES!%%a|"
    SET "DRIVES_TO_MONITOR=!DRIVES_TO_MONITOR!%%a:\\|"
)

我明显地构建了两个稍微不同格式的列表以供以后使用。然而,当我运行它时,我得到的输出看起来像这样:

C|D|E||
C:\\|D:\\|E:\\|:\\|

现在,我期望两种情况下都有尾随管道,我可以处理这个问题,但我真的很困惑为什么会有一个额外的空白条目。如果我手动运行wmic命令,我可以看到输出的末尾确实有一行空白行,但我的理解是/f应该忽略空白行。
如果我打开ECHO,看起来最后一行只是作为回车符/换行符或类似的东西进入。有没有办法做到我所期望的?我错过了什么吗?我试图在循环中编写一个if条件来排除这个最后一行,但它很奇怪,从来没有起作用。感谢任何/所有帮助。
7个回答

12

我刚看到这个话题。我一直在使用findstr /v来排除空行:

FOR /f "usebackq skip=1 tokens=1 delims=:" %%a in (`WMIC logicaldisk WHERE "drivetype=3" GET deviceid ^| findstr /v /r "^$"`) do (

在我看来,这是一个更好的答案,不需要延迟扩展、:CALL或嵌套的for循环。简单易懂! - wpg4665

5
在这种情况下,最后一次迭代产生的不是一个空项,因此使用 echo %DISK_DATABASES% 会输出 C|D|E||,但是使用 echo !DISK_DATABASES! 将输出 ||D|E|??
原因在于,最后一个元素是一个单独的 <CR> 字符。而 <CR> 字符在百分号展开后会直接被删除,但是在延迟展开时不会被删除。
您可以通过使用百分号扩展来避免这种情况并将其删除。
setlocal EnableDelayedExpansion
FOR /f "skip=1 tokens=1 delims=:" %%a in ('"WMIC logicaldisk WHERE drivetype=3 GET deviceid"') do (
  set "item=%%a"
  call :removeCR

  if not "!item!"=="" (
    SET "DISK_DATABASES=!DISK_DATABASES!!item!|"
    SET "DRIVES_TO_MONITOR=!DRIVES_TO_MONITOR!!item!:\\|"
  )
)
goto :eof
:removeCR

:removeCR
set "Item=%Item%"
exit /b

哇,我们总是学习和成长,不是吗?我想知道为什么像WMIC这样的实用程序首先会在一行上产生单个<CR>字符...或者这可能是@matt发现的结果。 - Andriy M
我想这不是Unicode的问题,因为即使在XP上,微软也不知道他们自己的行结尾应该是什么。IPconfig(XP)也会产生不一致的行结尾。 - jeb
感谢jeb和matt的回答。两种解决方案都有效,但是jeb的解决方案不需要我创建临时文件,所以我接受了他的建议。 - Morinar

4
我发现了一种更高效、更可靠的方法来去掉每行末尾不需要的<CR>,无需临时文件,也无需使用CALL。
我不理解FOR /F如何将WMIC Unicode输出转换为ASCII的机制。通常,FOR /F无法读取Unicode。但是不管它是怎么工作的,每个转换后的行都以<CR><CR><LF>结尾。FOR /F在每个<LF>处断行,然后如果该行的最后一个字符是<CR>,它会去除该最后一个<CR>,这样就留下了不需要的<CR>
解决方案是简单地再通过一个FOR /F来处理每一行 :-)
@echo off
setlocal enableDelayedExpansion
for /f "skip=1 delims=" %%A in (
  'wmic logicaldisk where "drivetype=3" get deviceid'
) do for /f "tokens=1 delims=:" %%B in ("%%A") do (
  set "disk_databases=!disk_databases!%%B|"
  set "drives_to_monitor=!drives_to_monitor!%%B:\\|"
)

相比于使用普通扩展方法,这种方法更加可靠,因为您不必担心引用或转义特殊字符。例如,使用普通扩展的CALL方法无法处理像"this & that" & the other这样的字符串。但是,此方法对于这样的字符串没有问题。


使用 for/f 处理这个问题是个好主意,通常我会觉得这种行为很烦人,但在这种情况下它确实很有用。 - jeb

4
根据http://ss64.com/nt/for_f.html,许多新的命令和实用程序(例如WMIC)以Unicode格式输出文本文件,这些文件无法被FOR命令读取,因为它期望ASCII格式。要转换文件格式,请使用TYPE命令。因此,WMIC和FOR似乎不能很好地协作。

1
Add ^| findstr . and you will get only not blank lines.

    REM 构建要监视的磁盘驱动器列表
    SETLOCAL enabledelayedexpansion
    FOR /f "skip=1 tokens=1 delims=:" %%a in (
    '"WMIC logicaldisk WHERE drivetype=3 GET deviceid" ^| findstr .') do (
        SET "DISK_DATABASES=!DISK_DATABASES!%%a|"
        SET "DRIVES_TO_MONITOR=!DRIVES_TO_MONITOR!%%a:\|"
    )
    


0
运行以下命令:
wmic blah /value | find "=" >> wherever

输出结果将为:

field=value

请注意,不会有额外的空行。

0

我处理这个问题的标准惯用语是将 WMIC 的输出写入临时文件,然后使用 TYPE(将 UTF16 缩减为 ASCII)将其馈送到 FOR 中,就像这样:

:: Standard environment setup
setlocal enabledelayedexpansion
:: Every variable whose name starts with "tf" will identify a temporary
:: file - remove any such variables inherited from the parent environment
for /f %%V in ('set tf') do set %%V=
:: Create some temporary filenames. Prefix all of them with this script's
:: own name to avoid clashes with those owned by other scripts.
for /l %%I in (1,1,4) set tf%%I="%temp%\%~n0-temp%%I.txt"

:: Use temp file to work around coding mismatch between WMIC out and FOR in
wmic product where "name like 'Microsoft Office %% 2010'" get packagecache >!tf1!
for /f "skip=1" %%P in ('type !tf1!') do if exist "%%~P" msiexec /x "%%~P" /passive /norestart

:: Before quitting script, clean up temporary files
for /f %%V in ('set tf') do if exist "%%~V" del /f /q "%%~V"
endlocal

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