关于批处理文件:如何将Windows CMD pipe(|)功能与CALL:Label命令选项一起使用?

How to use Windows CMD pipe( | ) feature with CALL :Label command option?

当我想在Windows的CMD shell的CALL:Label选项中使用pipe(|)功能时,我遇到了一个令人沮丧的问题。我有一个非常小的示例(如下):call-test.cmd和示例输出。

问题的根源是将CMD脚本的输出传递到另一个程序,例如tee实用程序或find命令。例如:

1
    @call   :Label-02  param  | tee call-test.log

它将在标签Label-02处启动当前命令文件,并将输出通过管道传输到tee。不幸的是,在带有" call:label"选项的行上使用竖线字符(|)会产生错误:

1
Invalid attempt to call batch label outside of batch script.

而" call example.cmd | tee example.log"工作正常。

另一个IO重定向>正常。这只是使用" call:label pipe(|)"失败的一种情况。对我来说,它就像一个Windows错误。

是否有人有解决方法和/或知道解释?

谢谢,

  • 通话测试输出

    1
    2
    3
    4
    5
    6
    7
    c:\> call-test
        [start]
        label 03 :: p1
    Invalid attempt to call batch label outside of batch script.
    Invalid attempt to call batch label outside of batch script.
        [done]
    Press any key to continue . . .
  • 通话测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    @echo off
    @rem   call-test.cmd
    @rem  _________________________________________________
    @rem    Test :label call option for .cmd files.
    @rem
    @echo   ^  [start]
    @call   :Label-03  p1
    @call   :Label-02  second  | find""
    @call   :Label-02  second  | tee call-test.log
    @goto   Done
    @rem  _________________________________________________
    :Label-01
    @echo   ^  label 01 :: %1
    @goto Exit
    @rem  _________________________________________________
    :Label-02
    @echo   ^  label 02 :: %1
    @goto Exit
    @rem  _________________________________________________
    :Label-03
    @echo   ^  label 03 :: %1
    @goto Exit
    @rem  _________________________________________________
    :Done
    @echo   ^  [done]
    @pause
    @rem  _________________________________________________
    :Exit
    @exit /b


原因是,管道在cmd上下文中开始于两侧(都在一个cmd框中并行运行),并且每一侧都被解释为真正的命令行参数,并且在cmd上不允许使用行标签。

但是,如果重新启动批处理,则可以调用函数。

1
2
if not"%1"=="" goto %1
@call"%~0" :Label-02  param  | tee call-test.log

编辑:完整的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@echo off
if not"%~1"=="START" goto :normalStart
shift
shift
call %0 %1 %2 %3 %4 %5 %6 %7 %8
exit /b

:normalStart
rem   call-test.cmd
rem  _________________________________________________
rem    Test :label call option for .cmd files.
rem
echo   ^  [start]
rem call   :Label-03  p1
rem call   :Label-02  second  | find""
call"%~dpf0""START" :Label-02  second  |  tee call-test.log
goto   Done
rem  _________________________________________________
:Label-01
echo   ^  label 01 :: %1
goto Exit
rem  _________________________________________________
:Label-02
echo   ^  label 02 :: %1
goto Exit
rem  _________________________________________________
:Label-03
echo   ^  label 03 :: %1
goto Exit
rem  _________________________________________________
:Done
echo   ^  [done]
pause
rem  _________________________________________________
:Exit
exit /b


我意识到这有点晚了,但可能会对其他人有所帮助。这不是一个hack,更多的解决方法。如果需要的话,还是漂亮的"不错的工具"。
我正在使用以下解决方案来解决类似问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@echo off

SET CURRENT_SCRIPT_IS=%~dpnx0
IF NOT"%RUN_TO_LABEL%" =="" (
  call :%RUN_TO_LABEL% %1 %2 %3 %4 %5 %6 %7 %8 %9
  goto:eof
)

goto over_debug_stuff

:debugstr
  echo %~1
  echo %~1>>%2
goto:eof

