【问题标题】:echo is adding space when used with a pipeecho 与管道一起使用时会增加空间
【发布时间】:2012-06-08 12:25:48
【问题描述】:

在回答 this 问题时,我发现了一些我无法解释的奇怪行为

for /f "delims=" %a in ('(for /l %z in (1,1,10^) do @echo %z^)') do @echo %a0

您会看到数字 10..100,现在只需添加管道,例如到sortmore,随便:

for /f "delims=" %a in ('(for /l %z in (1,1,10^) do @echo %z^)^|sort') do @echo %a0

%a0 之间将添加空格! 看起来像通过管道回显某些内容会添加尾随空格,可以很容易地看到:

>_tempfile echo no space here
>_tempfile echo and here's a space|more

甚至

>_tempfile <nul set /p =also a space|sort

(可能使用echo打印提示)

当没有输出重定向(无论是文件还是命令)时,不会发生这种情况。 这是一个错误还是我错过了什么?我如何摆脱空间? (除了用var:~0,-1 剥离最后一个字符的肮脏技巧)

【问题讨论】:

    标签: batch-file


    【解决方案1】:

    优秀而有趣的问题(+1)

    空间是由 CMD 解析器的管道机制引入的,而不是由 SORT 引入的。

    当您使用 FOR /F 执行命令时,该命令将在其自己的 CMD shell 中执行。此外,管道的每一侧都在其自己的 CMD shell 中执行。请参阅Why does delayed expansion fail when inside a piped block of code? 了解更多信息。

    因此,您的命令实际上实例化了 3 个 CMD shell,一个用于 FOR /F 命令,而后者又为管道的每一侧创建了 2 个。

    您可以使用 %CMDCMDLINE% 动态变量查看命令是如何被解析并输入到 CMD shell 中的。因为我们是从命令行执行命令,所以我们需要对变量名中的至少一个字符进行两次转义,以便在到达最里面的 CMD shell 之前它不会被扩展。

    这是带有结果的命令(前导 &gt; 是我的命令提示符):

    >for /f "delims=" %a in ('(echo %^^^cmdcmdline%^&for /l %z in (1,1,10^) do @echo %z^)^|sort') do @echo %a0
    1 0
    10 0
    2 0
    3 0
    4 0
    5 0
    6 0
    7 0
    8 0
    9 0
    C:\Windows\system32\cmd.exe  /S /D /c" ( echo %cmdcmdline% & FOR /L %z in (1 1 10) do @ echo %z )" 0
    

    输出的最后一行是用于管道左侧的命令行。您可以看到解析器如何在许多地方添加空格。

    您可以通过使用简单的批处理脚本来回显值而不是 ECHO 命令来规避该问题。

    echoArgs.bat

    @echo(%*
    

    现在,当您运行此命令时,您会得到想要的结果

    >for /f "delims=" %a in ('(for /l %z in (1,1,10^) do @echoArgs %z^)^|sort') do @echo %a0
    10
    100
    20
    30
    40
    50
    60
    70
    80
    90
    

    另一种规避该问题的方法是使用 ECHO 命令创建一个变量并适当地转义该变量的扩展。

    >set cmd=@(echo %z)
    
    >for /f "delims=" %a in ('(for /l %z in (1,1,10^) do %^^^cmd%^)^|sort') do @echo %a0
    10
    100
    20
    30
    40
    50
    60
    70
    80
    90
    

    编辑

    &gt;_tempfile echo and here's a space|more 的例子也很有趣。输出文件末尾有一个额外的空间。但是 Chad Nouis 是正确的,因为左侧的重定向,什么都没有被排序。任何命令都可以用在右边,结果都是一样的。

    问题的根源仍然是解析器,但解析器重组命令的方式很有趣。

    >>_tempfile echo %^cmdcmdline%|rem
    
    >type _tempfile
    C:\Windows\system32\cmd.exe  /S /D /c" echo %cmdcmdline% 1>_tempfile"
    

    注意重定向是如何从命令的开头移动到结尾的,并且显式添加了1 的文件句柄。您当然可以看到多余的空间是从哪里来的。

    【讨论】:

    • 啊,增加知识的人增加悲伤。从来没有想过管道不仅仅是标准输入/标准输出连接。接受您的回答以进行解释,但不接受解决方案。这是一个简单的:for /f "delims=" %a in ('cmd /c "for /l %z in (1,1,10) do @echo %z"^|sort') do @echo %a0
    • @panda-34 - 这当然是另一种解决方案。
    • +1,我来不及了,我喜欢这种类型的问题,但你已经用你的好分析回答了
    【解决方案2】:

    重定向操作符适用于它们最接近的命令——而不是整行。

    在您的三个测试用例中,您将echoset 命令的标准输出重定向到一个文件。通过重定向标准输出,没有任何东西可以通过管道传送到moresort

    在您的嵌套 for 循环示例中,我认为有一些不需要的括号会导致头痛。试试这个:

    for /f "delims=" %a in ('for /l %z in (1,1,10^) do @echo %z^|sort') do @echo %a0
    

    【讨论】:

    • 但是,如果您查看 _tempfile 在管道左侧创建时的内容,您会在末尾看到一个额外的空间。正如我在my answer 中解释的那样,管道解析器正在引入空间。但是您有一个有效的观点,即临时文件的内容没有通过 SORT。您的 FOR 案例解决方案有效。但有时管道的左侧可能是必须在括号中的命令块。
    • 您是否尝试执行您的命令?对您来说,结果是否已排序?
    • 我明白 panda-34 的意思了。需要括号,以便将整个 FOR 输出排序为一组。如果没有括号,排序将单独应用于每一行,这当然没有任何用处。 (这是我之前删除的评论的更正版本)
    猜你喜欢
    • 2011-01-01
    • 2013-11-15
    • 2020-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-29
    相关资源
    最近更新 更多