關於命令行參數的解析沒有特定的規則,目前比較流行的有unix風格和微軟風格。其實除了unix風格 的比較一致外,微軟自己提供的命令行參數解析就有很多種風格。在.net平台下的main函數中,僅僅把參 數分解為以空格分割的數組,這對需要加開關,並且有的開關有自己的參數的情況是不夠的,而且為了解 析這些參數需要學習部分詞法分析的知識,這對用處不是很大的命令行參數顯得有些“雞肋”,當然用 Antlr來處理命令行參數更顯得有些雞肋,並且是大才小用,因為Antlr的語法規則比較復雜,學習起來有 一定的難度。但對於已經使用Antlr進行DSL開發的開發人員來說,解決命令行參數解析的問題是舉手之勞 。
我們需要先定義命令行參數的輸入規則,參考.Net framework提供的命令行工具,制定如下的規則:
1、以空格分割參數,與.net命令行參數保持一致。
2、選項(option)或者開關(switch)用/ 或\ 或 - 作為標志。
3、選項可以有參數,使用冒號:分割,選項的參數如果是多個使用逗號(,)或者分號(;)分隔。
4、選項之後是命令行自身的參數。
5、選項名稱以英文字母開頭。
6、選項的參數和命令行自身的參數可以使用英文字母和數字型、下劃線,如果包含雙字節碼或者特殊 字符,需要使用雙引號。
規則制定好以後,開始進入正題:
第一步,需要從Antlr網站(http://www.antlr.org)上下載Antlr類庫及相關的學習資料及工具,工 具中比較重要的是Antlr works。
第二步,使用Antlr works或者文本編輯器編輯詞法和語法規則:
grammar CmdPara;
options{
language=CSharp2;
output=AST;
ASTLabelType=CommonTree;
}
tokens{
Option;
}
cmdLine : String option* para* EOF!;
option
: swt ((':' para)((','|';')para)*)? ->^(Option swt para*);
swt : ('/'|'-'|'\\') ID -> ID;
para : String|INT|ID;
ID : ('a'..'z'|'A'..'Z')(('a'..'z')
|('A'..'Z')
|'0'..'9'
|'&'
|'/'
|'\\'
|'.'
|'_'
)*; //don't support chinese
String : ('"' .+ '"')|('\'' .+ '\'');
INT : ('1'..'9')('0'..'9')*
;
WS
: (' '|'\r'|'\t'|'\u000C'|'\n') {$channel=HIDDEN;}
;
第三步:定義命令行參數解析實體類
namespace Rz.CmdLine
{
//命令行對象
public class CmdLine {
//可執行文件的文件名
public string ExeFile { get; set; }
private List<Switch> _cmdSwitchs = new List<Switch>();
//開關對象列表
public List<Switch> CmdSwitchs
{
get { return _cmdSwitchs; }
}
private List<string> _parameters = new List<string>();
//命令行參數
public List<string> Parameters
{
get { return _parameters; }
}
}
//開關實體類
public class Switch {
//開關名稱
public string SwitchName { get; set; }
//開關參數
private List<string> _parameters = new List<string>();
public List<string> Parameters
{
get { return _parameters; }
}
}
}
第四步:使用Antlr定義語法樹遍歷器
tree grammar CmdWalker;
options{
language=CSharp2;
tokenVocab=CmdPara;
ASTLabelType=CommonTree;
}
@header{
using Rz.CmdLine;
}
@members{
}
cmdLine returns[CmdLine r = new CmdLine()]
: String
{
r.ExeFile=$String.Text;
}
(option{r.CmdSwitchs.Add($option.r);})*
(para{r.Parameters.Add($para.r);})*
;
option returns[Switch r = new Switch()]
:
^(Option
swt{r.SwitchName=$swt.r;}
(para{r.Parameters.Add($para.r);})*
)
;
swt returns[string r]
: ID{r=$ID.Text;}
;
para returns[string r]
:
String{r=$String.Text;}
|INT{r=$INT.Text;}
|ID{r=$ID.Text;}
;
第 五 步:定義解析命令行的靜態類,把命令行轉變為前面定義的實體類。
public class CmdEngine
{
public static CmdLine Parse(string cmdLine) {
ANTLRStringStream input = new ANTLRStringStream(cmdLine);
CmdParaLexer lex = new CmdParaLexer(input);
CommonTokenStream token = new CommonTokenStream(lex);
CmdParaParser pars = new CmdParaParser(token);
CommonTreeNodeStream tree = new CommonTreeNodeStream(pars.cmdLine().Tree);
CmdWalker walker = new CmdWalker(tree);
return walker.cmdLine();
}
}
第六步:編寫測試程序
static void Main(string[] args)
{
Console.WriteLine(string.Join(" ",args));
CmdLine cmd = CmdEngine.Parse(Environment.CommandLine);
Console.WriteLine (cmd.ExeFile);
foreach (Switch s in cmd.CmdSwitchs) {
Console.WriteLine("Switch /{0}:{1}",s.SwitchName,string.Join (",",s.Parameters.ToArray()));
}
foreach (string s in cmd.Parameters) {
Console.WriteLine("Command Parameter [{0}]:{1}",cmd.Parameters.IndexOf (s),s);
}
Console.ReadLine();
}
第七步:查看程序運行結果
源代碼稍後會放到google開源庫中,由於codeplex的速度太慢,實在無法忍受,希望有一天微軟在 Internet上跟上google的腳步。
參考:
CodePlex上一個開源參數解析類庫
以-標明選項
短選項,-sHello,其中-是標志位,s是選項,Hello是參數。或者-s Hello,這個差別在於選項與參 數之間使用空格分割。
多個短選項:-x -y -z -w 或者-xyzw 或者-xy -zw
長選項:--long Hello 或者--long=hello
一個開源的Unix風格的參數解析:
http://blog.pumacode.org/2006/07/24/csharp-getopts-mono-getoptions/