tech.guitarrapc.cóm

Technical updates

ラッパーコマンドをConsoleAppFramework v4からv5へ移行できないポイント

ConsoleAppFrameworkはv4からv5で仕様が大きく変更されました。その中にはやりにくなったことやできなくなったこともありますが多くは改善されました。v5リリース後の改善状況は作者の記事いくつかの記事で紹介されているのでそちらをどうぞ。

私も手元のバッチをいくつかv4からv5に移行しましたが、その1つで破壊的な変更なく移行できず、またその破壊的変更が一般的なコマンド規則からずれるので困っているものがあります。今回はそれをメモします。

2025/02/06追記

ConsoleAppFramework 5.4.0で--に対応入ったので補足記事を書きました。

バッチの中で別のコマンドを呼び出す

あるコマンドAを内包するコマンドラインアプリケーション1で、コマンドAに渡す引数はコマンドラインアプリケーションの引数と区別したくなります。つまりこういう構造ですね。

バッチA 引数1 引数2
  コマンドA 引数

どのように渡すことが多いのか考えてみましょう。

--を使って引数を分ける

あるコマンドAを内包するコマンドラインアプリケーションで、バッチAの引数とコマンドAに渡す引数を区別するのに--を用いて分けることが多く、一般的に期待される挙動と認識しています。

$ バッチA --引数1--引数2-- コマンドAの引数

例えばdotnetコマンドはdotnet runというサブコマンドにおいて、中のcsprojに渡す引数と区別するためdotnet run -- argsに渡す引数という形式で渡します。このような形式は、.NETエンジニアにも馴染み深いのではないでしょうか。

コマンドパラメーター外の引数をそのまま取得して渡す

よく見かけるもう1つの方法は、バッチAで定義していない引数をそのまま中のコマンドAに渡すという方法です。

$ バッチA --引数1--引数2 値 バッチAで定義していない引数

これはこれで使いやすいものの、バッチAの引数とコマンドAの引数が混ざってしまうので、バッチAの引数とコマンドAの引数を区別するために--が好ましいのだろうと察するところです。

ラップするコマンドの引数を提供する

利用方法を割り切ったラッパーなら、ラップするコマンドの引数をそのまま提供することもあります。

$ バッチA --引数1--コマンドAの引数1--コマンドAの引数2

コマンドAの引数をどこまで網羅するのかという難しさはありますが、基本的にコマンドのデフォルトを使いつつ、一部だけ変更する場合に使われることが多いです。

ConsoleAppFramework v4からv5への移行できないポイント

以下のコードは、Gistにサンプルを置いておきます。

https://gist.github.com/guitarrapc/a3f724d4c00ac402cd6109ebe69f77d9

ConsoleAppFrameworkで上記のようなコマンドAを内包するバッチを作る場合、v4では「コマンドパラメーター外の引数をそのまま取得して渡す」が利用できました。例えば次のように書くことができます。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ConsoleAppFramework" Version="4.2.4" />
  </ItemGroup>
</Project>
var app = ConsoleApp.CreateBuilder(args).Build();
app.AddRootCommand((string msg, ConsoleAppContext context) => Console.WriteLine(string.Join(" ", context.Arguments)));
await app.RunAsync();

バッチパラメーターに定義していない--helloをコマンド内で取得できるのがわかります。この仕組みを利用すれば、バッチパラメーターとコマンドパラメーターを区別して渡すことができます。

// # OK
// # Output: --msg foo
args = ["--msg", "foo"];

// # OK
// # Output: --msg foo --hello bar
args = ["--msg", "foo", "--hello", "bar"];

ConsoleAppFramework v5での問題

ConsoleAppFramework v5ではバッチのパラメーターで定義されていない引数は渡せないため、params string[] argumentsのようにparamsを使って残りの引数を受けるするしかなさそうです。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ConsoleAppFramework" Version="5.3.4">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>
var app = ConsoleApp.Create();
app.Add("", (string msg, params string[] arguments) => Console.WriteLine(msg + string.Join(" ", arguments)));
await app.RunAsync(args);

実行してみましょう。

// # OK
// # Output: foo
args = ["--msg", "foo"];

// # NG
// # Output: Argument '--hello' is not recognized.
args = ["--msg", "foo", "--hello", "bar"];

// # OK
// # Output: --msg foo --arguments --hello bar
args = ["--msg", "foo", "--arguments", "--hello", "bar"];

ただ、--argumentなどといわれても違和感が強いですね。もしこのようにバッチ外の引数を渡すなら--を使うのが利用者からみても一般的な使い方を提供できそうです。

まとめ

というのをポストしていたら、ConsoleAppFrameworkのIssueにちょうど同じ要望が上がっていたようです。対応されるとv4で書いていたラッパーコマンドもv5で書けるようになるので、楽しみです。2

[Feature Request] Add supports for double-dash arguments syntax · Issue #161 · Cysharp/ConsoleAppFramework


  1. いわゆるラッパーコマンド
  2. --が必須になるので破壊的変更になるが、望ましい変更と捉えられる範囲かと