Поделиться через


Синтаксический анализ и вызов System.CommandLine

Это важно

System.CommandLine в настоящее время находится в предварительной версии, и эта документация предназначена для версии 2.0 бета-версии 5. Некоторые сведения относятся к предварительному выпуску продукта, который может быть существенно изменен до его выпуска. Корпорация Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых в отношении информации, предоставленной здесь.

System.CommandLine обеспечивает четкое разделение между синтаксическим анализом командной строки и вызовом действий. Процесс синтаксического анализа отвечает за синтаксический анализ входных данных командной строки и создание System.CommandLine.ParseResult объекта, содержащего проанализированные значения (и ошибки синтаксического анализа). Процесс вызова действия отвечает за вызов действия, связанного с проанализированной командой, параметром или директивой (аргументы не могут иметь действий).

В следующем примере из нашего руководства как начать System.CommandLine, ParseResult создаётся путём разбора командной строки. Никакие действия не определены или вызваны:

using System.CommandLine;
using System.CommandLine.Parsing;

namespace scl;

class Program
{
    static int Main(string[] args)
    {
        Option<FileInfo> fileOption = new("--file")
        {
            Description = "The file to read and display on the console."
        };

        RootCommand rootCommand = new("Sample app for System.CommandLine");
        rootCommand.Options.Add(fileOption);

        ParseResult parseResult = rootCommand.Parse(args);
        if (parseResult.GetValue(fileOption) is FileInfo parsedFile)
        {
            ReadFile(parsedFile);
            return 0;
        }
        foreach (ParseError parseError in parseResult.Errors)
        {
            Console.Error.WriteLine(parseError.Message);
        }
        return 1;
    }

    static void ReadFile(FileInfo file)
    {
        foreach (string line in File.ReadLines(file.FullName))
        {
            Console.WriteLine(line);
        }
    }
}

Действие вызывается при успешном анализе заданной команды (или директивы или параметра). Действие — это делегат, который принимает System.CommandLine.ParseResult параметр и возвращает int код выхода (асинхронные действия также доступны)). Код выхода возвращается методом System.CommandLine.Parsing.ParseResult.Invoke и может использоваться для указания того, выполнена ли команда успешно или нет.

В следующем примере из нашего руководства по началу работы System.CommandLine действие определяется для корневой команды и вызывается после разбора входных данных командной строки.

using System.CommandLine;

namespace scl;

class Program
{
    static int Main(string[] args)
    {
        Option<FileInfo> fileOption = new("--file")
        {
            Description = "The file to read and display on the console."
        };

        RootCommand rootCommand = new("Sample app for System.CommandLine");
        rootCommand.Options.Add(fileOption);

        rootCommand.SetAction(parseResult =>
        {
            FileInfo parsedFile = parseResult.GetValue(fileOption);
            ReadFile(parsedFile);
            return 0;
        });

        ParseResult parseResult = rootCommand.Parse(args);
        return parseResult.Invoke();
    }

    static void ReadFile(FileInfo file)
    {
        foreach (string line in File.ReadLines(file.FullName))
        {
            Console.WriteLine(line);
        }
    }
}

Некоторые встроенные символы, такие как System.CommandLine.Help.HelpOption, System.CommandLine.VersionOptionили System.CommandLine.Completions.SuggestDirective, приходят с предопределенными действиями. Эти символы автоматически добавляются в корневую команду при ее создании, и когда вы вызываете System.CommandLine.Parsing.ParseResult, они "просто работают". Использование действий позволяет сосредоточиться на логике приложения, а библиотека заботится о синтаксическом анализе и вызове действий для символов, встроенных в команду. Если вы предпочитаете, вы можете придерживаться процесса синтаксического анализа и не определять какие-либо действия (как в первом примере выше).

РезультатПарсинга

Тип System.CommandLine.Parsing.ParseResult — это класс, представляющий результаты анализа входных данных командной строки. Его необходимо использовать для получения проанализированных значений для параметров и аргументов (независимо от того, используется ли действие или нет). Вы также можете проверить наличие ошибок синтаксического анализа или несовпаденных маркеров.

GetValue

