Replace content in first set of quotes found in string c# - c#

I am working on a project that involves having to manipulate a bat file based on certain user produced parameters. The bat files themselves are created manually, with a static format. A basic example of a bat file would be:
cd \some\predefined\bat
start run_some_script "user_generated_argument" [other pre-defined arguments]
The "user_generated_argument" bit of the bat file is manipulated in C# by the following code:
string bat_text = File.ReadAllText(bat_path);
Regex regex = new Regex("(.*?)\".*\"(.*)");
string new_argument = "A new argument";
string new_bat = regex.Replace(bat_text , "$1\"" + new_argument + "\"$2", 1);
And that would produce the following:
cd \some\predefined\bat
start run_some_script "A new argument" [other pre-defined arguments]
which is the expected output.
However, the problem lies when one of the other pre-defined arguments after the first quoted argument is also in quotes when that is the case, it seems that the second quoted argument disappears. For example, if the bat file looks like:
cd \some\predefined\bat
start run_some_script "user_generated_argument" "a_predefined_quoted_argument" [other pre-defined arguments]
Running the same C# code from above would produce the following:
cd \some\predefined\bat
start run_some_script "A new argument" [other pre-defined arguments]
The "a_predefined_quoted_argument" would no longer be in the string.
I may be doing this completely wrong. How would I make the predefined quoted argument not disappear?

