?
Solved

Escaping and quoting in the commandline

Posted on 2009-02-24
19
Medium Priority
?
449 Views
Last Modified: 2013-11-17
I'm trying to execute a program through ShellExecuteEx, or rather a shell command using cmd /c and am redirecting STDOUT and STDERR to 2 different files which works most of the time.

cmd /c c:\directory\program.exe parameter1 paramter2 1>c:\stdout 2>stderr

This works quite well if there are no spaces in the directories and/or file names. I tried adding double quotes like so, but that didnt work.

cmd /c "c:\directory\program.exe" parameter1 paramter2 1>"c:\stdout" 2>"stderr"

The path to the stdout and stderr files could be anywhere, it is in the subdirectory \temp\ of wherever the exefile happens to be located. What is wrong in the above command?

Alternatively, is anybody aware of a VCL component that runs commands and captures  stdout and stderr? preferably in a seperate thread?


0
Comment
Question by:Xyptilon2
  • 7
  • 6
  • 4
  • +1
19 Comments
 
LVL 86

Assisted Solution

by:jkr
jkr earned 600 total points
ID: 23722506
I am not aware of a VCL component, but take a look at http://support.microsoft.com/default.aspx?scid=kb;en-us;190351 ("How to spawn console processes with redirected standard handles") which presents a fully fledged solution (in plain C) that shows how to perform such a redirection and handle the input and output of applications under such circumstances.
0
 
LVL 19

Accepted Solution

by:
LordOfPorts earned 800 total points
ID: 23722865
Try surrounding the entire command to be executed by cmd with double quotes and then surround the paths within the command that have a space with a quote too:
cmd /c ""c:\directory\program.exe" parameter1 paramter2 1> "c:\stdout" 2> "stderr""

Open in new window

0
 
LVL 8

Assisted Solution

by:Knut Hunstad
Knut Hunstad earned 600 total points
ID: 23722914
You don't really show how you are using ShellExecuteEx, or are you trying to say you don't? When trying this in a command window, it seems to work fine. If you want help on the solution you have, maybe you could include the code?
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 13

Author Comment

by:Xyptilon2
ID: 23722954
No problem, I'm using the following code:

Is there perhaps a limit to the maximum size of the command i can pass to ShellExecuteEx, or do i need to place \ before \ ? as in escaping it? if so, is there a standard function for it in BCB ?



	sCMD += " 1>" + sTempFileName + "  2>&1";
 
	SHELLEXECUTEINFO ShellInfo; 			    	 // Name structure
	memset(&ShellInfo, 0, sizeof(ShellInfo)); 		 // Set up memory block
	ShellInfo.cbSize = sizeof(ShellInfo); 			 // Set up structure size
	ShellInfo.hwnd = Handle; 						 // Calling window handle
	ShellInfo.lpVerb = "open"; 						 // Open the file with default program
	ShellInfo.lpFile = "cmd"; 						 // File to open
	ShellInfo.lpParameters = ("/c " + sCMD).c_str();
	ShellInfo.nShow = SW_HIDE; 						 // Open in normal window
	ShellInfo.fMask = SEE_MASK_NOCLOSEPROCESS; 		 // Necessary if you want to wait for spawned process
	bool res = ShellExecuteEx(&ShellInfo); 			 // Call to function
	if (res) {
		WaitForSingleObject(ShellInfo.hProcess, INFINITE);
		bool bResult = TerminateProcess( ShellInfo.hProcess, 1);
		if (bResult == false ) {
			fLog("Could not terminate a process we started waiting 1 second: " + (AnsiString) GetLastError());
			SleepEx(1000, false);
			bool bResult2 = TerminateProcess( ShellInfo.hProcess, 1);
				if (bResult2 == false ) {
					fLog("Could not terminate a process AGAIN: " + (AnsiString) GetLastError());
 
					fLog("Closing child through handles");
					CloseHandle(ShellInfo.hProcess);
				}
 
		}
	}

Open in new window

0
 
LVL 86

Expert Comment