Метод System.CommandLine.Parsing.ParseResult.GetValue<T> позволяет получить значения параметров и аргументов:

int integer = parseResult.GetValue(delayOption);
string? message = parseResult.GetValue(messageOption);

Кроме того, можно получить значения по имени, но для этого необходимо указать тип нужного значения.

В следующем примере используются инициализаторы коллекции C# для создания корневой команды:

RootCommand rootCommand = new("Parameter binding example")
{
    new Option<int>("--delay")
    {
        Description = "An option whose argument is parsed as an int."
    },
    new Option<string>("--message")
    {
        Description = "An option whose argument is parsed as a string."
    }
};

Затем он использует метод GetValue для получения значений по имени.

rootCommand.SetAction(parseResult =>
{
    int integer = parseResult.GetValue<int>("--delay");
    string? message = parseResult.GetValue<string>("--message");

    DisplayIntAndString(integer, message);
});

Этот вариант перегрузки GetValue возвращает значение после синтаксического анализа или значение по умолчанию для указанного имени символа в контексте команды после синтаксического анализа (не всего дерева символов). Он принимает имя символа, а не псевдоним.

Ошибки синтаксического анализа

Свойство System.CommandLine.Parsing.ParseResult.Errors содержит список ошибок синтаксического анализа, которые произошли во время процесса синтаксического анализа. Каждая ошибка представлена System.CommandLine.Parsing.ParseError объектом, который содержит сведения об ошибке, например сообщение об ошибке и маркер, вызвавшего ошибку.

При вызове System.CommandLine.Parsing.ParseResult.Invoke метода возвращается код выхода, указывающий, был ли синтаксический анализ успешным или нет. Если были ошибки синтаксического анализа, код выхода не равен нулю, и все ошибки синтаксического анализа печатаются в стандартную ошибку.

Если вы не вызываете метод System.CommandLine.Parsing.ParseResult.Invoke, необходимо самостоятельно обрабатывать ошибки, например, выводя их на экран.

foreach (ParseError parseError in parseResult.Errors)
{
    Console.Error.WriteLine(parseError.Message);
}
return 1;

Несопоставленные токены

Свойство System.CommandLine.Parsing.ParseResult.UnmatchedTokens содержит список маркеров, которые были проанализированы, но не соответствовали какой-либо настроенной команде, параметру или аргументу.

Список несовпадающих токенов полезен в командах, которые ведут себя как обёртки. Команда оболочки принимает набор маркеров и перенаправит их в другую команду или приложение. Команда sudo в Linux является примером. Он использует имя пользователя для олицетворения и запускает команду. Рассмотрим пример.

sudo -u admin apt update

Эта командная строка будет выполнять apt update команду от имени пользователя admin.

Чтобы реализовать команду-оболочку, например эту, задайте для свойства System.CommandLine.Command.TreatUnmatchedTokensAsErrors команды значение false. System.CommandLine.Parsing.ParseResult.UnmatchedTokens Затем свойство будет содержать все аргументы, которые явно не принадлежат команде. В предыдущем примере ParseResult.UnmatchedTokens будет содержать apt и update маркеры.

Действия

Действия — это делегаты, которые вызываются при успешном анализе команды (или параметра или директивы). Они принимают System.CommandLine.ParseResult параметр и возвращают int код выхода (или Task<int>). Код выхода используется для указания того, выполнено ли действие успешно или нет.

System.CommandLine предоставляет абстрактный базовый класс System.CommandLine.CommandLineAction и два производных класса: System.CommandLine.SynchronousCommandLineAction и System.CommandLine.AsynchronousCommandLineAction. Первый используется для синхронных действий, возвращающих int код выхода, а последний используется для асинхронных действий, возвращающих Task<int> код выхода.

Для определения действия не требуется создать производный тип. Вы можете использовать метод System.CommandLine.Command.SetAction, чтобы задать действие для команды. Синхронное действие может быть делегатом, который принимает System.CommandLine.ParseResult параметр и возвращает int код выхода. Асинхронное действие может быть делегатом, принимающим параметры System.CommandLine.ParseResult и CancellationToken и возвращающим Task<int>.

