wiki:dokuwiki:cs:コンソールアプリの引数の扱い方

コンソールアプリの引数の扱い方

コンソールアプリを、簡単そうだから作ってみようと思うと、意外と引数の扱い方に苦悶します。
というわけで、簡単(というほど簡単ではなかった)に引数を扱う方法を紹介します。
参考文献はこれ
https://learn.microsoft.com/ja-jp/dotnet/standard/commandline/define-commands とりあえず、迷ったら参考文献のリンク含めて全部読んでください。

まず、ここでつまづく。

NuGetでライブラリを取得するわだが、「system.commandLine」で検索するわけですが、必ず「プレスリリースを含める」にチェックしましょう。あたしゃこれだけで、1、2時間つぶしました。

どうやら、ルートコマンド・サブコマンドというものがあるらしいです。詳しくは知らん。

オプションというのは、いわゆるDOSでいうところの「/d」とか「/s /a」とかっていうやつ。基本的に、各オプションには「型」と「引数」というのがあって、文字列型オプション「-S」に「hogehoge」を渡す場合は「-s hogehoge」となる。
で!!「型」を「boolean」型にすると、「引数」を省略できる。つまり「-s」と書けば、「-s」スイッチが渡されたことになる。

オプションのバンドル

これは、上記参考文献の関連項目に記載されています。

POSIX では、1 文字のオプションの “バンドル” (“スタック” とも呼ばれます) をサポートすることが推奨されています。 バンドルされたオプションは、1 つのハイフン プレフィックスの後に、1 文字のオプション エイリアスをまとめて指定したものです。

つまりこれの「ltr」の部分。下の2つは等価であるということ。

  1. ls -ltr
  2. ls -l -t -r

引数は、オプションの指定が必要ないもの。下の場合、42と「Hello world!」がアプリケーションに渡される。

  1. myapp 42 "Hello world!"

「ルートコマンド」と定義して、その下に「オプション」や「引数」をぶらさげていく。正直、参考文献見た方が早い。

  1. var delayOption = new Option<int>
  2. (name: "--delay",
  3. description: "An option whose argument is parsed as an int.",
  4. getDefaultValue: () => 42);
  5. var messageOption = new Option<string>
  6. ("--message", "An option whose argument is parsed as a string.");
  7.  
  8. var rootCommand = new RootCommand();
  9. rootCommand.Add(delayOption);
  10. rootCommand.Add(messageOption);
  11.  
  12. rootCommand.SetHandler((delayOptionValue, messageOptionValue) =>
  13. {
  14. Console.WriteLine($"--delay = {delayOptionValue}");
  15. Console.WriteLine($"--message = {messageOptionValue}");
  16. },
  17. delayOption, messageOption);

上の例では、delayOption オプションとmessageOption オプションを定義して、rootCommand にAddしている。Option型でnewすればオプションの定義、Argument型でnewすれば引数の定義となる。どちらも定義したあとに、rootComandにAddすれば良い。
newするときの「name」は、ユーザが渡すときの「名称」となるので、「–delay」スイッチを渡すことになる。
使用例は下。

  1. myapp --delay 21 --message "Hello world!"

「description」は、helpを表示したときの説明となる。help表示に関しては、何も実装しなくてもコマンド概要が表示されるようになる。つまり、helpオプションを実装しなくても

  1. myapp -h

としたり、オプションエラーは引数エラーとなったときに表示される内容が担保される。

さて、rootCommandにオプションや引数をAddしたら、最後にrootCommandのハンドラーをセットする。これは、正直意味不明だが、イメージとして、「アプリケーションに引数が渡ってきた!」という「イベント」の処理を定義すると思って良いと思います。
ただし、このSetHandlerメソッドは引数が8個までしか受け入れない。つまり、9個以上の引数が必要なときに困る。
9個以上のときは、2つに分けて、SetHandlerメソッドを2回呼べばいいじゃんと考えてはダメ。「アプリケーションに引数が渡ってきた!」という「イベント」の処理の定義なので、2行かいたら、後に書かれた行で上書きされる。というか、ハンドラーと名のつくものを2回も3回も呼び出すのは良くないと覚えておいたほうが良い。

