【问题标题】:How to correct variable overwriting misbehavior when parsing output?解析输出时如何纠正变量覆盖错误行为?
【发布时间】:2014-07-25 17:53:44
【问题描述】:

我正在使用以下代码检查批处理文件中的基板信息。

BaseboardCheck.cmd:

@echo off
setlocal EnableDelayedExpansion

for /f "tokens=1,2* delims==" %%a in ('wmic baseboard get /format:list') do ( 
    
    if ["%%a"] EQU ["Product"] (
        set PlatformInfo=%%b

        if defined PlatformInfo (
            echo.!PlatformInfo!
            echo.!PlatformInfo!This overwrites the variable
        )
    )

    if ["%%a"] EQU ["Version"] (
        set BaseboardVersion=%%b

        if defined BaseboardVersion (
            echo.!BaseboardVersion!
            echo.!BaseboardVersion!This overwrites the variable
        )
    )   
)

上述问题:当回显出来时,变量会被覆盖而不是附加到。

输出:

DX79SI
This overwrites the variable
AAG28808-600
This overwrites the variable

我想得到的是:

DX79SI
DX79SIThis overwrites the variable
AAG28808-600
AAG28808-600This overwrites the variable

我已经为此花费了几个小时(并将继续这样做),但我希望其他人也遇到了这个问题。我希望以后遇到这个解析问题的任何人都可以避免它。

由此产生的另一个问题是它似乎破坏了条件逻辑。

更新:

在所有的帮助之后,我想出了这个解决方案:

for /f "skip=2 tokens=1,2 delims=," %%a in ('wmic baseboard get Product^,Version^,Width /format:csv') do (
    set Platform=%%a
    set BaseboardVersion=%%b
)
echo.Platform: %Platform% 
echo.Version %BaseboardVersion%

【问题讨论】:

  • 好问题。 setlocal enabledelayedexpansion 就是这么复杂。我总是使用调用将参数传递给另一个函数。

标签: windows batch-file for-loop cmd wmic


【解决方案1】:

哇,真的很难找出这里发生了什么。

首先,我无法相信批处理文件执行会发生什么。

经过多次命令行尝试,我在 Windows XP SP3 x86 上执行

wmic.exe baseboard get /format:list > Output.txt

并使用文件管理器 Total Commander 的查看器查看文件 Output.txt
我在顶部看到两条空行,但这并不重要。所以我继续进行其他试验。

后来我在文本编辑器 UltraEdit 中打开 Output.txt 并立即在状态栏 U-DOS 上看到,表明输出文件采用 UTF-16 Little Endian 编码,具有 DOS 行终止符。我切换到十六进制编辑模式并且可以看到:

00000000h: FF FE 0D 00 0A 00 0D 00 0A 00 43 00 61 00 70 00 ; ÿþ........C.a.p.
00000010h: 74 00 69 00 6F 00 6E 00 3D 00                   ; t.i.o.n.=.

所以输出文件确实是一个用 UTF-16 LE 和 BOM(字节顺序标记)编码的 Unicode 文件。没有 CR CR LF。所有行终止都是正确的 CR LF 对(回车 + 换行)。

现在我在 Stack Overflow 上搜索 [batch-file] 包含 wmic Unicode 字样的问题,发现 cmd is somehow writing Chinese text as output

dbenham 的公认答案不好,因为它创建了wmic.exe 的 Unicode 输出的 ANSI 版本,但 ANSI 文件现在确实包含 0D 0D 0A (= CR CR LF)。

Dharma Leonardi 的答案更好,因为使用命令 type 的解决方案可以将 Unicode 输出正确转换为 ANSI 输出,只要输出不包含 ANSI 代码页中不可用的字符。

注意:我在这里使用的术语 ANSI 对于每个字符编码一个字节并不是真正 100% 精确。请参阅有关 character encodingcode pageWindows code page 的 Wikipedia 文章。在 Windows 命令处理器控制台中,默认使用 OEM code page,例如 437(北美国家)或 850(西欧国家)。另请参阅Why are Danish characters not displayed as in text editor on executing batch file? 有一个示例解释了如何使用北美和西欧国家通常使用的各种字符编码使用哪些字节对相同的丹麦小文本进行编码。

但是,在将批处理代码更改为使用 ANSI 编码处理 wmic.exe 的输出后,if defined BaseboardVersion 的行始终评估为 true,尽管我看不到变量 BaseboardVersion 包含任何数据并且因此下一行导致显示回显状态。

我又花了一些时间才发现在条件上方插入set > Variables.txt 并查看此文件,在我的计算机上版本字符串只是一个空格字符。 Version 的值是所有键中唯一没有等号右侧的字符串的值,该字符串仅由一个空格组成。

这是最终在我的计算机上运行并产生预期输出的批处理文件:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
%SystemRoot%\System32\wbem\wmic.exe /OUTPUT:"%TEMP%\UnicodeData.tmp" baseboard get /format:list
for /f "tokens=1,2* delims==" %%I in ('type "%TEMP%\UnicodeData.tmp"') do (
    if "%%I" == "Product" (
        if not "%%J" == "" if not "%%J" == " " (
            set "PlatformInfo=%%J"
            echo !PlatformInfo!
            echo !PlatformInfo!This overwrites the variable
        )
    ) else if "%%I" == "Version" (
        if not "%%J" == "" if not "%%J" == " " (
            set "BaseboardVersion=%%J"
            echo !BaseboardVersion!
            echo !BaseboardVersion!This overwrites the variable
        )
    )
)
del "%TEMP%\UnicodeData.tmp"
endlocal