rootCommand.SetAction(parseResult =>
{
    FileInfo parsedFile = parseResult.GetValue(fileOption);
    ReadFile(parsedFile);
    return 0;
});

Асинхронные действия

Синхронные и асинхронные действия не должны быть смешанными в одном приложении. Если вы хотите использовать асинхронные действия, приложение должно быть асинхронным с верхней до нижней части. Это означает, что все действия должны быть асинхронными, и следует использовать System.CommandLine.Command.SetAction метод, который принимает делегат, возвращающий Task<int> код выхода. Более того, CancellationToken, переданный делегату действия, необходимо также передать всем методам, которые могут быть отменены, таким как операции ввода-вывода файлов или сетевые запросы.

Помимо этого, необходимо убедиться, что System.CommandLine.Parsing.ParseResult.InvokeAsync метод используется вместо System.CommandLine.Parsing.ParseResult.Invoke. Этот метод является асинхронным и возвращает Task<int> код выхода. Он также принимает необязательный CancellationToken параметр, который можно использовать для отмены действия.

В приведенном выше коде используется перегрузка SetAction, которая получает ParseResult и CancellationToken вместо того, чтобы просто ParseResult:

static Task<int> Main(string[] args)
{
    Option<string> urlOption = new("--url", "A URL.");
    RootCommand rootCommand = new("Handle termination example") { urlOption };

    rootCommand.SetAction((ParseResult parseResult, CancellationToken cancellationToken) =>
    {
        string? urlOptionValue = parseResult.GetValue(urlOption);
        return DoRootCommand(urlOptionValue, cancellationToken);
    });

    return rootCommand.Parse(args).InvokeAsync();
}

public static async Task<int> DoRootCommand(
    string? urlOptionValue, CancellationToken cancellationToken)
{
    using HttpClient httpClient = new();

    try
    {
        await httpClient.GetAsync(urlOptionValue, cancellationToken);
        return 0;
    }
    catch (OperationCanceledException)
    {
        await Console.Error.WriteLineAsync("The operation was aborted");
        return 1;
    }
}

Время ожидания завершения процесса

System.CommandLine.CommandLineConfiguration.ProcessTerminationTimeout обеспечивает возможность сигнализировать и обрабатывать завершение процесса (CTRL+CSIGINT, SIGTERM) с помощью CancellationToken, передаваемого каждому асинхронному действию во время вызова. Он включен по умолчанию (2 секунды), но его можно отключить, установив на null.

Если включено, и действие не завершено в течение указанного времени ожидания, процесс будет завершен. Это полезно для корректного завершения процесса, например, путем сохранения состояния перед его остановкой.

Чтобы проверить пример кода из предыдущего абзаца, выполните команду с URL-адресом, который займет некоторое время для загрузки, и до завершения загрузки нажмите клавиши CTRL+C. В macOS нажмите клавиши Command+Period(.). Рассмотрим пример.

testapp --url https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis
The operation was aborted

Коды выхода

Код выхода — это целочисленное значение, возвращаемое действием, указывающим на его успешность или сбой. По соглашению код 0 выхода означает успешность, а любое ненулевое значение указывает на ошибку. Важно определить значимые коды выхода в приложении для четкого обмена данными о состоянии выполнения команды.

Каждый SetAction метод имеет перегрузку, которая принимает делегат, возвращающий int код выхода, где код выхода должен быть предоставлен явно, и также перегрузку, которая возвращает 0.

static int Main(string[] args)
{
    Option<int> delayOption = new("--delay");
    Option<string> messageOption = new("--message");

    RootCommand rootCommand = new("Parameter binding example")
    {
        delayOption,
        messageOption
    };

    rootCommand.SetAction(parseResult =>
    {
        Console.WriteLine($"--delay = {parseResult.GetValue(delayOption)}");
        Console.WriteLine($"--message = {parseResult.GetValue(messageOption)}");
        // Value returned from the action delegate is the exit code.
        return 100;
    });

    return rootCommand.Parse(args).Invoke();
}

См. также

Как настроить синтаксический анализ и проверку в System.CommandLineSystem.CommandLine Обзор функций