by:jkr
ID: 23723033
Hm, the above does not seem to escape anything or add any quotes. Shouldn't that be like the following?
sCMD += " 1>\"" + sTempFileName + "\"  2>&1";

Open in new window

0
 
LVL 13

Author Comment

by:Xyptilon2
ID: 23723145
Yes i tried that, it didnt work, at least not for very long file names, but how can i escape any string programmatically?
0
 
LVL 86

Expert Comment

by:jkr
ID: 23723257
You don't need to escape anything, it's about adding the quotes. Since you are probably using the 'AnsiString' class, you could do that like
AnsiString QuotePath(const AnsiString& sPath) {
 
  AnsiString sResult;
 
  sResult = "\"" + sPath + "\"";
 
  return sResult;
}

Open in new window

0
 
LVL 86

Expert Comment

by:jkr
ID: 23723269
Oh, there is even an API for that: 'PathQuoteSpaces'(http://msdn.microsoft.com/en-us/library/bb773739(VS.85).aspx)
#include <windows.h>
#include <iostream.h>
#include "Shlwapi.h"
 
void main( void )
{
 
    // Path with spaces.
	char buffer_1[] = "C:\\sample_one\\sample two"; 
	char *lpStr1;
	lpStr1 = buffer_1;
 
	
 
	// Path before quote spaces.
	cout << "The path before PathQuoteSpaces: " << lpStr1 << endl;
 
	// Call "PathQuoteSpaces".		
    PathQuoteSpaces(lpStr1);
 
	// Path after quote spaces.
	cout << "The path after PathQuoteSpaces: " << lpStr1 << endl;
 
}
OUTPUT:
==================
The path before PathQuoteSpaces: C:\sample_one\sample two
The path after PathQuoteSpaces: "C:\sample_one\sample two"

Open in new window

0
 
LVL 13

Author Comment

by:Xyptilon2
ID: 23723900
I tried, it doesn't work. Perhaps the problem is the length of the command. I'm currently testing from the default location and the full command that goes into lpParameters is:

/c "C:\Documents and Settings\steven\Mijn documenten\RAD Studio\Projects\socketserver\Debug\project1.exe" 211588 1>"C:\Documents and Settings\steven\Mijn documenten\RAD Studio\Projects\socketserver\Debug\temp\BEJVBDLE.OUT" 2>"C:\Documents and Settings\steven\Mijn documenten\RAD Studio\Projects\socketserver\Debug\temp\BEJVBDLE.ERR"

Which is quite long, i cannot even paste into Start->Run
0
 
LVL 86

Expert Comment

by:jkr
ID: 23724006
OK, then we probably found the reason why that isn't working (as a last test, you could try to run that in a .cmd file or from the command line). But, what about shortening that a bit? e.g.
/c "%USERPROFILE%\Mijn documenten\RAD Studio\Projects\socketserver\Debug\project1.exe" 211588 1>"%USERPROFILE%\Mijn documenten\RAD Studio\Projects\socketserver\Debug\temp\BEJVBDLE.OUT" 2>"%USERPROFILE%\Mijn documenten\RAD Studio\Projects\socketserver\Debug\temp\BEJVBDLE.ERR"

Open in new window

0
 
LVL 86

Expert Comment

by:jkr
ID: 23724018
BTW, if that does not help, why not relocating the output files to e.g. 'c:\tmp'?
0
 
LVL 13

Author Comment

by:Xyptilon2
ID: 23724161
Yes, that would work, however that doesnt make me understand the problem. At the moment im guessing that the limit seems to be MAX_PATH...
0
 
LVL 86

Expert Comment

by:jkr
ID: 23724226
That 'MAX_PATH' thing is actually different from each Windows flavour to the other - but anyway, that command line is very long, so chances are that it is truncated. What you also could do is write a temp. batch/.cmd file and execute that, e.g.
set OUTFILE="C:\Documents and Settings\steven\Mijn documenten\RAD Studio\Projects\socketserver\Debug\temp\BEJVBDLE.OUT"
set ERRFILE="C:\Documents and Settings\steven\Mijn documenten\RAD Studio\Projects\socketserver\Debug\temp\BEJVBDLE.ERR"
cmd.exe /c "%USERPROFILE%\Mijn documenten\RAD Studio\Projects\socketserver\Debug\project1.exe" 211588 1>%OUTFILE% 2>%ERRFILE%

Open in new window

0
 
LVL 8

Expert Comment

by:Knut Hunstad
ID: 23724795
From the documentation of SHELLEXECUTEINFO:

lpParameters
Long pointer to a null-terminated string that contains the application parameters. The parameters must be separated by spaces. To include double quotation marks, you must enclose the marks in double quotation marks, as in the following example.
sei.lpParameters = "An example: \"\"\"quoted text\"\"\"";
In this case, the application receives three parameters: An, example:, and "quoted text".

I am a little in doubt here whether you really want to pass the file parameter in quotes, but I would try with both double quotes and the triple quotes shown here, since this is a fast test.

If this doesn't help, could you tell us if you have any solution yet that works with short file names with spaces? I have the feeling it's not the text length that is the problem, until you confirm that you have it working at all for something like:

/c "C:\My Documents\project1.exe" 211588 1>"C:\My Documents\BEJVBDLE.OUT" 2>"C:\ My Documents\BEJVBDLE.ERR"
0
 
LVL 19

Expert Comment

by:LordOfPorts
ID: 23724883
The max length of the command line parameter ranges between 2047 and 8191 characters depending on the OS according to http://support.microsoft.com/kb/830473

Can you try surrounding the entire command with a pair of double-quotes as I suggested above just to check if it makes a difference, e.g. http://www.ss64.com/nt/syntax-esc.html
0
 
LVL 8

Expert Comment

by:Knut Hunstad
ID: 23725811
After testing a little more, I think the quotes LordOfPorts suggest could be the solution, together with the quotes. I tested like this:

I made a batch file in "c:\Hello you\test.bat", containing one line:
@FOR %%P IN (%*) DO @echo _%%P_

In a command window, whether I do:
cmd /c ""c:\hello you\test.bat" param1 12345 1>"c:\hello you\stdout" 2>"c:\hello you\stderr""
cmd /c "c:\hello you\test.bat" param1 12345 1>"c:\hello you\stdout" 2>"c:\hello you\stderr"

I get the same (and wanted) output:
_param1_
_12345_

But ShellExecuteEx might interfer with the quotes here. Check if this works:

ShellInfo.lpParameters = ("/c \"\"\"" + sCMD + "\"\"\"").c_str();

which should send your whole sCMD as 1 parameter string to cmd, according to documentation. Sorry I can't test this...
0
 
LVL 8

Expert Comment

by:Knut Hunstad
ID: 23725845
Sorry, the first line in my last comment should be:

After testing a little more, I think the quotes LordOfPorts suggest could be the solution, together with the triple quotes from the lpParameters documentation.

And I forgot to point out that the "output" from my examples of course was in the file "c:\hello you\stdout".
0
 
LVL 13

Author Comment

by:Xyptilon2
ID: 23763232
My apologies for the late reply... I have done as suggested and after a lot of testing...the solution is:

cmd.exe /c "command"

where command (whatever is it) may contain quotes also, mostly to encapsulate spaces in paths or filenames. But the important thing was to encapsulate the entire thing in quotes. After all, it is a paramater to cmd.exe.

I am, however not sure, why the length was an issue before.
0
 
LVL 13

Author Closing Comment

by:Xyptilon2
ID: 31550601
Thank you all for your input :)
0

Featured Post

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

When writing generic code, using template meta-programming techniques, it is sometimes useful to know if a type is convertible to another type. A good example of when this might be is if you are writing diagnostic instrumentation for code to generat…
Introduction This article is a continuation of the C/C++ Visual Studio Express debugger series. Part 1 provided a quick start guide in using the debugger. Part 2 focused on additional topics in breakpoints. As your assignments become a little more …
THe viewer will learn how to use NetBeans IDE 8.0 for Windows to perform CRUD operations on a MySql database.
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.
Suggested Courses

749 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question