Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
Improve this question
Is there a command-line parsing library for C# with good support for "sub-commands" in the style of git, svn etc.? For example, the "git" command has several sub-commands:
git add
git status
git diff
...
There are both global options that must precede the sub-command name, and options specific to the sub-command that must follow its name. For example, these do different things:
git -p add
git add -p
Different sub-commands might each have entirely different sets of options and arguments.
I've been using NDesk.Options, but up until now I haven't needed to implement sub-commands. I think it's flexible enough to build sub-commands on top of, but it's not entirely obvious how best to do this in a concise and elegant fashion. Is it possible to do this in NDesk.Options or is there a more suitable C# command-line parsing library?
I'm partial to my own option parsing library, which I'm blogging about currently. I do plan to cover sub-commands, but it'll be a while before I get to it (it'll be one of the last posts).
Nito.KitchenSink.OptionParsing doesn't support sub-commands directly, but you can use the library to parse only parts of the command line, and handle the sub-commands yourself. The "global" and "sub-command-specific" option sets add an interesting twist, but it can be done like this:
using System;
using System.Linq;
using Nito.KitchenSink.OptionParsing;
class Program
{
private sealed class GlobalOptions : OptionArgumentsBase
{
// Use a better name than "POption". This is just an example.
[Option('p', OptionArgument.None)]
public bool POption { get; set; }
// Override Validate to allow AdditionalArguments.
public override void Validate()
{
}
}
private sealed class AddOptions : OptionArgumentsBase
{
[Option('p', OptionArgument.None)]
public bool POption { get; set; }
}
static int Main()
{
try
{
// Parse the entire command line into a GlobalOptions object.
var options = OptionParser.Parse<GlobalOptions>();
// The first entry in AdditionalArguments is our sub-command.
if (options.AdditionalArguments.Count == 0)
throw new OptionParsingException("No sub-command specified.");
object subcommandOptions = null;
string subcommand = options.AdditionalArguments[0];
switch (subcommand)
{
case "add":
{
// Parse the remaining arguments as command-specific options.
subcommandOptions = OptionParser.Parse<AddOptions>(options.AdditionalArguments.Skip(1));
break;
}
case "status": // TODO: Parse command-specific options for this, too.
break;
case "diff": // TODO: Parse command-specific options for this, too.
break;
default:
throw new OptionParsingException("Unknown sub-command: " + subcommand);
}
// At this point, we have our global options, subcommand, and subcommand options.
Console.WriteLine("Global -p option: " + options.POption);
Console.WriteLine("Subcommand: " + subcommand);
var addOptions = subcommandOptions as AddOptions;
if (addOptions != null)
Console.WriteLine("Add-specific -p option: " + addOptions.POption);
return 0;
}
catch (OptionParsingException ex)
{
Console.Error.WriteLine(ex.Message);
// TODO: write out usage information.
return 2;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return 1;
}
}
}
The sample program above produces the following output:
>CommandLineParsingTest.exe
No sub-command specified.
>CommandLineParsingTest.exe -p
No sub-command specified.
>CommandLineParsingTest.exe test
Unknown sub-command: test
>CommandLineParsingTest.exe add
Global -p option: False
Subcommand: add
Add-specific -p option: False
>CommandLineParsingTest.exe -p add
Global -p option: True
Subcommand: add
Add-specific -p option: False
>CommandLineParsingTest.exe add -p
Global -p option: False
Subcommand: add
Add-specific -p option: True
>CommandLineParsingTest.exe -p add -p
Global -p option: True
Subcommand: add
Add-specific -p option: True
>CommandLineParsingTest.exe status
Global -p option: False
Subcommand: status
>CommandLineParsingTest.exe -p status
Global -p option: True
Subcommand: status
You can try the Open Source project "Command Line" :
http://commandline.codeplex.com/
Related
I am trying to use System.CommandLine and I haven't been able to get my handler to see any of the values that I'm passing in. I've tried the simplest command line program just to see if any values make it through and so far I haven't been successful. I am targeting .NET 4.7.2 and I'm using System.CommandLine 2.0.0-beta1.20574.7
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
static class Program
{
public static void Main(string[] args)
{
var rootCommand = new RootCommand
{
new Option("--continue", "continue option")
};
rootCommand.Description = "Testing System.CommandLine";
rootCommand.Handler = CommandHandler.Create<bool>
((willContinue) => run(willContinue));
rootCommand.Invoke(args);
}
private static void run(bool willContinue)
{
Console.WriteLine(willContinue);
}
}
No matter how I call my application, I am not seeing the value of willContinue come across as true.
myapp.exe --continue -> False
myapp.exe --cont -> Unrecognized command or argument '--cont' (my options are at least getting recognized)
myapp.exe --continue true -> Unrecognized command or argument 'true'
myapp.exe --help ->
myapp:
Testing System.CommandLine
Usage:
myapp [options]
Options:
--continue continue option
--version Show version information
-?, -h, --help Show help and usage information
You need to fix 2 things:
add the option type, which is bool
change the name of the option to match the parameter name
the following command works:
var rootCommand = new RootCommand
{
new Option<bool>("--willContinue", "continue option")
};
and call it like so
myapp.exe --willContinue true
the option name and parameter name don't always have to match-up, but in this case it doesn't work because 'continue' is a reserved word
I wanted to add an answer to be very clear about exactly what resolved my problem
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
static class Program
{
public static void Main(string[] args)
{
var rootCommand = new RootCommand
{
new Option("--willContinue", "continue option")
// ^This option name
};
rootCommand.Description = "Testing System.CommandLine";
rootCommand.Handler = CommandHandler.Create<bool>
((WiLLCoNtInUe) => run(WiLLCoNtInUe));
// ^ HAS to match this parameter name where the command handler is created.
// but does not have to match case
rootCommand.Invoke(args);
}
private static void run(bool canBeSomethingElse)
// Because of how this is called ^ this does not have to match
{
Console.WriteLine(canBeSomethingElse);
}
}
Because the Argument/Option/Command/anything else that can be added to a Command object has to match the name of the parameter used when creating the CommandHandler, the names used can't have spaces, begin with numbers, or use a C# keyword (not having spaces is not a problem for most items because it would be silly/impossible to have an option --will continue, but with arguments you don't have to put the name of the argument into your command line call because it is listed in the usage as <argument name> and the argument is read in by position, so I was wanting to use an argument name like <file directory>, but that doesn't work because you can't have a variable name with spaces in it)
I am trying to create a project that accepts a configuration file and 2 comparison files using a command line arguments with the paths to these files included. Would I construct this the same way you would pass any command line argument? Sorry I am new to this so I am not sure if there is an exception when trying to pass files.
Can I get an example of how this would be done? Here is a picture of the directions of what exactly I have been asked.
Accept the following command line arguments:
Configuration file (with path) (described below)
Comparison File 1 (with path)
Comparison File 2 (with path)
Take a look at the documentation of Main function arguments
Assuming this is your main function and you want to accept 3 parameters:
static int Main(string[] args)
{
// check the length of args for validation.
// args[0] -> Configuration file
// args[1] -> Comparison File 1
// args[2] -> Comparison File 2
..... DO SOMETHING...
return 0;
}
Usage (from command line or debugger):
SomeProgram.exe "ConfigFilePath" "ComparisonFile1" "ComparisonFile2".
Because I really like this nuget(No association just a fan). Here is an example of it using CommandLineUtils
First add an new project with dotnet new consol TestConsolUtils then add the nuget dotnet add package McMaster.Extensions.CommandLineUtils then copy this code to the program class.
using McMaster.Extensions.CommandLineUtils;
using System;
namespace ConsolUtilsTest
{
class Program
{
public static int Main(string[] args)
=> CommandLineApplication.Execute<Program>(args);
[Argument(0, Description = "Configuration file")]
[FileExists]
public string ConfigurationFile { get; }
[Argument(1, Description = "Comparison file 1")]
[FileExists]
public string ComparisonFile1 { get; }
[Argument(2, Description = "Comparison File 2")]
[FileExists]
public string ComparisonFile2 { get; }
private void OnExecute()
{
Console.WriteLine(ConfigurationFile);
Console.WriteLine(ComparisonFile1);
Console.WriteLine(ComparisonFile2);
}
}
}
do a dotnet build
Go to the dll folder that was just build most likely in Debug\netcoreapp2.2\
Create a fake file A.json this is required because the utility will check if the file exists.
Run it with dotnet command
dotnet TestConsolUtils.dll A.json A.json A.json
There are a lot more you can do with this utill just look at the documentation.
In terminal or cmd, you can write commands, in which there is a main command and then sub-commands, or arguments and stuff...like this:
cd Desktop\Folder
lst
Format E: /fs:FAT32
I want to create a C# console application that could execute predefined commands like this, but which could also split up main commands and sub-commands, in which some could be optional and some not. I have tried just taking all as string and then splitting it to array and creating if(s) and switch and cases, but it looks really bad and hardly manageable. I'm sure that in the OS's terminal or cmd it's build in another way. Could you help me understand the basic structure of such an application?
Here, have a look at this concept.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpConsole
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to SharpConsole. Type in a command.");
while (true)
{
Console.Write("$ ");
string command = Console.ReadLine();
string command_main = command.Split(new char[] { ' ' }).First();
string[] arguments = command.Split(new char[] { ' ' }).Skip(1).ToArray();
if (lCommands.ContainsKey(command_main))
{
Action<string[]> function_to_execute = null;
lCommands.TryGetValue(command_main, out function_to_execute);
function_to_execute(arguments);
}
else
Console.WriteLine("Command '" + command_main + "' not found");
}
}
private static Dictionary<string, Action<string[]>> lCommands =
new Dictionary<string, Action<string[]>>()
{
{ "help", HelpFunc },
{ "cp" , CopyFunc }
};
private static void CopyFunc(string[] obj)
{
if (obj.Length != 2) return;
Console.WriteLine("Copying " + obj[0] + " to " + obj[1]);
}
public static void HelpFunc(string[] args)
{
Console.WriteLine("===== SOME MEANINGFULL HELP ==== ");
}
}
}
The basic idea is to generalize the idea of a command. We have a Dictionary, where the key is a string (the command's name), and the value you get from the dictionary is a function of type Action<string[]>. Any function which has the signature void Function(string[]) can be used as this type. Then, you can set up this dictionary with a bunch of commands and route them to the functions you want. Each of these functions will receive an array of optional arguments. So here, the command "help" will be routed to the HelpFunc(). And the "cp" command e.g. will receive an array of filenames. The parsing of the command is always the same. We read a line, split it a space. The first string is the program's name, command_main here. If you skip the first string, you'll get an enumeration of all the other subcommands or switches you typed in. Then, a lookup in the dictionary is being done to see if there is such a command. If yes, we get the function and execute it with the arguments. If not, you should display "command not found" or something. All in all, this exercise can be minimized to looking up a function in a dictionary of possible command strings, then executing it. So a possible output is
Welcome to SharpConsole. Type in a command.
$ help
===== SOME MEANINGFULL HELP ====
$ cp file1 otherfile2
Copying file1 to otherfile2
$ python --version
Command 'python' not found
$ ...
LXSH
It's a command interpreter similar to CMD or Bash.
We've distributed it under MIT license, a shell with some functionalities in C# (.NET Core). You can contribute if you wish on
GitHub.
To solve the problem of matching a given token (part of the command line) with a builtin or a command, we use a dictionary.
However, we don't index the programs in the path for the moment. We just combine the name of the program with all the paths in the %PATH% variable.
Capture input
Expand environment variables, expand aliases
Try to match a builtin and run it if there is a match
Try to match with a program in %PATH% / $PATH
Run the program or display error
While you are unlikely to find the internal working of CMD (because it's closed source), you can find easily unix shell (bash, sh, zsh, etc..) information.
Links:
Bash Reference
Zsh Reference
TCSH Reference
I am trying to call ghost script from my C# program, passing it some args to crop the footer of a PDF file, then overwrite the temp file with the new modified version.
I think I'm calling the gs.exe incorrectly. Does anyone see a reason that the string I'm passing to start(gs) doesn't work?
When trace the script, it triggers the catch when it gets to System.Diagnostics.Process.Start(gs);
This is the string that's being called in the process.start(gs) function
C:\gs\gs9.14\bin\gswin64c.exe -o C:\Users\myname\Desktop\assignment1\assignment1\data\temp\test.pdf -sDEVICE=pdfwrite -c "[/CropBox [24 72 559 794] /PAGES pdf mark" -f C:\Users\myname\Desktop\assignment1\assignment1\data\temp\test.pdf
This is the message that I get in my console.
System.ComponentModel.Win32Exception (0x80004005): The system cannot find the file specified
at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start()
at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start(String fileName)
at assignment1.Program.cropPDFFooter(String tempPDF) in C:\Users\tessierd\Desktop\assignment1\assignment1\Program.cs:line 78
Then this is the code for my method.
public static void cropPDFFooter(string tempPDF)
{
try
{
byte[] croppedPDF = File.ReadAllBytes(tempPDF);
string gsPath = #"C:\gs\gs9.14\bin\gswin64c.exe";
List<string> gsArgsList = new List<string>();
gsArgsList.Add(" -o " + tempPDF);
gsArgsList.Add(" -sDEVICE=pdfwrite");
gsArgsList.Add(" -c \"[/CropBox [24 72 559 794] /PAGES pdfmark\"");
gsArgsList.Add(" -f " + tempPDF);
var gsArgs = String.Join(null, gsArgsList);
string gs = gsPath + gsArgs; // not needed anymore (see solution)
// * wrong code here.
// System.Diagnostics.Process.Start(gs);
// * Correct code below.
System.Diagnostics.Process.Start(gsPath, gsArgs);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Console.ReadLine();
}
}
System.Diagnostics.Process.Start(gs); takes 2 parms. a file, and then args.
I had to change the code to
System.Diagnostics.Process.Start(gsPath, gsArgs);
I would suggest you to use Ghostscript wrapper for .NET.
You can find one here: Ghostscript.NET on GitHub
Usage example can be found here: Ghostscript Processor C# Sample
There is also an example on how to add the watermark with a -c switch which postscript you can simply replace with your cropbox postscript: Ghostscript.NET - Passing Postscript commands (take a look at the bottom function)
Is there any way to retrieve the current source filename and linenumber in C# code and print that value in the console output? Like LINE and FILE in C?
Please advise.
Many thanks
Anders Hejlsberg presented new API for that in BUILD keynote:
Print current file name, method name and line number
private static void Log(string text,
[CallerFilePath] string file = "",
[CallerMemberName] string member = "",
[CallerLineNumber] int line = 0)
{
Console.WriteLine("{0}_{1}({2}): {3}", Path.GetFileName(file), member, line, text);
}
Test:
Log(".NET rocks!");
Output:
Program.cs_Main(11): .NET rocks!
What's going on here?
You define a method with optional parameters and decorate them with special attributes. If you call method without passing actual arguments (leave defaults) - the Framework populates them for you.
This answer is outdated! See #taras' answer for more recent information.
No constant :(
What you can do is a lot uglier :
string currentFile = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileName();
int currentLine = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileLineNumber();
Works only when PDB files are available.
You can use the StackTrace object from the System.Diagnostics namespace but the information will only be available if the PDB files are there.
PDB files are generated by default for both the Debug and Release builds the only difference is that Debug is setup to generate a full debug info where as the Release build is setup to only generate a pdb (full/pdb-only).
Console.WriteLine(new StackTrace(true).GetFrame(0).GetFileName());
Console.WriteLine(new StackTrace(true).GetFrame(0).GetFileLineNumber());
There are no constants defined for that as of now.
The .NET way of doing it is using StackTrace class.
It however works only for Debug builds. So in case you use it, you can have the code using StackTrace between
#if DEBUG
//your StackTrace code here
#endif
You can read about using #if preprocessors for your DEBUG vs. RELEASE builds in the following Stackoverflow thread.
C# if/then directives for debug vs release
EDIT: Just in case you still need this debugging information in release builds, read the following answer on Stackoverflow:
Display lines number in Stack Trace for .NET assembly in Release mode
If you want some more internal detail, but you don't specifically need filename and line number, you can do something like this:
System.Diagnostics.Debug.Print(this.GetType().ToString() + " My Message");
This has an advantage over printing out the filename in that if you put this in a parent class, it will print out the child class name that is actually running the code.
If you wanted to write your own version of Debug.Assert, then here's a more complete answer:
// CC0, Public Domain
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System;
public static class Logger {
[Conditional("DEBUG")]
public static void Assert(bool condition, string msg,
[CallerFilePath] string file = "",
[CallerMemberName] string member = "",
[CallerLineNumber] int line = 0
)
{
// Debug.Assert opens a msg box and Trace only appears in
// a debugger, so implement our own.
if (!condition)
{
// Roughly follow style of C# error messages:
// > ideone.cs(14,11): error CS1585: Member modifier 'static' must precede the member type and name
Console.WriteLine($"{file}({line}): assert: in {member}: {msg}");
// Or more precisely match style with a fake error so error-parsing tools will detect it:
// Console.WriteLine($"{file}({line}): warning CS0: {msg}");
}
}
}
class Program {
static void Main(string[] args) {
Logger.Assert(1+1 == 4, "Why not!");
}
}
Try it online.