the problem is that your expression
\".*\"
is eager or greedy, taking everything between the first quote and the last quote it finds. To make it lazy or reluctant, put a ? after the *
like so (I used VB, which escapes double quotes by double double quotes)
Dim batfile As String = "cd \some\predefined\bat" & vbCrLf & "start run_some_script ""user_generated_argument"" ""a_predefined_quoted_argument"" [other pre-defined arguments]"
Dim regex As Regex = New Regex("(.*?)"".*?""(.*)")
Dim new_argument As String = "A new argument"
Dim new_bat As String = regex.Replace(batfile, "$1""" + new_argument + """ $2", 1)
It will now take everything between the first quote, and the next quote.

Instead of using Regex you could also read the lines with File.ReadAllLines(), take the desired line and split it with string.Split() and replace them in that way.
Something like:
string[] lines = File.ReadAllLines(fileName);
string commandLine = lines.Where(d => d.StartsWith("start")).Single();
string[] arguments = commandLine.Split(' ');
foreach (var argument in arguments)
{
if (argument.StartsWith("\""))
{
// do your stuff and reassemble
}
}

Related

Replace text in file regardless of the end of the line

I've been working on a tool to modify a text file to change graphics settings for a game. A few examples of the settings are as follows:
sg.ShadowQuality=0
ResolutionSizeX=1440
ResolutionSizeY=1080
bUseVSync=False
I want to be able to find sg.ShadowQuality=(rest of line, regardless of what is after this text), and replace it. This is so that a user can set this to say, 10 then 1 without having to check for 10 and 1 etc.
Basically, I'm try to find out what I need to use to find/replace a string in a text file without knowing the end of the string.
My current code looks like:
FileInfo GameUserSettings = new FileInfo(#SD + GUSDirectory);
GameUserSettings.IsReadOnly = false;
string text = File.ReadAllText(SD + GUSDirectory);
text = text.Replace("sg.ShadowQuality=0", "sg.ShadowQuality=" + Shadows.Value.ToString());
File.WriteAllText(SD + GUSDirectory, text);
text = text.Replace("sg.ShadowQuality=1", "sg.ShadowQuality=" + Shadows.Value.ToString());
File.WriteAllText(SD + GUSDirectory, text);
SD + GUSDirectory is the location of the text file.
The file must have readonly Off to be edited, otherwise the game can revert the settings back, hence the need for this.(It is turned back to readonly On after any change, its just not included in this code provided)
You can do it like you do, if you use a regular expression to match all the line
FileInfo gameUserSettings = new FileInfo(Path.Combine(#SD, GUSDirectory)); //name local varaible in camelCase, use Path.Combine to combine paths
gameUserSettings.IsReadOnly = false;
string text = File.ReadAllText(gameUserSettings.FullName); //use the fileinfo you just made rather than make the path again
text = Regex.Replace(text, "^sg[.]ShadowQuality=.*$", $"sg.ShadowQuality={Shadows.Value}", RegexOptions.Multiline); //note switch to interpolated strings
File.WriteAllText(gameUserSettings.FullName, text);
That regex is a Multiline one (so ^ and $ have altered meanings):
^sg[.]ShadowQuality=.*$
start of line ^ (not start of input)
followed by sg
followed by period . (in a character class it loses its "any character" meaning)
followed by ShadowQuality=
followed by any number of any character(.*)
followed by end of line $ (not end of input)
The vital bit is "any number of any character" that can cope with the vlaue in the file being 1, 2, 7, hello and so on..
The replacement is:
$"sg.ShadowQuality={Shadows.Value}"
This is an interpolated string; a neater way of representing strings that mix constant content (hardcoded chars) and variable content. When a $tring contains a { that "breaks out" of the string and back into normal c# code so you can write code that resolves to values that will be included in the string -> if Shadows.Value is for example a decimal? of 1.23 it will become 1.23
You can format data too; calling for $"to one dp is {Shadows.Value:F1}" would produce "to one dp is 1.2" - the 1.23 is formatted to 1 decimal place by the F1, just like calling Shadows.Value.ToString("F1") would

C# Parse command line string with spaces. System.IO.Path and FileInfo fails

I'm looking for a reliable way to parse a path string that has spaces
e.g
"C:/Test has spaces/More Spaces.exe argument"
System.IO.Path.GetDirectoryName works (returns "C:/Test has spaces"), but System.IO.Path.GetFileName returns "More Spaces.exe argument". I want get the .exe and split the arguments .
The string is not double quoted so let's say I cannot modify the input string.
You should take the character index after the extension and remove the rest of the string.
like this:
string myString = "C:/Test has spaces/More Spaces.exe argument";
int index_Of_ext = myString.LastIndexOf("exe")
//including "exe"
+ 3;
string output = myString.Remove(index_Of_ext,
//starting remove
myString.Length - index_Of_ext);
//the rest of the string
in this way in the output string you miss the part after "exe" (including extension)

How to remove the exe part of the command line

This is my code. The input command line is var1 val1 var2 val2:
var rawCmd = Environment.CommandLine;
// Environment.CommandLine adds the .exe info that I don't want in my command line:
// rawCmd = "path\to\ProjectName.vshost.exe" var1 val1 var2 val2
// A. This correction makes it work, although it is pretty ugly:
var cleanCmd = rawCmd.Split(new string[] { ".exe\" " }, StringSplitOptions.None)[1];
// B. This alternative should be cleaner, but I can't make it work:
var exePath = System.Reflection.Assembly.GetCallingAssembly().Location;
cleanCmd = rawCmd.Replace(string.Format($"\"{exePath}\" "), "");
So to make B work, I should be able to find the .vhost.exe info (which I am not able to find).
But also I would like to know if there is a cleaner way to do all this.
As for the reason why I want to achieve this, here is the explanation (tl;dr: parsing a json from the command line): https://stackoverflow.com/a/36203572/831138
Instead of using
var rawCmd = Environment.CommandLine;
You can use:
var rawCmd = Environment.CommandLine;
var argsOnly = rawCmd.Replace("\"" + Environment.GetCommandLineArgs()[0] + "\"", "");
This will return "var1 val1 var2 val2" in your example. And it should work with the JSON example in the other post.
This only strips the command invocation part, no matter you write it as program, program.exe, .\program, c:program, "c:\Program Files\program", "\Program Files\program.exe", etc. or your path separator.
var exe = Environment.GetCommandLineArgs()[0]; // Command invocation part
var rawCmd = Environment.CommandLine; // Complete command
var argsOnly = rawCmd.Remove(rawCmd.IndexOf(exe),exe.Length).TrimStart('"').Substring(1);
It will leave double quotes, carets, and spaces between arguments untouched, even spaces at the beginning i.e., just after program name. Note there's an extra space at the beginning, don't ask me why. If that bothers you, remove first character, it should be easy. Hence the final .Substring(1). I define the two variables to make it more readable.
Edit:
Takes account of quoted invocation and the case where the program name string happens to appear as part of an argument (e.g., if your program is me.exe and you run me "Call me Ishmael", .Replace would trim the second me too). Now I also take out that extra space.
This is a very old question, but as of Windows 10 20H2 and .NET Framework 4.8, all of the above solutions appear to be broken in one way or another (eg. double-quote delimited exe paths).
I needed to remove the exe from Environment.CommandLine in a more generally robust way, so I decided to try a regex based approach. (Then I had 2 problems, lol.) Hope this helps somebody!
internal static string GetRawCommandLineArgs( )
{
// Separate the args from the exe path.. incl handling of dquote-delimited full/relative paths.
Regex fullCommandLinePattern = new Regex(#"
^ #anchor match to start of string
(?<exe> #capture the executable name; can be dquote-delimited or not
(\x22[^\x22]+\x22) #case: dquote-delimited
| #or
([^\s]+) #case: no dquotes
)
\s* #chomp zero or more whitespace chars, after <exe>
(?<args>.*) #capture the remainder of the command line
$ #match all the way to end of string
",
RegexOptions.IgnorePatternWhitespace|
RegexOptions.ExplicitCapture|
RegexOptions.CultureInvariant
);
Match m = fullCommandLinePattern.Match(Environment.CommandLine);
if (!m.Success) throw new ApplicationException("Failed to extract command line.");
// Note: will return empty-string if no args after exe name.
string commandLineArgs = m.Groups["args"].Value;
return commandLineArgs;
}
Testing done:
exe paths with/without doublequote
args containing doublequotes and also referencing the exe name
invoking exe with no args returns empty string
[Edit] Testing NOT done:
.NET 5 or any other runtime or OS
I have found a way to get the vhost path:
var exePath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, System.AppDomain.CurrentDomain.FriendlyName);
...although I am afraid it could lead to inconsistencies depending on where the code is executed (Debug / production ...). I hope not, I'll have to keep testing.
If the number of spaces between elements is not important, you can just Join the argument array, which is split on unquoted space characters, and doesn't contain the executable:
void Main(string[] args)
{
var cleanCmd = string.Join(" ", args);
// etc
}
Actually, the command invocation part is always enclosed in double quotes, even if the path doesn't contain any spaces. And it is always followed by a space character, even if no command line params are specified. So this should be enough in any case:
string args = Environment.CommandLine
.Substring(Environment.GetCommandLineArgs()[0].Length + 3);
But if you're like me and want to make it 100% waterproof, you can use:
string args = Environment.CommandLine
.TrimStart('"')
.Substring(Environment.GetCommandLineArgs()[0].Length)
.TrimStart('"')
.TrimStart(' ');

Arguments are not passing into command prompt

string appPath = Path.GetDirectoryName(Path.GetDirectoryName(System.IO.Directory.GetCurrentDirectory()));
Process p = new Process();
p.StartInfo = new ProcessStartInfo("cmd.exe", #"/c opencvproject.exe " + #appPath + #"\\bin\\Debug\\center\\centerilluminate.jpg");
p.Start();
I tried this at my other computer and it works , however when I tried it in my new computer this doesn't work somehow. Anyone knows how to solve this? The program I am using is c# and im calling cmd to call c++ program which is opencvproject.exe
There are still multiple instances where I use cmd to trigger other c++ program and python scripts to run and those are not working too. I am not sure what am I doing wrong..
Hold the path between double quotation.
p.StartInfo = new ProcessStartInfo("cmd.exe", "/c opencvproject.exe \"" + appPath + "\\bin\\Debug\\center\\centerilluminate.jpg\"");
Explanation
Character combinations consisting of a backslash () followed by a letter or by a combination of digits are called "escape sequences."
http://msdn.microsoft.com/ja-jp/library/h21280bw.aspx
# makes escape sequence no effect, so..
var s1 = #"\\bin\\Debug\\"; // This contains wrong path \\bin\\Debug\\
var s2 = "\\bin\\Debug\\"; // This contains right path \bin\Debug\
And need using escape sequence to hold the double quotation between double quotation.
var s3 = "\"\\bin\\Debug\\\""; // This contains "\bin\Debug\"
var s4 = #"\"\\bin\\Debug\\\""; // compile error
The # character automatically escapes characters in the string behind it, so you shouldn't double backslash your path.
Eg, where you have:
#"\\bin\\debug\\.."
it should either be:
#"\bin\debug\..."
or:
"\\bin\\debug\\.." (without the #)
Also, if your apppath contains spaces, then you should surround the entire string with " characters

.NET Regex - Help with regex 'alternatives' (Pipe symbol | )

I have a regex expression that I'm trying to construct that processes a file path and tries to find a file path whose directory ends with "Processed" or "Failed".
I have something like this...
static string PROCESSED_DIRECTORY = "Processed";
static string FAILURE_DIRECTORY = "Failed";
...
if (Regex.IsMatch(FileFullPath, String.Format(#"(.*)\\({0})|({1})$", PROCESSED_DIRECTORY, FAILURE_DIRECTORY)))....
This works fine.
However, I created an additional Regex expression because I am also trying to match the occurance of a file that is located in the Processed or Failed directory. The regex is not matching and I believe it has something to do with the pipe symbol. It matches when I check for either 'Failed' or 'Processed' without the pipe symbol.
For example: The following files don't match
- C:\ftp\business\Processed\file.txt
- C:\ftp|business\Failed\file.txt
I would expect them to match.
if (Regex.IsMatch(FileFullPath, String.Format(#"(.*)\\({0}|{1})\\(.*)", PROCESSED_DIRECTORY, FAILURE_DIRECTORY)))
If I somehow could combine the two Regex queries into one to say "Match a path that ends with Failed' or 'Processed' and also match a file that exists in the 'Failed' or 'Processed' directory", that'd be amazing. Right now though, I'm content with having two separate regex calls and getting the second to work.
Works ok for me... Ran this in LINQPad:
string PROCESSED_DIRECTORY = "Processed";
string FAILURE_DIRECTORY = "Failed";
string FileFullPath = #"C:\ftp\business\Processed\moof\file.txt";
Regex.IsMatch(FileFullPath, String.Format(#"(.*)\\({0}|{1})\\(.*)", PROCESSED_DIRECTORY, FAILURE_DIRECTORY)).Dump();
FileFullPath = #"C:\ftp|business\Failed\file.txt";
Regex.IsMatch(FileFullPath, String.Format(#"(.*)\\({0}|{1})\\(.*)", PROCESSED_DIRECTORY, FAILURE_DIRECTORY)).Dump();
Here's a version that will look for either containing the processed/failed strings OR ending in \Processed|Failed\filename.ext:
string PROCESSED_DIRECTORY = "ProcessedPath";
string FAILURE_DIRECTORY = "FailedPath";
string FileFullPath = #"C:\ftp\business\ProcessedPath\moof\file.txt";
Regex.IsMatch(FileFullPath, String.Format(#"((.*)\\({0}|{1})\\(.*))|(.*\\(Processed|Failed)\\(?!.*\\.*))", PROCESSED_DIRECTORY, FAILURE_DIRECTORY)).Dump();
FileFullPath = #"C:\ftp\business\NotTheProcessedPath\moof\Processed\file.txt";
Regex.IsMatch(FileFullPath, String.Format(#"((.*)\\({0}|{1})\\(.*))|(.*\\(Processed|Failed)\\(?!.*\\.*))", PROCESSED_DIRECTORY, FAILURE_DIRECTORY)).Dump();
There are quite a few ways to do this (progressively more complicated and more correct), but the simplest is probably this:
var targetStrings = new[] { PROCESSED_DIRECTORY, FAILURE_DIRECTORY }; //needles
string FileFullPath = #"C:\ftp\business\Processed\moof\file.txt"; //haystack
if (FileFullPath.Split('\\').Any(str => targetStrings.Contains(str)))
{
//...
No regex required. Regex seems overkill and possibly error-prone for this anyway.
System.IO.Path may be relevant to whatever you're doing here; it's what the Path class was made for.

Categories

Resources