What is the correct way to implement and architect a command line tool as a C# console application?
将命令行工具实现和构建为C#控制台应用程序的正确方法是什么?
Concerns to address include proper parsing of command line variables, and the proper way to output text. While Console.WriteLine() is the most obvious choice for output, what are the circumstances in which one should instead opt to write to the standard error stream, .Error, .SetErrorStream, etc?
需要解决的问题包括正确解析命令行变量以及输出文本的正确方法。虽然Console.WriteLine()是输出最明显的选择,但是应该选择写入标准错误流,.Error,.SetErrorStream等的情况是什么?
What is the proper way for the application to exit while returning a proper return code to the calling command?
在将正确的返回代码返回给调用命令时,应用程序退出的正确方法是什么?
How should the the CancelKeyPress event be implemented to interrupt the program? Is it only for use when an asynchronous operation is occurring on a separate thread?
如何实现CancelKeyPress事件来中断程序?它是否仅在单独线程上发生异步操作时使用?
Is there a concise guide to command line tool programming in C#, or even better an open source project or template which I could use to properly implement a relatively simple tool?
是否有一个简明的C#命令行工具编程指南,或者更好的开源项目或模板,我可以使用它来正确实现一个相对简单的工具?
5 个解决方案
#1
Error messages should be written to stderr aka Console.Error, and normal output to stdout aka Console.Out. This is particularly important for "filter" type console apps whose output (stdout) can be piped to another process, e.g. in a batch file.
错误消息应写入stderr aka Console.Error,并正常输出到stdout aka Console.Out。这对于“过滤器”类型的控制台应用程序尤其重要,其输出(stdout)可以通过管道输送到另一个进程,例如在批处理文件中。
Generally if you encounter an error, write an error message to Console.Error and return a non-zero result. Or if it's an exception, just don't bother handling it.
通常,如果遇到错误,请向Console.Error写入错误消息并返回非零结果。或者,如果它是一个例外,只是不要打扰处理它。
To return a result code, you can either pass it as an argument to Environment.Exit, set the Environment.ExitCode property, or return a non-zero value from main.
要返回结果代码,可以将其作为参数传递给Environment.Exit,设置Environment.ExitCode属性,或者从main返回非零值。
For simple console apps I would:
对于简单的控制台应用,我会:
-
have a helper class to parse the command line.
有一个帮助器类来解析命令行。
-
have a facade class that provides a testable API for the functionality implemented by your command line tool. Like most .NET APIs, this would normally throw an exception if an error occurs.
有一个facade类,为您的命令行工具实现的功能提供可测试的API。与大多数.NET API一样,如果发生错误,通常会抛出异常。
-
the main program simply uses the helper to parse the command line and calls the API passing the arguments passed from the command line. It optionally catches exceptions thrown from the API, logs them, writes a user-oriented error message to Console.Error and sets a non-zero return code.
主程序只是使用帮助程序来解析命令行并调用API传递从命令行传递的参数。它可选地捕获从API抛出的异常,记录它们,将面向用户的错误消息写入Console.Error并设置非零返回代码。
But I wouln't consider this to be the one true way: there isn't really such a thing which is why you're unlikely to find the book you're looking for.
但我不认为这是一个真实的方式:没有真正的这样的事情,这就是为什么你不太可能找到你正在寻找的书。
#2
As to how to implement command parsing, I've succesfully used reflection and delegates before. They way it works is to decorate command methods with a special attribute you make yourself that states the method should be user-invokable, either through the method's name or a string specified in the attribute, i.e.:
至于如何实现命令解析,我以前成功地使用了反射和委托。它们的工作方式是使用您自己制作的特殊属性来装饰命令方法,该属性表明该方法应该是用户可调用的,可以通过方法的名称或属性中指定的字符串,即:
[Command("quit")]
public void QuitApp()
{
...
}
On program startup you can scan a class for such methods, and store delegates that target them in a dictionary where the keys are the commands. This makes it easy to parse commands based on looking up the first word in a dictionary (amortized O(1) ) and easily extensible and maintanable for the future, as new commands are added simply adding separate methods.
在程序启动时,您可以扫描类以获取此类方法,并将指向它们的委托存储在字典中,其中键是命令。这使得基于查找字典中的第一个单词(分摊的O(1))并且在将来可以轻松扩展和维护来轻松解析命令,因为添加新命令只需添加单独的方法。
#3
For command-line handling, check out Mono.GetOptions. It makes it easy to populate variables from short (-f style) and long (--file style) command line options.
有关命令行处理,请查看Mono.GetOptions。它可以很容易地从短(-f style)和long( - file style)命令行选项填充变量。
#4
As for command line arguments, you'll find various schemes, but I've always been a fan of
至于命令行参数,你会发现各种方案,但我一直都是粉丝
app.exe "self-explanatory arg" /noArgumentSwitch /argumentSwitch="argument"
As for the return code, you can change the signature of your Main()
function to return an int
rather than void
. This will allow you to return a code to the calling process, if necessary.
至于返回代码,您可以更改Main()函数的签名以返回int而不是void。这将允许您在必要时将代码返回给调用进程。
As for the error stream, I've never personally used it, and I don't think that it should come at the expense of outputting error information in the standard output stream. It's probably better used for specific error debugging information.
至于错误流,我从来没有亲自使用它,我认为它不应该以在标准输出流中输出错误信息为代价。它可能更适合用于特定的错误调试信息。
#5
I've opted to write a number of console utility apps as windows form applications instead of console apps. Generally, I add a timer to delay initial start and just add a cancel button with a progress meter--thus allowing a more intuitive cancel option. You can still output to the console that way too.
我选择将一些控制台实用程序应用程序编写为Windows窗体应用程序而不是控制台应用程序。通常,我添加一个计时器来延迟初始启动,只需添加带有进度表的取消按钮 - 从而允许更直观的取消选项。您仍然可以通过这种方式输出到控制台。
#1
Error messages should be written to stderr aka Console.Error, and normal output to stdout aka Console.Out. This is particularly important for "filter" type console apps whose output (stdout) can be piped to another process, e.g. in a batch file.
错误消息应写入stderr aka Console.Error,并正常输出到stdout aka Console.Out。这对于“过滤器”类型的控制台应用程序尤其重要,其输出(stdout)可以通过管道输送到另一个进程,例如在批处理文件中。
Generally if you encounter an error, write an error message to Console.Error and return a non-zero result. Or if it's an exception, just don't bother handling it.
通常,如果遇到错误,请向Console.Error写入错误消息并返回非零结果。或者,如果它是一个例外,只是不要打扰处理它。
To return a result code, you can either pass it as an argument to Environment.Exit, set the Environment.ExitCode property, or return a non-zero value from main.
要返回结果代码,可以将其作为参数传递给Environment.Exit,设置Environment.ExitCode属性,或者从main返回非零值。
For simple console apps I would:
对于简单的控制台应用,我会:
-
have a helper class to parse the command line.
有一个帮助器类来解析命令行。
-
have a facade class that provides a testable API for the functionality implemented by your command line tool. Like most .NET APIs, this would normally throw an exception if an error occurs.
有一个facade类,为您的命令行工具实现的功能提供可测试的API。与大多数.NET API一样,如果发生错误,通常会抛出异常。
-
the main program simply uses the helper to parse the command line and calls the API passing the arguments passed from the command line. It optionally catches exceptions thrown from the API, logs them, writes a user-oriented error message to Console.Error and sets a non-zero return code.
主程序只是使用帮助程序来解析命令行并调用API传递从命令行传递的参数。它可选地捕获从API抛出的异常,记录它们,将面向用户的错误消息写入Console.Error并设置非零返回代码。
But I wouln't consider this to be the one true way: there isn't really such a thing which is why you're unlikely to find the book you're looking for.
但我不认为这是一个真实的方式:没有真正的这样的事情,这就是为什么你不太可能找到你正在寻找的书。
#2
As to how to implement command parsing, I've succesfully used reflection and delegates before. They way it works is to decorate command methods with a special attribute you make yourself that states the method should be user-invokable, either through the method's name or a string specified in the attribute, i.e.:
至于如何实现命令解析,我以前成功地使用了反射和委托。它们的工作方式是使用您自己制作的特殊属性来装饰命令方法,该属性表明该方法应该是用户可调用的,可以通过方法的名称或属性中指定的字符串,即:
[Command("quit")]
public void QuitApp()
{
...
}
On program startup you can scan a class for such methods, and store delegates that target them in a dictionary where the keys are the commands. This makes it easy to parse commands based on looking up the first word in a dictionary (amortized O(1) ) and easily extensible and maintanable for the future, as new commands are added simply adding separate methods.
在程序启动时,您可以扫描类以获取此类方法,并将指向它们的委托存储在字典中,其中键是命令。这使得基于查找字典中的第一个单词(分摊的O(1))并且在将来可以轻松扩展和维护来轻松解析命令,因为添加新命令只需添加单独的方法。
#3
For command-line handling, check out Mono.GetOptions. It makes it easy to populate variables from short (-f style) and long (--file style) command line options.
有关命令行处理,请查看Mono.GetOptions。它可以很容易地从短(-f style)和long( - file style)命令行选项填充变量。
#4
As for command line arguments, you'll find various schemes, but I've always been a fan of
至于命令行参数,你会发现各种方案,但我一直都是粉丝
app.exe "self-explanatory arg" /noArgumentSwitch /argumentSwitch="argument"
As for the return code, you can change the signature of your Main()
function to return an int
rather than void
. This will allow you to return a code to the calling process, if necessary.
至于返回代码,您可以更改Main()函数的签名以返回int而不是void。这将允许您在必要时将代码返回给调用进程。
As for the error stream, I've never personally used it, and I don't think that it should come at the expense of outputting error information in the standard output stream. It's probably better used for specific error debugging information.
至于错误流,我从来没有亲自使用它,我认为它不应该以在标准输出流中输出错误信息为代价。它可能更适合用于特定的错误调试信息。
#5
I've opted to write a number of console utility apps as windows form applications instead of console apps. Generally, I add a timer to delay initial start and just add a cancel button with a progress meter--thus allowing a more intuitive cancel option. You can still output to the console that way too.
我选择将一些控制台实用程序应用程序编写为Windows窗体应用程序而不是控制台应用程序。通常,我添加一个计时器来延迟初始启动,只需添加带有进度表的取消按钮 - 从而允许更直观的取消选项。您仍然可以通过这种方式输出到控制台。