【讨论】:

  • 您进入的深度令人难以置信!然而,MC ND 有一个更优雅的解决方案。我很高兴知道这并非微不足道。
  • CRCRLF 仅在 wmic 使用 C 运行时标准 I/O 写入 consolepipe 而不是磁盘文件时发生。它将以 CRLF 结尾的字符串写入文本模式 CRT 文件描述符,然后将 LF 转换为 CRLF,从而产生 CRCRLF。写入磁盘文件采用不同的路径,该路径调用自定义类方法wmic!CFileOutputStream::Write,该方法在没有将 LF 文本模式转换为 CRLF 的情况下写入字符串。
  • 正如我所说,当输出定向到磁盘文件(即>List.txt)时,不会发生 CRLF => CRCRLF,因为它在不使用 C 运行时的代码中采用单独的路径标准输入/输出。通常人们调用 wmic.exe 而没有将输出定向到管道,例如在 CMD for /f 循环中或转到 more.com。
  • 我的意思是一个不是FILE_TYPE_DISK的文件,它可能是FILE_TYPE_CHAR(例如控制台)或FILE_TYPE_PIPE。当 wmic 写入控制台或管道时附加调试器。您会看到它将以 CRCRLF 结尾的字符串传递给 WriteFile。这是使用文本模式下的文件描述符将一个以 CRLF 结尾的字符串传递给fprintf(C 标准 I/O)。在这种情况下,控制台和 more.com 都具有弹性。 CMD 的 for /f 循环会去除最后的 CRLF,但每一行都会保留一个 CR。
  • 顺便说一句,wmic 实际上将 OEM(例如代码页 437 或 850)写入管道或控制台,而不是 ANSI(例如代码页 1252)。在某些语言环境中它们是相同的,但我认为在所有西方语言环境中它们是不同的代码页。
【解决方案2】:

是的,你有问题,但不是你想的那样。

wmic 有一个特殊的行为:在每行输出的末尾有一个额外的回车,即每行以0x0d 0x0d 0x0a 结尾

这个额外的回车存储在你的变量中,当回显到控制台时,你会得到数据和回车,所以,如果变量后面跟着更多的文本,因为光标已经定位在行(回车),此文本会在之前的回显数据上回显。

如何解决?

@echo off
setlocal enabledelayedexpansion

for /f "tokens=1,* delims==" %%a in ('wmic baseboard get /format:list') DO ( 


    if ["%%a"] EQU ["Product"] (
        for /f "delims=" %%c in ("%%b") do set "PlatformInfo=%%c"

        if defined PlatformInfo (
            echo(!PlatformInfo!
            echo(!PlatformInfo!This does not overwrite the variable
        )
    )

    if ["%%a"] EQU ["Version"] (
        for /f "delims=" %%c in ("%%b") do set "BaseboardVersion=%%c"

        if defined BaseboardVersion (
            echo(!BaseboardVersion!
            echo(!BaseboardVersion!This does not overwrite the variable
        )
    )   
)

在这种情况下,无需更改代码逻辑,可以使用附加的for /f 命令删除附加的回车

【讨论】:

  • 其实不是wmic搞错了,是for /F做的Unicode-to-ASCII/ANSI转换...
  • @aschipfl,不,当 piped 时,wmic 命令的输出不是 unicode,并且数据流包括结尾的 CR CR LF 序列。 for 命令检索所有输出,处理输入行的代码搜索第一个 LF 作为行终止符。一旦找到LF,如果前一个字符是CR,它将被删除,其余数据(从开始)是要处理的行。在这种情况下,wmic 行尾中的第一个 CR 包含在正在处理的数据中。
  • 真的是wmic命令造成的吗?我很确定这是由为管道的任一侧调用的cmd 实例引起的,它们都默认为 ANSI 模式(/A),我不知道wmic 区分它的输出去哪里并改变它.很抱歉提出了错误的声明!
  • @aschipfl,在 Mofi 的关于wmic 输出的答案中从 eryksun 读取 cmets。 “问题”不在于任何命令(wmicfor /f),而在于它们之间的交互。
【解决方案3】:

正如已经回答的那样,问题在于wmic 的行尾。

如果不使用行尾,则可以解决此问题:wmic baseboard get Product,Version,Width 使用三个标记:Product、Version 和 Width(在大多数情况下为空)。所以输出将是:DX79SI,AAG28808-600, 我们正在使用令牌 1 和令牌 2,忽略令牌 3(这会有问题)

set Platform=undefined
set BaseboardVersion=undefined

for /f "tokens=1,2 delims=," %%a in ('wmic baseboard get Product^,Version^,Width^|findstr "."') do (
 set Platform=%%a
 set BaseboardVersion=%%b
)
echo it is a %Platform% with Version %BaseboardVersion%. No overwriting

我还添加了delims=,,以防任何字符串包含空格(product 不太可能)

【讨论】:

  • 我也喜欢这个。它并没有像我希望的那样工作,但它激励我写一些更干净的东西。
猜你喜欢
  • 1970-01-01
  • 2021-10-26
  • 2016-05-04
  • 1970-01-01
  • 2022-10-07
  • 2011-08-04
  • 2021-04-05
  • 2016-06-11
  • 2021-08-17
相关资源
最近更新 更多