I have a (hopefully) simple C# question.
I am parsing arguments in a program where a file will be read from command line, I've allowed for both short and long arguments as input (so for my scenario /f and file are both valid)
The value after either of the above arguments should be the file name to be read.
What I want to do is find this file name in the array based off whichever argument is chosen and copy it in to a string while not leaving any loopholes.
I have functioning code, but I'm not really sure it's "efficient" (and secure).
Code (comments and writes removed):
if ( args.Contains("/f") || args.Contains("file"))
{
int pos = Array.IndexOf(args, "/f");
if (pos == -1)
pos = Array.IndexOf(args, "file");
if (pos > -1)
pos++;
inputFile = (args[pos]);
if (File.Exists(inputFile) == false)
{
Environment.Exit(0);
}
}
Is there a more efficient way to do this, perhaps using some nifty logic in the initial if statement to check which parameter is valid and then do a single check on that parameter?
Using 4 ifs and 2 Array.IndexOf's seems horrible just to support 2 different ways to allow someone to say they want to input a file...
Thanks! And I'm sorry if this seems trivial or is not what SO is meant for. I just don't have any real way to get feedback on my coding practises unfortunately.
Your solution won't scale well. Imagine you have two different arguments with a short and long form. How many conditionals and index checks would that be?
You'd be better off using an existing tool (e.g. Command Line Parser Library) for argument parsing.
One problem I see with the code you provided is that, it will fail if the /f or file is the last argument.
If you don't want to write or use complete argument parsing code, the following code will work slightly better.
var fileArguments = new string[] { "/f", "file" };
int fileArgIndex = Array.FindIndex(args,
arg => fileArguments.Contains(arg.ToLowerInvariant()));
if (fileArgIndex != -1 && fileArgIndex < args.Length - 1)
{
inputFile = args[fileArgIndex + 1];
if (!File.Exists(inputFile))
{
Environment.Exit(0);
}
}
You could write a simple argument parser for your specific need and still have support for "new" scenarios. For example, in your entry method have
// The main entry point for the application.
[STAThread]
static void Main(string[] args)
{
// Parse input args
var parser = new InputArgumentsParser();
parser.Parse(args);
....
}
Where your InputArgumentsParser could be something similar to
public class InputArgumentsParser
{
private const char ArgSeparator = ':';
private Dictionary<string[],Action<string>> ArgAction =
new Dictionary<string[],Action<string>>();
public InputArgumentsParser()
{
// Supported actions to take, based on args
ArgAction.Add(new[] { "/f", "/file" }, (param) =>
Console.WriteLine(#"Received file argument '{0}'", param));
}
/// Parse collection, expected format is "<key>:<value>"
public void Parse(ICollection<string> args)
{
if (args == null || !args.Any())
return;
// Iterate over arguments, extract key/value pairs
foreach (string arg in args)
{
string[] parts = arg.Split(ArgSeparator);
if (parts.Length != 2)
continue;
// Find related action and execute if found
var action = ArgAction.Keys.Where(key =>
key.Contains(parts[0].ToLowerInvariant()))
.Select(key => ArgAction[key]).SingleOrDefault();
if (action != null)
action.Invoke(parts[1]);
else
Console.WriteLine(#"No action for argument '{0}'", arg);
}
}
}
In this case /f:myfile.txt or /file:myfile.txt would spit out to console
Received file argument 'myfile.txt'
Related
I am trying to get better in C# and so I thought making a simple programming language that can run basic commands like println("Hi"); and print("Hi");
However, the code shown scans for one foreach() value, my C# code:
string text = File.ReadAllText(inputFile);
string[] fileText = text.Split(";");
foreach (string token in fileText) {
if (token.StartsWith("println(") && token.EndsWith(")")) {
if (token.Split("\"").Length == 3) {
Console.WriteLine(token.Split("\"")[1]);
} else {
throw new Exception($"println(string); takes exactly 3 arguments of which {token.Split("\"").Length} were given.");
}
} else if (token.StartsWith("println(")) {
throw new Exception("Syntax error");
} else if (token.StartsWith("print(") && token.EndsWith(")")) {
if (token.Split("\"").Length == 3) {
Console.Write(token.Split("\"")[1]);
} else {
throw new Exception(($"print(string); takes exactly 3 arguments of which {token.Split("\"").Length} were given."));
}
} else if (token.StartsWith("print(")) {
throw new Exception("Syntax error");
}
}
My testing file:
print("This ");
println("is a test.");
I only get This as output.
You have stated in a comment (now in the question proper) that text is populated with:
string text = File.ReadAllText(inputFile);
That means a split based on semi-colons will give you the following strings (though the third depends on what comes after the final semi-colon):
print("This ")
<newline>println("is a test.")
<possibly-newline-but-irrelevant-for-this-question>
In other words, that second one does not start with println(, which is why your code is not picking it up. You may want to rethink how you're handling that, especially if you want to handle indented code as well.
I'd (at least initially) opt for stripping all white-space from the beginning and end of token before doing any comparisons.
What #paxdiablo said but the following will also work for the input shown:
var lines = File.ReadAllLines(inputFile); // ReadAllLines, not ReadAllText
foreach (string line in lines) {
var token = line.TrimEnd(new []{';'});
// your code here
I have a C#-Console-Program that gets called by another program.
The other Program is supposed to pass an argument to my program.
Now, in case there is no argument passed to the program, I want to make it so that the user inputs an argument into the args string array of the Main function.
I know that you can check for the length of the args string to see whether it has something in it or not like this:
if(args.Length == 0){}
But I dont seem to get it so that the user can input a new value into the console into the args array.
I already tried it like this:
if(args.Length == 0)
{
args[0] = Console.ReadLine();
}
But it only throws an error because there is the Index is out of range.
Is there a way to add an index to the args-array or any other way to handle this case? Or do I need to rewrite my code so that it doesnt take the args-Array directly but instead checks if the array has an argument in it and if not create a new array?
A better approach would be:
String username, password;
if (args.Length >= 1) username = args[0];
else username = Console.ReadLine();
if (args.Length >= 2) password = args[1];
else password = Console.ReadLine();
That way you get meaningful variables instead of a dumb array;
if (args.Length == 0)
{
// args[0] does not exist because it is an empty array.
// assign it with an new array of string instead.
args = new string[] { Console.ReadLine(), };
}
I was wondering if it's possible to turn this while loop into a lambda statement? I know it's possible if it were a for or a foreach loop, but it's a normal, plain while loop:
while (path.Substring(path.Length - 4) != ".txt" || path.Substring(path.Length - 4) != ".xml")
{
Console.WriteLine("File not a .txt or .xml extension! Enter the file name:");
path = Console.ReadLine();
}
If it is possible, how would one transform this loop into such a lambda statement?
Given that the comments on the question suggest that this isn't really about lambdas, but about minimizing code, here are some small suggestions to avoid some code duplication:
string[] validExtensions = { ".txt", ".xml" };
do
{
Console.WriteLine("Enter the file name:");
path = Console.ReadLine();
if (!validExtensions.Contains(Path.GetExtension(path)))
{
Console.Write("File not a .txt or .xml extension! ");
path = null;
}
}
while (path == null);
Checking for another extension merely requires adding the extension to the array, it doesn't require duplicating the code to determine the extension. The string "Enter the file name:" only has to appear once, even if you want a slightly different message for the first prompt. The code to read a line also only has to appear once.
Personally, I'd say the duplication you had is so small that there is no need to avoid it yet, but you may find this useful if, for example, you need to allow three more extensions, or read from some other location where a single function call does not suffice.
Some additional comments:
Console.ReadLine() can return null. Just like the code in your question, this version doesn't handle that properly.
Case in file extensions is usually ignored. Do you really want to reject ".TXT" as a file extension?
Your while condition path.Substring(path.Length - 4) != ".txt" || path.Substring(path.Length - 4) != ".xml" would never be false. It could be true, or it could throw an exception, but the loop would never terminate normally.
In case you want to do this for learning and academic purposes:
Func<string[], string> getFile = (validExtensions) =>
{
string path = "";
while (!validExtensions.Contains(Path.GetExtension(path)))
{
Console.WriteLine("File not a .txt or .xml extension! Enter the file name:");
path = Console.ReadLine();
}
return path;
};
string path = getFile.Invoke(new string[]{ ".txt", ".xml" });
Use .Invoke(string[]) to call it, passing in desired file extensions.
To pass it into a method, for example:
public string UseFunc(Func<string[], string> getFile, string[] validExtensions)
{
return getFile.Invoke(validExtensions);
}
string path = foo.UseFunc(getFile);
Currently I'm working on an web-API dependent application. I have made a class with a lot of API strings like: /api/lol/static-data/{region}/v1/champion/{id}. Also I made a method:
public static String request(String type, Object[] param)
{
return "";
}
This is going to do the requesting stuff. Because it's very different with each request type how many parameters are being used, I'm using an array for this. Now the question is, is it posssible to String.Format using an array for the paramaters while the keys in the strings are not numbers? Or does anyone know how to do this in a different way?
No, string.Format only supports index-based parameter specifications.
This:
"/api/lol/static-data/{region}/v1/champion/{id}"
^^^^^^^^ ^^^^
will have to be handled using a different method, like string.Replace or a Regex.
You will need to:
Decide on the appropriate method for doing the replacements
Decide how an array with index-based values should map to the parameters, are they positional? ie. {region} is the first element of the array, {id} is the second, etc.?
Here is a simple LINQPad program that demonstrates how I would do it (though I would add a bit more error handling, maybe caching of the reflection info if this is executed a lot, some unit-tests, etc.):
void Main()
{
string input = "/api/lol/static-data/{region}/v1/champion/{id}";
string output = ReplaceArguments(input, new
{
region = "Europe",
id = 42
});
output.Dump();
}
public static string ReplaceArguments(string input, object arguments)
{
if (arguments == null || input == null)
return input;
var argumentsType = arguments.GetType();
var re = new Regex(#"\{(?<name>[^}]+)\}");
return re.Replace(input, match =>
{
var pi = argumentsType.GetProperty(match.Groups["name"].Value);
if (pi == null)
return match.Value;
return (pi.GetValue(arguments) ?? string.Empty).ToString();
});
}
I'm sorry, I can't really google this because I'm not sure how to properly say this in a few words.
But basically I'd like to have something like, when you open your program via dos or via a shortcut looking like this:
"c:\program.exe" value1 value2
that my application would be able to use these values. But also when I don't enter the values, that my application still starts fine.
I hope that made any sense what I'm trying to say here
Any help is appereciated
Those are the args that get passed to your main function:
public static void main (string[] args)
{
// Check to see if at least two args were passed in.
if(args.Length >= 2)
{
Console.WriteLine(args[0]); // value1
Console.WriteLine(args[1]); // value2
}
}
Keep in mind, though, that there's no way to guarantee the order of the args passed in or that they are the values you expect. You should use named arguments and then parse and validate them at the beginning of your application. Your command might look something like:
C:\program.exe /V1 value1 /V2 value2
As for a good list of parsers, I would check out:
.net - Best way to parse command line arguments in C#
Have a peek at the Microsoft tutorial for command line parameters
If a parameter isn't provided then just use some defauls.
public static void Main(string[] args)
{
// The Length property is used to obtain the length of the array.
// Notice that Length is a read-only property:
Console.WriteLine("Number of command line parameters = {0}",
args.Length);
for(int i = 0; i < args.Length; i++)
{
Console.WriteLine("Arg[{0}] = [{1}]", i, args[i]);
}
if(args.length < 2)
{
x = 1;
} else {
{
x = Arg[2];
}
}
When you create a executable you have a Main function that has Main(string[] args), here you can read the params which you used to call the program.
If you want default values, you can make a class variable with a defined value (or use the application properties) and if the program program is called with parameters overwrite them.
Hope it helps you :)
Execute your Program.exe from commandline this way
C:\Program Test1 Test2
To get knowledge on how to this in C# please use the link MSDN