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 " "
精彩评论