forfiles without cmd /c

2020-01-28 09:57发布

问题:

I can run a forfiles command with cmd /c, as expected

C:\>forfiles /c "cmd /c ping /a"
IP address must be specified.

However if I remove the cmd /c, it no longer recognizes any arguments, only the base command

C:\>forfiles /c "ping /a"
Usage: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]
            [-r count] [-s count] [[-j host-list] | [-k host-list]]
            [-w timeout] [-R] [-S srcaddr] [-4] [-6] target_name

Must I use cmd /c, even with external commands on the PATH?

回答1:

This is the original answer. Please read below the line for a complete analysis of the problem and a better way to solve it

Place two spaces between the command and its arguments

forfiles /c "ping  /a"

edited While the posted solution works for the OP question, it is not a complete answer, as there are some cases where this option does not work.

Why?

I started to debug the forfiles argument parsing routines thinking the problem is in the way the command string is parsed and converted to generate the final running command.

No, it works without any problem

The cause of the problem is in the call to the CreateProcess API function and the way the C argument parser works and how the programmers usually handle the arguments.

forfiles calls the API as

CreateProcess('c:\windows\system32\ping.exe','/a', ....)

That is, the application name and the arguments to it. Nice and clean, but problematic, because the first argument to the application is /a

Where is the problem? In the first argument. The usual argument handling in almost any program assumes that the first argument to the program is the program itself (at least its name), that is, argv[0] is the program name.

But for it to behave that way, the call to CreateProcess from forfiles should be any of

CreateProcess('c:\windows\system32\ping.exe','ping.exe /a', ....)
CreateProcess(NULL, 'c:\windows\system32\ping.exe /a', .... )

As almost everyone programming in C (and in a lot more languages that follow the same convention) is expecting that argv[0] (arguments value table first position) will be the program name and argv[1] (arguments value table, second position) will be the first argument, and as this is not the case in forfiles started processes, the first argument will be ignored because the first real argument will be stored in argv[0], not argv[1]

So, correct behaviour or failure will depend on the parser/lexer/tokenizer used by the called program.

  • Some of them will see the added space as an aditional argument that will be stored inside argv[0] (the standard tokenizer in mingw/gcc and VC behave this way).

  • Others will remove the spaces and take the first non blank data as argv[0] (the case of find)

  • Any other behaviour you can think can be adopted by the tokenizer.

And once the tokenizer has ended its work, the program will handle the arguments and select one of

  • Ignore the first argument as it is assummed the program name is in this position

  • Make no assumptions on what will be found in the command line and identify the argument.

So, the space solution is not a bulletproof solution (thank you dbenham)

How to solve it?

As the problem is the absence of the program name in the command line argument, and the bad location of the following arguments, the best option seems to include it (well we can include anything to be used as argv[0], but as most programs expect the program name ...)

forfiles /c "ping ping -a"
forfiles /c "find find /c /v 0x220x22 @path"


回答2:

You can avoid CMD /C if and only if your are running a single external command. If you want to use an internal commmand, or if you want to use redirection, pipe, command concatenation, etc, then you must use CMD /C.

There is something wrong about the way FORFILES passes the arguments to the command. As MC ND has found, adding an extra space works with PING. But that does not seem to work with all external commands.

It works with PING, FINDSTR and HELP:

D:\>forfiles /m test.txt /c "ping  /a"

IP address must be specified.

D:\>forfiles /m test.txt /c "help  vol"

Displays the disk volume label and serial number, if they exist.

VOL [drive:]

D:\>forfiles /m test.txt /c "findstr  . @path"

line 1

But it fails for FIND - the /C (count) option is still missed:

D:\>forfiles /m test.txt /c "find  /c /v 0x220x22 @path"


---------- D:\TEST.TXT
line 1

A safer bet seems to be to add an extra single character argument with only one intervening space. But I have no idea if this works for all external commands.

D:\>forfiles /m test.txt /c "find x /c /v 0x220x22 @path"


---------- D:\TEST.TXT: 1

D:\>forfiles /m test.txt /c "ping x /a"

IP address must be specified.

D:\>forfiles /m test.txt /c "help x vol"

Displays the disk volume label and serial number, if they exist.

VOL [drive:]

D:\>forfiles /m test.txt /c "findstr x . @path"

line 1


标签: cmd forfiles