で、そうするの?ってところだけども、オプション用や引数用のクラスを定義するのが良い。というより、これしか方法がない。
ここからtouchコマンドの実装を一部公開していく。(公開っていうか、まぁ、全部見たかったら逆コンパイルでもして)。

  1. public class CommandOptions
  2. {
  3. public string timeStamp = "", timeStamp2 = "", r = "";
  4. public bool c = false, D = false, n = false, s = false, v = false, w=false,ig=false, eos = false;
  5. }
  6.  
  7. public class optionBinder : BinderBase<CommandOptions>
  8. {
  9. private readonly Option<string> _timeStampOption, _timeStampOption2, _readFileOption;
  10. private readonly Option<bool> _createArgument, _directroyArgument, _fileNodeOption, _subDirectoryOption, _viewMoreOption, _warningOption, _ignoreOption, _cultureOption;
  11.  
  12. public optionBinder(Option<string> timeStampOption, Option<string> timeStampOption2, Option<string> readFileOption, Option<bool> createArgument, Option<bool> directroyArgument, Option<bool> fileNodeOption, Option<bool> subDirectoryOption, Option<bool> viewMoreOption, Option<bool> warningOption, Option<bool> ignoreOption, Option<bool> cultureOption)
  13. {
  14. _timeStampOption = timeStampOption;
  15. _timeStampOption2 = timeStampOption2;
  16. _readFileOption = readFileOption;
  17. _createArgument = createArgument;
  18. _directroyArgument = directroyArgument;
  19. _fileNodeOption = fileNodeOption;
  20. _subDirectoryOption = subDirectoryOption;
  21. _viewMoreOption = viewMoreOption;
  22. _warningOption = warningOption;
  23. _ignoreOption = ignoreOption;
  24. _cultureOption = cultureOption;
  25. }
  26.  
  27. protected override CommandOptions GetBoundValue(BindingContext bindingContext) =>
  28. new CommandOptions
  29. {
  30. timeStamp = bindingContext.ParseResult.GetValueForOption(_timeStampOption),
  31. timeStamp2 = bindingContext.ParseResult.GetValueForOption(_timeStampOption2),
  32. r = bindingContext.ParseResult.GetValueForOption(_readFileOption),
  33. c = bindingContext.ParseResult.GetValueForOption(_createArgument),
  34. D = bindingContext.ParseResult.GetValueForOption(_directroyArgument),
  35. n = bindingContext.ParseResult.GetValueForOption(_fileNodeOption),
  36. s = bindingContext.ParseResult.GetValueForOption(_subDirectoryOption),
  37. v = bindingContext.ParseResult.GetValueForOption(_viewMoreOption),
  38. w = bindingContext.ParseResult.GetValueForOption(_warningOption),
  39. eos = bindingContext.ParseResult.GetValueForOption(_cultureOption),
  40. ig = bindingContext.ParseResult.GetValueForOption(_ignoreOption),
  41.  
  42. };
  43. }

まず、全てのオプション、引数を格納するためだけのクラス「CommandOptions」を定義する。その後、「BinderBase」から継承するクラス「optionBinder」を作成する。「optionBinder」のコンストラクタで、メンバー変数にアプリに渡ってきたオプションや引数を格納する。正直、きまったことを書くだけの作業となる。 最後に、GetBoundValueをオーバーライドして、CommandOptionsクラスを参照さえすれば、引数を受け取れる状態にする。
メイン処理では、このように実装する。

  1. var rootCommand = new RootCommand("Developed by https://it01.hooray-eri.com/");
  2. rootCommand.Add(createArgument);
  3. rootCommand.Add(directroyArgument);
  4. rootCommand.Add(timeStampOption);
  5. rootCommand.Add(timeStampOption2);
  6. rootCommand.Add(fileNodeOption);
  7. rootCommand.Add(readFileOption);
  8. rootCommand.Add(subDirectoryOption);
  9. rootCommand.Add(viewMoreOption);
  10. rootCommand.Add(warningOption);
  11. rootCommand.Add(ignoreOption);
  12. rootCommand.Add(cultureOption);
  13. rootCommand.Add(fileName);
  14.  
  15. CommandOptions CO = new CommandOptions();
  16.  
  17. rootCommand.SetHandler((CommandOptionsV, filePtnV) =>
  18. {
  19. CO = CommandOptionsV;
  20. fileNamePtn = filePtnV;
  21. },
  22. new optionBinder(timeStampOption,timeStampOption2,readFileOption,createArgument,directroyArgument,fileNodeOption,subDirectoryOption,viewMoreOption,warningOption,ignoreOption,cultureOption)
  23. ,fileName
  24. );

今回の例では、11個のオプションは別のクラスにして、1個の引数(fileName)はクラスを使うこと無く使用することにした。オプションは「CO」クラスメンバーを参照すれば、引数で渡ってきた値がわかるようになる、という仕掛けである。

  1. if (reg1.IsMatch(CO.timeStamp))
  2. {
  3. ・・・
  4. }

例えば、timeStampオプションであれば、上記のように使用することができるということ。

  • wiki/dokuwiki/cs/コンソールアプリの引数の扱い方.txt
  • 最終更新: 2024/11/04 00:47
  • by 127.0.0.1