2

I have the following code that I use to run a batch script from within a program:

#include <string>
#include <tchar.h>
#include <iostream>

int main()
{
    std::wstring command = _T("\"C:\\cygwin64\\home\\ravik\\path with space\\sample.bat\"");
    std::wstring arg1 = _T("C:\\cygwin64\\home\\ravik\\another_space\\test");
    std::wstring arg2 = _T("\"C:\\cygwin64\\home\\ravik\\another space\\test\"");


    std::wstring fullCommand1 = command + _T(" ") + arg1;

    _wsystem(fullCommand1.c_str());

    std::wstring fullCommand2 = command + _T(" ") + arg2;

    _wsystem(fullCommand2.c_str());

    std::cin.get();

    return 0;
}

In both fullCommand1 and fullCommand2, I use the same command (namely sample.bat), but pass a different command-line argument to said command (arg1 vs arg2). However, while the _wsystem command works as expected when using fullCommand1, when I use fullCommand2, I get the error

'C:\cygwin64\home\ravik\path' is not recognized as an internal or external command, operable program or batch file.

It appears that somehow, the " surrounding my path with space has disappeared for the second call to _wsystem. What is the cause for this?

R_Kapp
  • 2,818
  • 1
  • 18
  • 32
  • How can you be sure that `fullCommand1` worked properly? – Mark Ransom Jun 19 '17 at 20:49
  • @MarkRansom: The simple batch script "sample.bat" simply reads the file at the location given it and prints it back out. `_wsystem(fullCommand1.c_str());` read the appropriate file and printed it out exactly correctly, so, as far as I can tell, it worked properly. By simply copying the same file to another directory and using `_wsystem(fullCommand2.c_str());`, I get the error as described in the question. – R_Kapp Jun 19 '17 at 20:52
  • (Note, though, that the question isn't about this particular batch script - "sample.bat" is just something simple I wrote to reproduce the issue and it's exact contents aren't of too much concern) – R_Kapp Jun 19 '17 at 20:53
  • I only ask because the error message implies it's the path to the batch file that failed, not the other path. I can't think of any reason why the path to the batch file wouldn't behave consistently between the two commands. – Mark Ransom Jun 19 '17 at 21:01
  • @MarkRansom: Yep - that's why I asked the question; that makes absolutely no sense to me either... The only thing I've been able to work out is if `arg2` contains a `\"`, I get the error, but if it doesn't, everything works. The best guess I have at the moment is some obscure Microsoft bug... – R_Kapp Jun 19 '17 at 21:04
  • 1
    I suspect this is something to do with system() not using the /S switch when it constructs the call to cmd.exe. Try prefixing the command with `cmd /s /c "` (you'll also need to add an extra quote at the end). – Harry Johnston Jun 20 '17 at 03:05
  • @HarryJohnston: That worked - if you want to make that an answer, I'll be happy to accept. – R_Kapp Jun 20 '17 at 13:52

1 Answers1

3

Why does this happen?

There's a design flaw in the Microsoft C runtime's implementation of system() and _wsystem() which is tripping you up in this case.

The command you're trying to run looks like this:

"c:\path with spaces\my.bat" "argument with spaces"

The C runtime, taking the simplest possible approach, turns this into

cmd /c "c:\path with spaces\my.bat" "argument with spaces"

Unfortunately, cmd.exe has rather arcane rules when the first character in the requested command is a quote mark, and in this case winds up interpreting the command as

c:\path with spaces\my.bat" "argument with spaces

which doesn't work for obvious reasons.

(I say this is a design flaw because if the runtime used the /S switch and added quotes around the command, it would work perfectly in every case. This probably can't be fixed for backwards compatibility reasons. Or perhaps it just isn't a priority for the runtime's development team.)

Whatever the reason, the outcome is that you have to deal with the problem yourself.

How can I fix it in this particular case?

In this particular case, the simplest solution is to use a command like this:

""c:\path with spaces\my.bat" "argument with spaces""

The runtime will turn this into

cmd /c ""c:\path with spaces\my.bat" "argument with spaces""

which will be interpreted as desired, since there are more than two quote marks. You just need to be a little careful when using this approach as it may cause confusion that the extra quotes are only needed sometimes.

(It is almost always safe to put the extra quotes in even when they aren't needed, but there is a potential ambiguity - if the path to the executable combined with the command-line arguments happens to form a valid path to a different executable. See the addendum to the answer linked above for an example, albeit a rather contrived one!)

How can I fix it in the general case?

The most general solution is to specify the command string as

cmd /s /c ""c:\path with spaces\my.bat" "argument with spaces""

the runtime will turn it into

cmd /c cmd /s /c ""c:\path with spaces\my.bat" "argument with spaces""

which will run

cmd /s /c ""c:\path with spaces\my.bat" "argument with spaces""

which will be interpreted as

"c:\path with spaces\my.bat" "argument with spaces"

as desired; because of the /S flag, this variant will always work as expected even if the command you're running doesn't have any quote marks.

This approach does mean there are two instances of cmd.exe being launched, one inside the other; if this slight overhead is unacceptable, you always have the option of using CreateProcess directly rather than calling system().

(Another situation where it may be best to call CreateProcess yourself is if you need to use carets or other special characters on the command line.)

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158