#title(Command Line Parser Libraryを使ってコマンドライン引数を解析する1) #navi(.NETプログラミング研究) #contents *Command Line Parser Libraryを使ってコマンドライン引数を解析する1 [#vf98cfa6] アプリケーションを起動する時に指定されたコマンドライン引数は、「[[起動時のコマンドライン引数を取得する>http://dobon.net/vb/dotnet/programing/commandline.html]]」で紹介しているように、System.Environment.CommandLineプロパティやGetCommandLineArgsメソッド、あるいはMainメソッドのパラメータを使って取得することができます。これで十分という場合も多いですが、コマンドライン引数によるオプションが複雑な場合は、どのようなオプションが指定されたかという解析がとても面倒です。 アプリケーションを起動する時に指定されたコマンドライン引数は、「[[起動時のコマンドライン引数を取得する>https://dobon.net/vb/dotnet/programing/commandline.html]]」で紹介しているように、System.Environment.CommandLineプロパティやGetCommandLineArgsメソッド、あるいはMainメソッドのパラメータを使って取得することができます。これで十分という場合も多いですが、コマンドライン引数によるオプションが複雑な場合は、どのようなオプションが指定されたかという解析がとても面倒です。 コマンドライン引数を解析するためのフリーのクラスやライブラリは色々あるようですが、ここでは「[[Command Line Parser Library>http://commandline.codeplex.com/]]」(バージョン1.9.71.2)を使ってみます。Command Line Parser LibraryはMITライセンスで、.NET Framework 3.5以降、あるいはMono Profile 2.1以降で使えます。 **Command Line Parser Libraryをインストールする [#t1338ee6] ***NuGetを使った方法 [#bcbcb8fa] Command Line Parser Libraryは[[NuGet>http://nuget.codeplex.com/]]を使ってインストールすることができます。Visual Studio 2012ではデフォルトでNuGetがインストールされていると思います。Visual Studio 2010では、拡張機能マネージャー(メニューの[ツール]-[拡張機能マネージャー])を使ってインストールできます(詳しくは、「[[NuGet - NuGet でプロジェクト ライブラリを管理する>http://msdn.microsoft.com/ja-jp/magazine/hh547106.aspx]]」等を参考にしてください)。 &embed(<a href="https://www.flickr.com/photos/dobondotnet/8599105827/" title="拡張機能マネージャー by DOBON.NET, on Flickr"><img src="https://farm9.staticflickr.com/8507/8599105827_86f47949ba.jpg" width="500" height="281" alt="拡張機能マネージャー"></a>); もしVisual Studio 2010未満でNuGetがインストールできないのであれば、「[[NuGet.exe Command Line bootstrapper>http://nuget.codeplex.com/releases/view/58939]]」を使う方法もあります。 Visual Studio 2010以上でNuGetがインストールされているならば、次のような手順でCommand Line Parser Libraryをインストールできます。 +Command Line Parser Libraryを使用するプロジェクトをVisual Studioで開きます。 +メニューの[ツール]-[プロジェクト]-[NuGetパッケージの管理]を選択して、ダイアログを表示します。&br;&embed(<a href="https://www.flickr.com/photos/dobondotnet/8599073137/" title="NuGetパッケージの管理ダイアログ by DOBON.NET, on Flickr"><img src="https://farm9.staticflickr.com/8378/8599073137_1535ff760e.jpg" width="500" height="281" alt="NuGetパッケージの管理ダイアログ"></a>); +ダイアログの左側で「オンライン」を選択し、右上の「オンラインの検索」欄に「Command Line Parser Library」と入力して検索します。すると、「Command Line Parser Library」が見つかるはずです。 +見つかった「Command Line Parser Library」を選択して、「インストール」ボタンを押すと、プロジェクトに「Command Line Parser Library」がインストールされ、参照設定に「CommandLine」が追加されます。 このようなダイアログを使わずに、パッケージマネージャーコンソールを使ってインストールすることもできます。この場合は、メニューの[ツール]-[ライブラリパッケージマネージャー]-[パッケージマネージャーコンソール]を選択してパッケージマネージャーコンソールを表示して、以下のように入力すると(「Install-Package」以降)、Command Line Parser Libraryがインストールされます。 PM> Install-Package CommandLineParser ***バイナリをダウンロードする方法 [#r7c3a110] NuGetを使わずに、バイナリを直接ダウンロードしてインストールする昔ながらの方法も有効です。「[[Command Line Parser Library>http://commandline.codeplex.com/]]」からZIPファイルをダウンロードすると、ZIPファイル内の「CommandLine\src\libcmdline\bin\Release」ディレクトリに「CommandLine.dll」と「CommandLine.XML」がありますので、「CommandLine.dll」をプロジェクトの参照設定に追加してください。(方法が分からない方は、「[[「○○○.dllを参照に追加します」の意味は?>http://dobon.net/vb/dotnet/help/addreference.html]]」をご覧ください。) NuGetを使わずに、バイナリを直接ダウンロードしてインストールする昔ながらの方法も有効です。「[[Command Line Parser Library>http://commandline.codeplex.com/]]」からZIPファイルをダウンロードすると、ZIPファイル内の「CommandLine\src\libcmdline\bin\Release」ディレクトリに「CommandLine.dll」と「CommandLine.XML」がありますので、「CommandLine.dll」をプロジェクトの参照設定に追加してください。(方法が分からない方は、「[[「○○○.dllを参照に追加します」の意味は?>https://dobon.net/vb/dotnet/help/addreference.html]]」をご覧ください。) **Command Line Parser Libraryが解析できるコマンドライン引数 [#ob365688] Command Line Parser Libraryが解析できるコマンドライン引数はUNIX風のもので、オプション名の前にダッシュかハイフンが付いたものです。Windowsでお馴染みのスラッシュ(/)ではありません。 例えば、 -o test.txt -v というコマンドライン引数を解析することはできますが、 /o test.txt /v というものは解析できません。 **簡単な使い方 [#z240b773] それでは早速使ってみましょう。 ここでは、"o"と"v"2つのオプションを定義することで、例えば -o test.txt -v というコマンドライン引数を、 「"o"オプションには"test.txt"が指定されていて、"v"オプションも指定されている」 と解析できるようにします。 まずコンソールアプリケーションのプロジェクトを作成して、上記の方法で「Command Line Parser Library」を参照設定に追加しておきます。そして、次のようなクラスを作成します。 #code(vbnet){{ 'オプションをプロパティに持つクラス Public Class Options 'String型のオプション <CommandLine.Option("o"c)> _ Public Property OutputFile As String 'Boolean型のオプション <CommandLine.Option("v"c)> _ Public Property Overwrite As Boolean End Class }} #code(csharp){{ //オプションをプロパティに持つクラス public class Options { //String型のオプション [CommandLine.Option('o')] public string OutputFile { get; set; } //Boolean型のオプション [CommandLine.Option('v')] public bool Overwrite { get; set; } } }} このクラスにはOption属性が適用されたプロパティが2つありますが、これらのプロパティにコマンドライン引数の解析結果が入ります。"o"オプションで指定された値がOutputFileプロパティに、"v"オプションで指定された値がOverwriteプロパティに格納されます(OverwriteプロパティはBoolean型ですので、"v"が指定されていればTrue、指定されていなければFalseになります)。 #column(補足){{ プロパティには、GetとSetの両方が必要です。Getだけではエラーになります。 }} 次に、解析結果を取得するためのコードを記述します。これは、エントリポイントに記述します。 #code(vbnet){{ Module Module1 'エントリポイント Sub Main(args As String()) 'Optionsクラスのインスタンスを作成 Dim opts = New Options() 'コマンドライン引数を解析する Dim isSuccess As Boolean = _ CommandLine.Parser.Default.ParseArguments(args, opts) If isSuccess Then '解析に成功した時は、解析結果を表示 Console.WriteLine("OutputFile: {0}", opts.OutputFile) Console.WriteLine("Overwrite: {0}", opts.Overwrite) Else '解析に失敗 Console.WriteLine("コマンドライン引数の解析に失敗") End If Console.ReadLine() End Sub End Module }} #code(csharp){{ public class Program { //エントリポイント static void Main(string[] args) { //Optionsクラスのインスタンスを作成 var opts = new Options(); //コマンドライン引数を解析する bool isSuccess = CommandLine.Parser.Default.ParseArguments(args, opts); if (isSuccess) { //解析に成功した時は、解析結果を表示 Console.WriteLine("OutputFile: {0}", opts.OutputFile); Console.WriteLine("Overwrite: {0}", opts.Overwrite); } else { //解析に失敗 Console.WriteLine("コマンドライン引数の解析に失敗"); } Console.ReadLine(); } } }} ParseArgumentsメソッドに、Mainメソッドのパラメータであるargsと、Optionsオブジェクトを渡すと、このOptionsオブジェクトに解析結果が入ります。 はじめに示した -o test.txt -v というコマンドライン引数でこのアプリケーションを起動した時、OptionsオブジェクトのOutputFileプロパティが"test.txt"、OverwriteプロパティがTrueになります。 コマンドライン引数に何も指定されなかった場合は、OutputFileプロパティはnull(VB.NETでは、Nothing)、OverwriteプロパティはFalseになります。(この規定値を変更する方法は、後述します。) #column(補足){{ Visual Studioのデバッグでコマンドライン引数を指定してプロジェクトを実行するには、プロジェクトのプロパティを開き(ソリューションエクスプローラーでプロジェクトを右クリックしてコンテキストメニューを表示し、[プロパティ]を選択する)、「デバッグ」タブにある「コマンドライン引数」の欄にコマンドライン引数を入力します。 }} ParseArgumentsメソッドがFalseを返した時は、解析に失敗した時です。解析に失敗するのは、存在しないオプションが含まれていたり、必須のオプション(後述します)が見つからなかったり、指定されたオプションがプロパティの型に変換できなかったり、排他的なオプション(後述します)が指定されている場合などです。ただしParseArgumentsメソッドがFalseを返したとしても、大抵の場合は解析できた結果がOptionsオブジェクトに格納されています。 **長いコマンドラインオプション名 [#y321a393] 上記の例ではオプション名が一文字(Char型)でしたが、2文字以上の長い名前を短い名前と同時に(あるいは単独で)指定することもできます。 次のようにOptionsクラスを変更すると、長い名前を使えるようになります。 #code(vbnet){{ 'オプションをプロパティに持つクラス Public Class Options '短い名前と長い名前を両方を指定する <CommandLine.Option("o"c, "output")> _ Public Property OutputFile As String '長い名前だけを指定する <CommandLine.Option("overwrite")> _ Public Property Overwrite As Boolean End Class }} #code(csharp){{ //オプションをプロパティに持つクラス public class Options { //短い名前と長い名前を両方を指定する [CommandLine.Option('o', "output")] public string OutputFile { get; set; } //長い名前だけを指定する [CommandLine.Option("overwrite")] public bool Overwrite { get; set; } } }} #column(補足){{ 短い名前はChar型、長い名前はString型で指定しますが、1文字のString型のみを指定した場合は、短い名前と解釈されるようです。 }} #column(補足){{ 長い名前にはダッシュやハイフンを含めることもできます。半角スペースや"="は含めることができません。短い名前にはハイフンやダッシュ、それに数字を指定できませんが、アルファベット以外の様々な記号を指定することができます。短い名前と長い名前の双方に日本語が使用できましたが、すべての環境で有効かは分かりません。 }} 長いオプション名をコマンドライン引数に使う場合は、オプション名の前にダッシュ(あるいはハイフン)を2つ付けます。 --output "C:\My Documents\test.txt" --overwrite 上記の例では、OutputFileは短い名前も有効ですので、次のように短い名前を使うこともできます。 -o "C:\My Documents\test.txt" --overwrite 長いオプション名を使った場合は、オプション名と値の区切り文字に"="を使うこともできます。 --output="C:\My Documents\test.txt" --overwrite #column(補足){{ 短い名前の前にもダッシュ(あるいはハイフン)を2つ付けることができます。この時、オプション名と値の区切り文字に"="を使うこともできます。 }} ちなみに短い名前も長い名前も付けられていないプロパティは、そのプロパティ名がそのままオプションの長い名前になります。 #code(vbnet){{ 'オプションをプロパティに持つクラス Public Class Options '"OutputFile"がコマンドラインオプションの長い名前になる <CommandLine.Option> _ Public Property OutputFile As String End Class }} #code(csharp){{ //オプションをプロパティに持つクラス public class Options { //"OutputFile"がコマンドラインオプションの長い名前になる [CommandLine.Option()] public string OutputFile { get; set; } } }} **コマンドライン引数の書き方 [#i0d22b61] 繰り返しになりますが、Command Line Parser Libraryが解析できるコマンドライン引数はダッシュかハイフンを使ったUNIX風のもので、Windowsのようなスラッシュは使えません。 コマンドは、まとめて記述することができます。例えば、 -vo test.txt としたり、 -votest.txt としたりすることもできます。 #column(補足){{ 同じコマンドが2回記述された時は、後で記述されたものが優先されるようです。 }} **必須のオプションを指定する [#p277851c] 必ずなければならない必須のオプションは、Option属性のRequiredプロパティをTrueにします。必須のオプションがコマンドライン引数になかった場合は、ParseArgumentsメソッドがFalseを返します。 以下の例では、OutputFileを必須のオプションにしています。 #code(vbnet){{ 'オプションをプロパティに持つクラス Public Class Options '必須のオプションにする <CommandLine.Option("o"c, "output", Required:=True)> _ Public Property OutputFile As String End Class }} #code(csharp){{ //オプションをプロパティに持つクラス public class Options { //必須のオプションにする [CommandLine.Option('o', "output", Required=true)] public string OutputFile { get; set; } } }} **既定値を指定する [#m0c25aad] オプションが指定されていなかった時の既定値を指定するには、DefaultValueプロパティを使います。 以下の例では、OutputFileの既定値を"test.txt"にしています。 #code(vbnet){{ 'オプションをプロパティに持つクラス Public Class Options '既定値を"test.txt"にする <CommandLine.Option("o"c, DefaultValue:="test.txt")> _ Public Property OutputFile As String End Class }} #code(csharp){{ //オプションをプロパティに持つクラス public class Options { //既定値を"test.txt"にする [CommandLine.Option('o', DefaultValue="test.txt")] public string OutputFile { get; set; } } }} **数値型や列挙型のオプション [#td390437] 上記の例ではオプションは文字列と論理値でしたが、数値や列挙型でも大丈夫です。Null許容型でもOKです。 以下に、整数型、Null許容型、列挙型のオプションの例を示します。 #code(vbnet){{ Public Enum FileType Text Html Xml End Enum 'オプションをプロパティに持つクラス Public Class Options '整数型のオプション <CommandLine.Option("s"c)> _ Public Property Size As Integer '浮動小数点型(Null許容型)のオプション <CommandLine.Option("c"c)> _ Public Property Score As System.Nullable(Of Double) '列挙型のオプション <CommandLine.Option("t"c)> _ Public Property FileType As FileType End Class }} #code(csharp){{ public enum FileType { Text, Html, Xml } //オプションをプロパティに持つクラス public class Options { //整数型のオプション [CommandLine.Option('s')] public int Size { get; set; } //浮動小数点型(Null許容型)のオプション [CommandLine.Option('c')] public double? Score { get; set; } //列挙型のオプション [CommandLine.Option('t')] public FileType FileType { get; set; } } }} 列挙型のオプションの場合、コマンドライン引数でその値を指定するには、その要素の名前を使用します。大文字と小文字の区別はしません。 上記のクラスで正しく解析されるコマンドライン引数の例を示します。 -s 100 -c 1E3 -t text オプションで指定された文字列がそれに対応するプロパティの型に変換できない場合は、ParseArgumentsメソッドがFalseを返します。 オプションが数値型の場合、指定された文字列が数値型に変換できるかはカルチャによって変わるかもしれませんが、上記のようにParser.Defaultを使った解析ではインバリアントカルチャが使われます。これを変える方法は、次回紹介する予定です。 **配列やリストのオプション [#ge7d1468] 配列やリストのオプションも可能です。この場合は、Option属性の代わりに、OptionArrayとOptionList属性を使用します。 以下に、配列とリストのオプションの例を示します。 #code(vbnet){{ 'オプションをプロパティに持つクラス Public Class Options '配列のオプション <CommandLine.OptionArray("n"c)> _ Public Property Numbers As Integer() 'リストのオプション <CommandLine.OptionList("f"c, "files")> _ Public Property Files As IList(Of String) End Class }} #code(csharp){{ //オプションをプロパティに持つクラス public class Options { //配列のオプション [CommandLine.OptionArray('n')] public int[] Numbers { get; set; } //リストのオプション [CommandLine.OptionList('f', "files")] public IList<string> Files { get; set; } } }} #column(注意){{ OptionList属性を適用する場合は、短い名前と長い名前の両方を指定しないと、値を正常に取得できませんでした。 }} 解析可能なコマンドライン引数は、例えば以下のようなものです。リストは、デフォルトでは、コロン(:)で区切ります。 -n 1 5 7 -f 1.txt:2.txt:3.txt リストの区切り文字を変更するには、Separatorプロパティを使います。例えばコンマ(,)にするには、以下のようにします。 #code(vbnet){{ <CommandLine.OptionList("f"c, "files", Separator:=","c)> _ Public Property Files As IList(Of String) }} #code(csharp){{ [CommandLine.OptionList('f', "files", Separator=',')] public IList<string> Files { get; set; } }} **オプション名のないリストを取得する [#bce1f089] 例えばコマンドライン引数が 1.txt 2.txt 3.txt のようにオプション名が付いていない場合でも、その値をリストとして取得することができます。これにはValueList属性を使います。 #code(vbnet){{ 'オプションをプロパティに持つクラス Public Class Options 'String型のオプション <CommandLine.Option("o"c, "output")> _ Public Property OutputFile As String 'オプション名がない値のリスト <CommandLine.ValueList(GetType(List(Of String)))> _ Public Property InputFiles As IList(Of String) End Class }} #code(csharp){{ //オプションをプロパティに持つクラス public class Options { //String型のオプション [CommandLine.Option('o', "output")] public string OutputFile { get; set; } //オプション名がない値のリスト [CommandLine.ValueList(typeof(List<string>))] public IList<string> InputFiles { get; set; } } }} 例えばコマンドライン引数が -o test.txt 1.txt 2.txt 3.txt だった場合、InputFilesプロパティには、"1.txt"、"2.txt"、"3.txt"の3つの文字列が入ります。 1.txt -o test.txt 2.txt 3.txt のように順番が変わっても同じです。 #column(補足){{ コマンドライン引数に不明なオプション(文字列の先頭にダッシュやハイフンが付いていて、Option属性でオプション名として定義されていないもの)が含まれている場合、この不明なオプションをValueList属性で取得することができません。 }} ValueList属性は、MaximumElementsプロパティによって、最大の要素数を指定することもできます。例えば上記のInputFilesプロパティを以下のようにしてMaximumElementsプロパティを1にしてみましょう。 #code(vbnet){{ 'オプション名がない値のリスト <CommandLine.ValueList(GetType(List(Of String)), MaximumElements:=1)> _ Public Property InputFiles As IList(Of String) }} #code(csharp){{ //オプション名がない値のリスト [CommandLine.ValueList(typeof(List<string>), MaximumElements=1)] public IList<string> InputFiles { get; set; } }} この状態でコマンドライン引数を -o test.txt 1.txt 2.txt 3.txt とすると、ParseArgumentsメソッドがFalseを返し、InputFilesプロパティは"1.txt"だけになります。 **次回予告 [#lb6c1405] まだまだ紹介したい機能がありますが、今回はこれまでとさせてください。次回は、大文字と小文字を区別する方法や、同時に指定できないオプションを設定する方法、ヘルプやエラーを表示する方法などを紹介させていただく予定です。 **コメント [#u7f6559a] - ParseArguments(IEnumerable<string> args, params Type[] types) optsは渡せないよね -- &new{2015-09-15 (火) 10:58:01}; #comment //これより下は編集しないでください #pageinfo([[:Category/.NET]] [[:Category/ASP.NET]],2013-03-30 (土) 01:17:29,DOBON!,2013-03-30 (土) 01:17:29,DOBON!) |