如何将 Windows CMD 管道 ( | ) 功能与 CALL :Label 命令选项一起使用?

发布于 2024-10-03 18:11:43 字数 1645 浏览 6 评论 0原文

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

问题的核心是将 CMD 脚本的输出通过管道传输到另一个程序,例如 tee 实用程序或 find 命令。例如:

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

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

Invalid attempt to call batch label outside of batch script.

而“call example.cmd | tee example.log”则工作得很好。

其他 IO 重定向> 工作正常。这只是使用“call :label pipeline(|)”失败的一种情况。对我来说,它看起来就像一个 Windows 错误。

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

谢谢, 将


  • 调用测试输出

    <前><代码>c:\>呼叫测试 [开始] 标签 03::p1 在批处理脚本之外调用批处理标签的尝试无效。 在批处理脚本之外调用批处理标签的尝试无效。 [完毕] 按任意键继续。 。 。
  • 调用测试

    <前><代码>@echo 关闭 @rem call-test.cmd @雷姆_________________________________________________ @rem Test:.cmd 文件的标签调用选项。 @雷姆 @echo ^ [开始] @call:Label-03 p1 @call :Label-02 第二个 |寻找 ” ” @call :Label-02 第二个 | tee 呼叫测试.log @goto 完成 @雷姆_________________________________________________ :标签-01 @echo ^ 标签 01::%1 @goto 退出 @雷姆_________________________________________________ :标签-02 @echo ^ 标签 02::%1 @goto 退出 @雷姆_________________________________________________ :标签-03 @echo ^ 标签 03::%1 @goto 退出 @雷姆_________________________________________________ :完毕 @echo ^ [完成] @暂停 @雷姆_________________________________________________ :出口 @退出/b

I have a frustrating problem when I want to use the pipe(|) feature with the Window's CMD shell's CALL :Label option. I have a very small example (below): call-test.cmd and sample output.

The nub of the issue was/is to pipe the output of a CMD script to another program, for example the tee utility, or find command. For example:

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

Which would start the current command file at the label Label-02 and pipe the output to tee. Unfortunately using the pipe character(|) on the line with "call :label" option gives an error:

Invalid attempt to call batch label outside of batch script.

Whereas, "call example.cmd | tee example.log", works just fine.

The other IO redirection > works OK. It is just the one case when "call :label pipe(|)" is used that fails. To me it just looks like a windows bug.

Does anyone have a workaround and/or know of an explanation?

Thanks,
Will


  • call-test output

    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 . . .
    
  • call-test

    @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
    

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

芸娘子的小脾气 2024-10-10 18:11:43

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

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

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

编辑:完整示例

@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

The cause is, that a pipe starts both sides in a cmd context (both run parallel in one cmd-box), and each side is interpreted as a real command line argument, and on the cmd line labels aren't allowed.

But you can call your function, if you restart your batch.

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

EDIT: The complete sample

@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
牵强ㄟ 2024-10-10 18:11:43

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

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

The obvious workaround is to redirect output of call to temporary file, use it as an input for find/tee, then delete file:

@call :Label-02 second > tmp
tee call-test.log < tmp
delete tmp
笑脸一如从前 2024-10-10 18:11:43

我意识到这来得有点晚,但可能对其他人有帮助。这不完全是一个黑客,更多的是一个解决方法。或者如果你必须的话,相当“不错的黑客”。
我正在使用以下解决方案来解决类似的问题:

@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* 调用有包装函数,这样我就不会在每次调用时携带日志文件。另外,在我的调试日志调用中,我还测试了调试标志,因此包装器本身有更多的逻辑,这主要取决于所使用的脚本。

I realize this comes a bit late, but might be helpful for others. this isn't quite a hack, more a workaround. Or pretty "nice hack" if you must.
I'm using the following solution to a similar problem:

@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

The nice thing about it is that I can copy-paste the "header" into any batch script I need to run it in. I didn't bother to make it recursive-safe as I didn't needed it, so make sure you test that scenario before putting it in.
Obviously, I have wrapper functions on these debug* calls, so that I don't carry around the log file with each call. Also, in my debug log calls, I also test for the debug flag, so the wrapper itself has some more logic to it, which mainly depends on the script used in.

┊风居住的梦幻卍 2024-10-10 18:11:43

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

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

[更新:必须执行shift /1以避免在脚本使用%0时更改它]

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

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

@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

将输出:

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

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


问题的解决方案:

此脚本:

@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

将 echo:

C:\>call-test
    [start]
    label 03 :: p1
    label 02 :: second
    label 02 :: second
    [done]
Press any key to continue . . .

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

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

This is a more succinct version of jebs answer.

It uses the same goto technique, but instead of passing a unique "START" parameter when re-entering, it tests if the first character of the first parameter is ":" using a substring extraction and only calls goto if it's a label. This simplifies the call, however you can't use substring extraction with %1 variables or empty/non-existent variables so it has to use a temporary variable that always contains a value. It needs the temp var anyway to remember the label as SHIFT /1 will remove the first :LABEL parameter, but it only has to use SHIFT once, and doesn't require an extra parameter at the call site.

[update: must do shift /1 to avoid changing %0 in case it's used by the script]

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

So the following script shows how you can use the parameters passed to the original script, as well as re-entering to process labels:

@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

will output:

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

the echo (bar) line being stripped by the pipe to findstr


solution to the question:

This script:

@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

will echo:

C:\>call-test
    [start]
    label 03 :: p1
    label 02 :: second
    label 02 :: second
    [done]
Press any key to continue . . .

And call-test.log has the correct content:

C:\>more call-test.log
    label 02 :: second
早乙女 2024-10-10 18:11:43

我认为你可以使用“|”那么管道将被视为普通字符。

I think you can use "|" then pipe is treated as just regular character.

佼人 2024-10-10 18:11:43

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

@call :Label-02 秒 ^|找到“”

put the ^ in front of the pipe command....e.g.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文