4

This snippet of code

forfiles /P %pathname% /M *.log /c "cmd /c echo @file"

will happily list out a bunch of files.

We could replace the ECHO with RENAME or MOVE or many other combinations of internal commands: the exception being a CALL to a label name.

For instance replacing the ECHO above as follows:

forfiles /P %pathname% /M *.log /c "cmd /c CALL :listit @file"
 : :
 : :
exit /b

:listit
echo %1
exit /b

Which I would have hoped would have the same output gives an error message, typically

Invalid attempt to call batch label outside of batch script.

This behaviour is not something that Microsoft's documentation remarks on, and since the alternatives are perfectly adequate, I've not gone looking to make it work.

But I am interested in the Well, why not?!, and so I'm posing the Question

Why can't we use a CALL :label command in the FORFILES script?

Thank you.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
ilinkcs
  • 142
  • 8
  • 2
    `cmd /c` opens a new instance of `cmd`. So the following is just a single command line; there is no context of a batch file. You can use `for` instead: `for %%a in ("%pathname%\*.log) do call :listit "%%a"` – Stephan Feb 03 '20 at 13:06
  • It's because the call instruction you are using is being run within another cmd.exe instance not as part of your batch files cmd.exe instance. – Compo Feb 03 '20 at 13:07
  • [@Stephan](https://stackoverflow.com/users/2152082/stephan), thanks, yes that is what I have used. – ilinkcs Feb 03 '20 at 13:09
  • [@Compo](https://stackoverflow.com/users/6738015/compo) yes, I wondered if that might not be the case, so I tried a `CALL :listit.bat @file` as an alternative, reasoning that would work, but hmmm ... no it didn't. – ilinkcs Feb 03 '20 at 13:12
  • That is no different, you cannot call a label with an argument, in a separate cmd.exe instance and expect it to work like that. As you don't appear to be using `forfiles` for something specific, _like incorporating its `/D` option_, you may wish to consider `findstr /m "^" "%pathname%\*.log"` as an alternative. – Compo Feb 03 '20 at 13:16
  • The `CALL :label` statement is for calling a label inside the current batch file. Use `CALL listit.bat @file` instead. See https://ss64.com/nt/call.html – DodgyCodeException Feb 03 '20 at 13:19
  • [@Compo](https://stackoverflow.com/users/6738015/compo) you are right, that is a very limited script, to ilustrate the problem so that I could get an understanding of the "why" which has been explained very well. – ilinkcs Feb 03 '20 at 13:22
  • [DodgyCodeException](https://stackoverflow.com/users/8473028/dodgycodeexception) Thanks for the reference, yes, I've had a read of SS64,before posing the Q and also Rob van der Woude, it didn't seem to give me the answer. But I understand the principle clearly enough now. Thanks to all. – ilinkcs Feb 03 '20 at 13:26

1 Answers1

6

As @Stephan commented already, it can't uses labels, because it's executed in a command line context.

In most of the cases you can use the simpler FOR command.

But in cases where you need forfiles and labels, this can be solved with a simple trick.

  1. At the begin of your batch file you need the line FOR /F ... goto :%%L.
  2. And when calling the label it has to be enclosed forfiles ... call %~d0\:<myLabel>:\..\%~pn0
@echo off
FOR /F "tokens=3 delims=:" %%L in ("%~0") DO goto :%%L

...
forfiles /P %pathname% /M *.log /c "cmd /c CALL %~d0\:listit:\..\%~pn0 @file"
...

:listit
echo %1
exit /b

A good advice from aschipfl:
To add some more stability (against spaces in the path/filename) you can enclose the call destination into quotes call 0x22%~d0\:listit:\..\%~pn00x22 @file.

This is a special syntax only in forfiles, where 0xHH is the hexadecimal code for the character 0x22=".

jeb
  • 78,592
  • 17
  • 171
  • 225
  • Thanks [Jeb](https://stackoverflow.com/users/463115/jeb) that's a great piece of scripting – ilinkcs Feb 03 '20 at 13:28
  • 2
    So the purpose of embedding the jump label into the script path is to not alter the number of arguments and therefore the output of `%*` then, right? I'd put the script path in between (escaped) quotes: `0x22%~d0\:listit:\..\%~pn00x22`... – aschipfl Feb 03 '20 at 14:31
  • 1
    @aschipfl You are right. This technique doesn't influence the normal behaviour of a script, especially when it already uses `%1`... And it's a generic trampoline to any function. I heavily use it in my batchLibrary – jeb Feb 03 '20 at 14:41
  • gets better and better... that's a deep apreciation of what's happening in the engine room. – ilinkcs Feb 04 '20 at 11:55
  • @jeb can you provide a reference for this? I tried it but it does not seem to jump to the specified label. As I understand, it basically does this `cmd /c D:\:mylabel:\..\myscript arg1`, correct? I also tried running this directly on the commandline but it does not work. – kapitanluffy Nov 09 '20 at 14:46
  • @kapitanluffy Did you add some debugging code to the first `FOR /F "tokens=3 ...`? If the problem still persists you should ask a separate question – jeb Nov 09 '20 at 14:51