:debuglbl
  SET RUN_TO_LABEL=%1
  for /f"tokens=*" %%L in ('%CURRENT_SCRIPT_IS% %3 %4 %5 %6 %7 %8 %9') do (
    echo %%L
    echo %%L>>%2
  )
  SET RUN_TO_LABEL=
goto:eof

:over_debug_stuff

call :debugstr"this is a string""test_str.txt"
call :debuglbl tst"test_lbl.txt"

goto:eof

:tst
  echo label line 1
  echo label line 2
  echo label line 3
goto:eof

关于它的好处是,我可以将"标头"复制粘贴到我需要在其中运行的任何批处理脚本中。由于不需要它,所以我没有费心使其递归安全,因此请确保您在放入之前先测试该方案。
显然,我在这些debug *调用中具有包装器功能,因此我不会在每次调用时都随身携带日志文件。另外,在调试日志调用中,我还测试了调试标志,因此包装器本身具有更多的逻辑,这主要取决于所使用的脚本。


显而易见的解决方法是将调用的输出重定向到临时文件,将其用作find / tee的输入,然后删除文件:

1
2
3
@call :Label-02 second > tmp
tee call-test.log < tmp
delete tmp


这是jebs答案的更简洁版本。

它使用相同的goto技术,但不是在重新输入时传递唯一的" START"参数,而是使用子字符串提取测试第一个参数的第一个字符是否为":",并且仅在有标签的情况下才调用goto。这简化了调用,但是您不能将子字符串提取与%1变量或空/不存在的变量一起使用,因此必须使用始终包含值的临时变量。无论如何,它都需要temp var来记住标签,因为SHIFT /1将删除第一个:LABEL参数,但是它只需要使用SHIFT一次,并且在调用站点不需要额外的参数。

[更新:必须执行SHIFT /1以避免在脚本使用的情况下更改%0]

1
2
set"LABEL=%~1_"
if"%LABEL:~0,1%"==":" SHIFT /1 & goto %LABEL:~0,-1%

因此,以下脚本显示了如何使用传递给原始脚本的参数以及如何重新输入以处理标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@echo off

set"LABEL=%~1_"
if"%LABEL:~0,1%"==":" SHIFT /1 & goto %LABEL:~0,-1%

call"%~f0" :LABEL_TEST param1 p2 | findstr foo

echo param 1 is %1

exit /b

:LABEL_TEST
echo (foo) called label with PARAMS: %1 %2 %3
echo (bar) called label with PARAMS: %1 %2 %3
exit /b

将输出:

1
2
3
C:\>call-test-with-params TEST
(foo) called label with PARAMS: param1 p2
param 1 is TEST

echo (bar)行被管道剥离到findstr

问题的解决方案:

该脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@echo off

set"LABEL=%~1_"
if"%LABEL:~0,1%"==":" SHIFT /1 & goto %LABEL:~0,-1%

@rem   call-test.cmd
@rem  _________________________________________________
@rem    Test :label call option for .cmd files.
@rem
@echo   ^  [start]
@call"%~f0" :Label-03  p1
@call"%~f0" :Label-02  second  | find""
@call"%~f0" :Label-02  second  | tee call-test.log
@goto   Done
@rem  _________________________________________________
:Label-01
@echo   ^  label 01 :: %1
@goto Exit
@rem  _________________________________________________
:Label-02
@echo   ^  label 02 :: %1
@goto Exit
@rem  _________________________________________________
:Label-03
@echo   ^  label 03 :: %1
@goto Exit
@rem  _________________________________________________
:Done
@echo   ^  [done]
@pause
@rem  _________________________________________________
:Exit
@exit /b

将回显:

1
2
3
4
5
6
7
C:\>call-test
    [start]
    label 03 :: p1
    label 02 :: second
    label 02 :: second
    [done]
Press any key to continue . . .

并且call-test.log具有正确的内容:

1
2
C:\>more call-test.log
    label 02 :: second


我认为您可以使用" |"然后将管道视为常规字符。


^放在管道命令的前面....例如

@call :Label-02 second ^| find""