20

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
    
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
will
  • 4,799
  • 8
  • 54
  • 90

6 Answers6

16

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
jeb
  • 78,592
  • 17
  • 171
  • 225
  • 1
    To be fair to other readers, I need to point out that this suggestion is in error. There are two things broken. The first is the original issue with a pipe(|) not working with the call. And the second is that the syntax: call "%~0" :Label-02 param Fails when you try to use it. While I could and did try jumping to a label manually with a GOTO, it is a hack and it would be better to have a second .CMD script. (Which is the thing I want to avoid here). – will Nov 25 '10 at 21:34
  • I tested it with XP and Vista and it works (also with the pipe), only the %1 in your functions has to be moved to %2 – jeb Nov 26 '10 at 19:10
  • Fair comment -- The 'solution' sought; is to **pipe output** to the 'tee' command. I know the '@call' and parameters work if we hack them about. – will Jan 07 '11 at 14:54
  • I hope, the complete sample is what you want – jeb Jan 07 '11 at 17:05
  • well, is `| tee call-test.log` hardcoded into the batch file, can that be avoided? – n611x007 May 21 '15 at 10:01
  • NB: using `shift /1` will avoid changing %0 in case that's used by the script – Sam Hasler Feb 08 '16 at 16:00
  • @n611x007 -- I do not understand your question. The use-case is for an internal `call :someLabel | tee someFile`. Which fails. And that is the _whole_ question -- How to make that work. Which means the `... | tee fileName` is mandatory. It may be a variable, e.g. '%TEE_TO_LOG%`, say. I am pretty sure I tried that, with no luck. – will Jan 31 '18 at 14:36
1

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
Sam Hasler
  • 12,344
  • 10
  • 72
  • 106
  • The question is, "how to have the PIPE(|) send data to the `tee` in the call. There's no interest in the `%1` parameters of labels for this example. The objective is to capture output from one or more sub-routine calls. So far none of the suggestions regarding the pipe worked. – will Feb 05 '16 at 11:46
  • 1
    The two lines I posted will solve your problem if you put `"%~dpf0"` (name of current script) as the first parameter to `call`. See the example script I've added. – Sam Hasler Feb 05 '16 at 15:35
  • That's a really good *work-around*. The result of putting the **"%~dpf0"** in the call is the same as *not*-using `CALL` command; back to the old DOS before version-3.3; and invokes `call-test` as a command file directly (but with the label as parameter). The *fix* at the top handles the label passed as you correctly point out. – will Feb 09 '16 at 05:30
  • What fix at the top? I can only see the same workaround in jebs answer. And as jeb states, because the pipe starts both sides in a new cmd context, you can't do what you want. The is no "fix" there is only restarting the batch and jumping to the desired label. – Sam Hasler Feb 09 '16 at 09:24
  • If you don't like typing `@call "%~f0"` or think it looks messy/verbose in the script you can put it in a variable: `set "CALL=@call ""%~f0""" ` then you can `%CALL% :LABEL_1 param1` – Sam Hasler Feb 09 '16 at 10:22
  • Hi - I guess I'm not making my point clear. On Window/MS-DOS when you use the `call` command as: "`CALL call-test :string`", that does NOT use the Command file "`Call :label`" mechanism. What it does, is to recursively call the command file: "`call-test.cmd`"; without the hack using `SHIFT /1` to drop the ":string", the result is a *stackoverflow* by the command shell. This was the case for every BAT and CMD file before the call command was added with DOS v3.3. "`CALL ` [...]" doesn't execute the CALL-command, it opens & executes the file. Thus losing local environment, etc. – will Feb 09 '16 at 12:24
  • Thanks for bearing with me and explaining that. What do you mean by "Thus losing local environment, etc.", I've testing setting variables after the goto jump has been passed on the first run of the script, and they are available upon re-entry, so as far as I can tell, the environment is preserved. In what way is the Shift hack insufficient? – Sam Hasler Feb 09 '16 at 13:49
1

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
chalup
  • 8,358
  • 3
  • 33
  • 38
  • That's more helpful. The value of **tee** is to catch a problem in the flow of the work. These are long jobs. I had log files before and the job was finished before I knew there was a problem. – will Nov 25 '10 at 21:39
  • 1
    Not to mention ... if the label or anything asks for user input it will just 'hang' to most users. – Brent Rittenhouse Jun 07 '19 at 10:48
1

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.

ciuly
  • 532
  • 5
  • 13
  • 1
    I guess this workaround is specifically for `call :label ... | tee ...`, not for `call :label ... | any_command ...`, am I right? – Andriy M Jan 23 '12 at 21:07
  • more or less. I have written it specifically to log and display teh same things without executing it twice (just as the tee solution) but you can do anything in the "debuglbl" function. so you would basically move the any_command in the debuglbl body (obviously name the label accordingly) and you can have any functionality you like (maybe the "any" is a bit strong here) the error basically is pretty self-explanatory and the net is also full with the same explanations for it: the call :label is not a command line feature but a batch script one so you can't use it there. – ciuly Jan 24 '12 at 19:41
  • this "command line" and "batch script" differences is very similar to the sql and plsql differences. just as you do for %%I in when inside the batch script but you must write for %I in, when you're in command line, there are also otehr differences, among which you cannot call labels. Which is obvious since the label is not defined in the command interpreter environment, its defined in the script. Just like in oracle, you can use for example the BOOLEAN type in plsql, but not in sql. I think you get the picture – ciuly Jan 24 '12 at 19:45
  • More or less. :) I mean, given the parallel you've drawn and knowing what I know about the differences between batch files and the command line, I do now understand better the nature of the differences between SQL and PL/SQL. (With regard to SQL, I've been mostly a SQL Server person all this time, you see.) Still, the main point is, I've been educated either way, thanks. :) – Andriy M Jan 24 '12 at 20:40
  • you have a nice cat, ciuly – n611x007 May 21 '15 at 09:59
-1

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

Ravi Sambangi
  • 343
  • 3
  • 2
  • 2
    You are right, but that wasn't the question. The OP don't wan't to handle the pipe character, the output of a function should be piped – jeb Oct 25 '13 at 20:42
-3

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

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

SchmitzIT
  • 9,227
  • 9
  • 65
  • 92
Jack
  • 3
  • 1