开发者

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

I have a frustrating problem when I want to use the pipe(|) feature with the Wi开发者_如何学编程ndow'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
    


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


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


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.


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


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


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

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