Reputation: 35637
I have a .Net application that take a bunch of command line arguments, process some of it, and use the rest as arguments for another application
E.g.
MyApp.exe foo1 App2.exe arg1 arg2 ...
MyApp.exe
is my application,
foo1
is a parameter that my application care. App2.exe
is another application, and my application will run App2 with arg1 arg2, etc. as arguments.
Currently my application just run App2.exe using something like this
Process.Start(args[1], String.Join(" ", args.Skip(2))
. So the command above will correctly run: App2.exe with arguments "arg1 arg2". However, consider something like this
MyApp.exe foo1 notepad.exe "C:\Program Files\readme.txt"
The code above will not be aware of the quotes, and will run notepad.exe with arguments C:\Program Files\readme.txt (without quotes). How can I fix this problem?
Upvotes: 4
Views: 2741
Reputation: 1193
Environment.CommandLine
will give you the exact command line - you'll have to parse out the path to your app but otherwise works like a charm - @idle_mind alluded to this earlier (kind of)
Edited to move example into answer (because people are obviously still looking for this answer). Note that when debuging vshost messes up the command line a little.
#if DEBUG
int bodgeLen = "\"vshost.\"".Length;
#else
int bodgeLen = "\"\"".Length;
#endif
string a = Environment.CommandLine.Substring(Assembly.GetExecutingAssembly().Location.Length+bodgeLen).Trim();
Upvotes: 8
Reputation: 20564
Credit to @mp3ferret for having the right idea. But there was no example of a solution using Environment.CommandLine
, so I went ahead and created a OriginalCommandLine
class that will get the Command Line arguments as originally entered.
An argument is defined in the tokenizer
regex as being a double quoted string of any type of character, or an unquoted string of non-whitespace characters. Within the quoted strings, the quote character can be escaped by a backslash. However a trailing backslash followed by a double quote and then white space will not be escaped.
There reason I chose the exception of the escape due to whitespace was to accommodate quoted paths that end with a backslash. I believe it is far less likely that you'll encounter a situation where you'd actually want the escaped double quote.
static public class OriginalCommandLine
{
static Regex tokenizer = new Regex(@"""(?:\\""(?!\s)|[^""])*""|[^\s]+");
static Regex unescaper = new Regex(@"\\("")(?!\s|$)");
static Regex unquoter = new Regex(@"^\s*""|""\s*$");
static Regex quoteTester = new Regex(@"^\s*""(?:\\""|[^""])*""\s*$");
static public string[] Parse(string commandLine = null)
{
return tokenizer.Matches(commandLine ?? Environment.CommandLine).Cast<Match>()
.Skip(1).Select(m => unescaper.Replace(m.Value, @"""")).ToArray();
}
static public string UnQuote(string text)
{
return (IsQuoted(text)) ? unquoter.Replace(text, "") : text;
}
static public bool IsQuoted(string text)
{
return text != null && quoteTester.Match(text).Success;
}
}
As you can see from the results below the above method fixes maintains the quotes, while more gracefully handling a realistic scenario you might encounter.
Test:
ConsoleApp1.exe foo1 notepad.exe "C:\Progra\"m Files\MyDocuments\" "C:\Program Files\bar.txt"
args[]:
[0]: foo1
[1]: notepad.exe
[2]: C:\Progra"m Files\MyDocuments" C:\Program
[3]: Files\bar.txt
CommandLine.Parse():
[0]: foo1
[1]: notepad.exe
[2]: "C:\Progra"m Files\MyDocuments\"
[3]: "C:\Program Files\bar.txt"
I debated using an alternative scheme for escaping double quotes. I feel that using ""
is better given that command lines so often deal with backslashes. I kept the backslash escaping method because it is backwards compatible with how command line arguments are normally processed.
If you want to use that scheme make the following changes to the regexes:
static Regex tokenizer = new Regex(@"""(?:""""|[^""])*""|[^\s]+");
static Regex unescaper = new Regex(@"""""");
static Regex unquoter = new Regex(@"^\s*""|""\s*$");
static Regex quoteTester = new Regex(@"^\s*""(?:""""|[^""])*""\s*$");
If you want to get closer to what you expect from args
but with the quotes intact, change the two regexes. There is still a minor difference, "abc"d
will return abcd
from args
but [0] = "abc", [1] = d
from my solution.
static Regex tokenizer = new Regex(@"""(?:\\""|[^""])*""|[^\s]+");
static Regex unescaper = new Regex(@"\\("")");
If you really, really want to get the same number of elements as args
, use the following:
static Regex tokenizer = new Regex(@"(?:[^\s""]*""(?:\\""|[^""])*"")+|[^\s]+");
static Regex unescaper = new Regex(@"\\("")");
Result of exact match
Test: "zzz"zz"Zzz" asdasd zz"zzz" "zzz"
args OriginalCommandLine
------------- -------------------
[0]: zzzzzZzz [0]: "zzz"zz"Zzz"
[1]: asdasd [1]: asdasd
[2]: zzzzz [2]: zz"zzz"
[3]: zzz [3]: "zzz"
Upvotes: 1
Reputation: 2107
You will need to modify MyApp to enclose any arguments with quotes.
Short story, the new code should be this:
var argsToPass = args.Skip(2).Select(o => "\"" + o.Replace("\"", "\\\"") + "\"");
Process.Start(args[1], String.Join(" ", argsToPass);
The logic is this:
each argument should be enclosed with quotes, so if you are calling with :
MyApp.exe foo1 notepad.exe "C:\Program Files\readme.txt"
The app will get called this way:
notepad.exe "C:\Program Files\readme.txt"
each argument should escape the quotes (if any), so if you are calling with:
MyApp.exe foo1 notepad.exe "C:\Program Files\Some Path with \"Quote\" here\readme.txt"
The app will get called this way:
notepad.exe "C:\Program Files\Some Path with \"Quote\" here\readme.txt"
Upvotes: 4
Reputation: 496
One solution might be to try using Command Line Parser, a free 3rd-party tool, to set up your application to take specific flags.
For example, you could define the accepted options as follows:
internal sealed class Options
{
[Option('a', "mainArguments", Required=true, HelpText="The arguments for the main application")]
public String MainArguments { get; set; }
[Option('t', "targetApplication", Required = true, HelpText = "The second application to run.")]
public String TargetApplication { get; set; }
[Option('p', "targetParameters", Required = true, HelpText = "The arguments to pass to the target application.")]
public String targetParameters { get; set; }
[ParserState]
public IParserState LastParserState { get; set; }
[HelpOption]
public string GetUsage()
{
return HelpText.AutoBuild(this, current => HelpText.DefaultParsingErrorsHandler(this, current));
}
}
Which can then be used in your Program.cs as follows:
static void Main(string[] args)
{
Options options = new Options();
var parser = new CommandLine.Parser();
if (parser.ParseArgumentsStrict(args, options, () => Environment.Exit(-2)))
{
Run(options);
}
}
private static void Run(Options options)
{
String mainArguments = options.MainArguments;
// Do whatever you want with your main arguments.
String quotedTargetParameters = String.Format("\"{0}\"", options.TargetParameters);
Process targetProcess = Process.Start(options.TargetApplication, quotedTargetParameters);
}
You would then call it on the command line like this:
myApp -a mainArgs -t targetApp -p "target app parameters"
This takes all the guesswork out of trying to figure out what's an argument for which app while also allowing your user to specify them in whatever order they want. And if you decide to add in another argument down the road, you can easily do so without breaking everything.
EDIT: Updated Run method to include ability to add quotes around the target parameters.
Upvotes: 0
Reputation: 901
Try with "\"". I have to pass as arguments url too, this is the way:
_filenameDestin and _zip are urls. I hope it helps.
string ph = "\"";
var psi = new ProcessStartInfo();
psi.Arguments = "a -r " + ph + _filenameDestin + ".zip " + ph + _filenameDestin + ph;
psi.FileName = _zip;
var p = new Process();
p.StartInfo = psi;
p.Start();
p.WaitForExit();
Upvotes: 0
Reputation: 664
Try the following.
This code preserved the double quotes characters as well as giving the option to escape the \ and " characters (see comments in the code below).
static void Main(string[] args)
{
// This project should be compiled with "unsafe" flag!
Console.WriteLine(GetRawCommandLine());
var prms = GetRawArguments();
foreach (var prm in prms)
{
Console.WriteLine(prm);
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern System.IntPtr GetCommandLine();
public static string GetRawCommandLine()
{
// Win32 API
string s = Marshal.PtrToStringAuto(GetCommandLine());
// or better, managed code as suggested by @mp3ferret
// string s = Environment.CommandLine;
return s.Substring(s.IndexOf('"', 1) + 1).Trim();
}
public static string[] GetRawArguments()
{
string cmdline = GetRawCommandLine();
// Now let's split the arguments.
// Lets assume the fllowing possible escape sequence:
// \" = "
// \\ = \
// \ with any other character will be treated as \
//
// You may choose other rules and implement them!
var args = new ArrayList();
bool inQuote = false;
int pos = 0;
StringBuilder currArg = new StringBuilder();
while (pos < cmdline.Length)
{
char currChar = cmdline[pos];
if (currChar == '"')
{
currArg.Append(currChar);
inQuote = !inQuote;
}
else if (currChar == '\\')
{
char nextChar = pos < cmdline.Length - 1 ? cmdline[pos + 1] : '\0';
if (nextChar == '\\' || nextChar == '"')
{
currArg.Append(nextChar);
pos += 2;
continue;
}
else
{
currArg.Append(currChar);
}
}
else if (inQuote || !char.IsWhiteSpace(currChar))
{
currArg.Append(currChar);
}
if (!inQuote && char.IsWhiteSpace(currChar) && currArg.Length > 0)
{
args.Add(currArg.ToString());
currArg.Clear();
}
pos++;
}
if (currArg.Length > 0)
{
args.Add(currArg.ToString());
currArg.Clear();
}
return (string[])args.ToArray(typeof(string));
}
Upvotes: 0
Reputation: 39142
Use Environment.GetCommandLine() as it will keep the parameter in quotes together as one argument.
Upvotes: 2
Reputation: 63075
You can use backslashes for escape quotes. below will work
MyApp.exe foo1 notepad.exe \"C:\Program Files\readme.txt\"
Above will be the best solution if you are don't have idea about which other exes going to run and what are the arguments they expecting. In that case you can't add quotes from your program.
give instructions to add backslashes when there is quotes when running your application
Upvotes: 1
Reputation: 3020
Well, the simple answer is to just wrap every argument in quotes when calling MyApp2.exe. It doesn't hurt to wrap arguments that are one word, and it will fix the case that it has spaces in the argument.
The only thing that might go wrong is if the argument has an escaped quote in it already.
Upvotes: 1