共用方式為


教學課程:開始使用 System.CommandLine

重要

System.CommandLine 目前為預覽版,本文件適用於版本 2.0 搶鮮版 (Beta) 4。 部分資訊與發行前產品相關,發行前可能會大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

此教學課程說明如何建立使用 System.CommandLine 程式庫的 .NET 命令列應用程式。 首先,您會建立具有一個選項的簡單根命令。 然後,您將新增至該基底,建立更複雜的應用程式,其中包含多個子命令與每個命令的不同選項。

在本教學課程中,您會了解如何:

  • 建立命令、選項與引數。
  • 指定選項的預設值。
  • 將選項與引數指派給命令。
  • 以遞迴方式將選項指派給命令下的所有子命令。
  • 使用多個層級的巢狀子命令。
  • 建立命令與選項的別名。
  • 使用 stringstring[]intboolFileInfo 與列舉選項類型。
  • 將選項值繫結至命令處理常式程式碼。
  • 使用自訂程式碼剖析及驗證選項。

必要條件

Or

建立應用程式

建立名為 "scl" 的 .NET 6 主控台應用程式專案。

  1. 為專案建立名為 scl 的資料夾,然後在此新資料夾中開啟命令提示字元。

  2. 執行以下命令:

    dotnet new console --framework net6.0
    

安裝 System.CommandLine 套件

  • 執行以下命令:

    dotnet add package System.CommandLine --prerelease
    

    必須有 --prerelease 選項,因為程式庫仍為搶鮮版 (Beta)。

  1. 以下列程式碼取代 Program.cs 的內容:

    using System.CommandLine;
    
    namespace scl;
    
    class Program
    {
        static async Task<int> Main(string[] args)
        {
            var fileOption = new Option<FileInfo?>(
                name: "--file",
                description: "The file to read and display on the console.");
    
            var rootCommand = new RootCommand("Sample app for System.CommandLine");
            rootCommand.AddOption(fileOption);
    
            rootCommand.SetHandler((file) => 
                { 
                    ReadFile(file!); 
                },
                fileOption);
    
            return await rootCommand.InvokeAsync(args);
        }
    
        static void ReadFile(FileInfo file)
        {
            File.ReadLines(file.FullName).ToList()
                .ForEach(line => Console.WriteLine(line));
        }
    }
    

上述程式碼:

  • 建立名為 --file 之類型 FileInfo選項,並將其指派給根命令

    var fileOption = new Option<FileInfo?>(
        name: "--file",
        description: "The file to read and display on the console.");
    
    var rootCommand = new RootCommand("Sample app for System.CommandLine");
    rootCommand.AddOption(fileOption);
    
  • 指定 ReadFile 作為叫用根命令時要呼叫的方法:

    rootCommand.SetHandler((file) => 
        { 
            ReadFile(file!); 
        },
        fileOption);
    
  • 叫用根命令時,顯示指定檔案的內容:

    static void ReadFile(FileInfo file)
    {
        File.ReadLines(file.FullName).ToList()
            .ForEach(line => Console.WriteLine(line));
    }
    

測試應用程式

您可以在開發命令列應用程式時,使用下列任何一種方式進行測試:

  • 執行 dotnet build 命令,然後在 scl/bin/Debug/net6.0 資料夾中開啟命令提示字元,以執行可執行檔:

    dotnet build
    cd bin/Debug/net6.0
    scl --file scl.runtimeconfig.json
    
  • 使用 dotnet run,並藉由將選項值包含在 -- 之後,將其傳遞至應用程式,而不是傳遞至 run 命令,如下列範例所示:

    dotnet run -- --file scl.runtimeconfig.json
    

    在 .NET 7.0.100 SDK Preview 中,您可以執行 dotnet run --launch-profile <profilename> 命令來使用 launchSettings.json 檔案的 commandLineArgs

  • 將專案發佈至資料夾、開啟該資料夾的命令提示字元,然後執行可執行檔:

    dotnet publish -o publish
    cd ./publish
    scl --file scl.runtimeconfig.json
    
  • 在 Visual Studio 2022 中,從功能表依序選取 [偵錯] > [偵錯屬性],然後在 [命令列引數] 方塊中輸入選項與引數。 例如:

    Visual Studio 2022 中的命令列引數

    然後執行應用程式,例如按 Ctrl+F5。

此教學課程假設您正在使用這些選項的第一個。

當您執行應用程式時,其會顯示 --file 選項所指定的檔案內容。

{
  "runtimeOptions": {
    "tfm": "net6.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "6.0.0"
    }
  }
}

說明輸出

System.CommandLine 會自動提供說明輸出:

scl --help
Description:
  Sample app for System.CommandLine

Usage:
  scl [options]

Options:
  --file <file>   The file to read and display on the console.
  --version       Show version information
  -?, -h, --help  Show help and usage information

版本輸出

System.CommandLine 會自動提供版本輸出:

scl --version
1.0.0

新增子命令與選項

在本節中,您可:

  • 建立更多選項。
  • 建立子命令。
  • 將新選項指派給新的子命令。

新的選項可讓您設定前景與背景文字色彩,以及朗讀速度。 這些功能將用於讀取來自 Teleprompter 主控台應用程式教學課程的引號集合。

  1. 請從 GitHub 儲存機制將此範例的 sampleQuotes.txt 檔案複製到您的專案目錄中。 如需如何下載檔案的資訊,請參閱範例與教學課程中的指示。

  2. 開啟專案檔,並在結尾 </Project> 標籤之前新增 <ItemGroup> 元素:

    <ItemGroup>
      <Content Include="sampleQuotes.txt">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      </Content>
    </ItemGroup>
    

    新增此標記會導致您在建置應用程式時,將文字檔複製至 bin/debug/net6.0 資料夾。 因此,當您在該資料夾中執行可執行檔時,可以依名稱存取檔案,而不需要指定資料夾路徑。

  3. Program.cs 中,於建立 --file 選項的程式碼之後,建立可控制朗讀速度與文字色彩的選項:

    var delayOption = new Option<int>(
        name: "--delay",
        description: "Delay between lines, specified as milliseconds per character in a line.",
        getDefaultValue: () => 42);
    
    var fgcolorOption = new Option<ConsoleColor>(
        name: "--fgcolor",
        description: "Foreground color of text displayed on the console.",
        getDefaultValue: () => ConsoleColor.White);
    
    var lightModeOption = new Option<bool>(
        name: "--light-mode",
        description: "Background color of text displayed on the console: default is black, light mode is white.");
    
  4. 在建立根命令的行之後,刪除新增 --file 選項至其中的行。 因為您會將該選項新增至新的子命令,所以您正在此處移除該選項。

    var rootCommand = new RootCommand("Sample app for System.CommandLine");
    //rootCommand.AddOption(fileOption);
    
  5. 在建立根命令的行之後,建立 read 子命令。 將選項新增至此子命令,並將子命令新增至根命令。

    var readCommand = new Command("read", "Read and display the file.")
        {
            fileOption,
            delayOption,
            fgcolorOption,
            lightModeOption
        };
    rootCommand.AddCommand(readCommand);
    
  6. 以新子命令的下列 SetHandler 程式碼取代 SetHandler 程式碼:

    readCommand.SetHandler(async (file, delay, fgcolor, lightMode) =>
        {
            await ReadFile(file!, delay, fgcolor, lightMode);
        },
        fileOption, delayOption, fgcolorOption, lightModeOption);
    

    因為根命令不再需要處理常式,所以您不會再於根命令上呼叫 SetHandler。 當命令有子命令時,您通常必須在叫用命令列應用程式時指定其中一個子命令。

  7. 以下列程式碼取代 ReadFile 處理常式方法:

    internal static async Task ReadFile(
            FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
    {
        Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
        Console.ForegroundColor = fgColor;
        List<string> lines = File.ReadLines(file.FullName).ToList();
        foreach (string line in lines)
        {
            Console.WriteLine(line);
            await Task.Delay(delay * line.Length);
        };
    }
    

應用程式現在看起來像這樣:

using System.CommandLine;

namespace scl;

class Program
{
    static async Task<int> Main(string[] args)
    {
        var fileOption = new Option<FileInfo?>(
            name: "--file",
            description: "The file to read and display on the console.");

        var delayOption = new Option<int>(
            name: "--delay",
            description: "Delay between lines, specified as milliseconds per character in a line.",
            getDefaultValue: () => 42);

        var fgcolorOption = new Option<ConsoleColor>(
            name: "--fgcolor",
            description: "Foreground color of text displayed on the console.",
            getDefaultValue: () => ConsoleColor.White);

        var lightModeOption = new Option<bool>(
            name: "--light-mode",
            description: "Background color of text displayed on the console: default is black, light mode is white.");

        var rootCommand = new RootCommand("Sample app for System.CommandLine");
        //rootCommand.AddOption(fileOption);

        var readCommand = new Command("read", "Read and display the file.")
            {
                fileOption,
                delayOption,
                fgcolorOption,
                lightModeOption
            };
        rootCommand.AddCommand(readCommand);

        readCommand.SetHandler(async (file, delay, fgcolor, lightMode) =>
            {
                await ReadFile(file!, delay, fgcolor, lightMode);
            },
            fileOption, delayOption, fgcolorOption, lightModeOption);

        return rootCommand.InvokeAsync(args).Result;
    }

    internal static async Task ReadFile(
            FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
    {
        Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
        Console.ForegroundColor = fgColor;
        List<string> lines = File.ReadLines(file.FullName).ToList();
        foreach (string line in lines)
        {
            Console.WriteLine(line);
            await Task.Delay(delay * line.Length);
        };
    }
}

測試新的子命令

現在,若您嘗試執行應用程式而不指定子命令,您會收到錯誤訊息,後面接著用於指定可用子命令的說明訊息。

scl --file sampleQuotes.txt
'--file' was not matched. Did you mean one of the following?
--help
Required command was not provided.
Unrecognized command or argument '--file'.
Unrecognized command or argument 'sampleQuotes.txt'.

Description:
  Sample app for System.CommandLine

Usage:
  scl [command] [options]

Options:
  --version       Show version information
  -?, -h, --help  Show help and usage information

Commands:
  read  Read and display the file.

子命令 read 的說明文字會顯示有四個選項可供使用。 其會顯示列舉的有效值。

scl read -h
Description:
  Read and display the file.

Usage:
  scl read [options]

Options:
  --file <file>                                               The file to read and display on the console.
  --delay <delay>                                             Delay between lines, specified as milliseconds per
                                                              character in a line. [default: 42]
  --fgcolor                                                   Foreground color of text displayed on the console.
  <Black|Blue|Cyan|DarkBlue|DarkCyan|DarkGray|DarkGreen|Dark  [default: White]
  Magenta|DarkRed|DarkYellow|Gray|Green|Magenta|Red|White|Ye
  llow>
  --light-mode                                                Background color of text displayed on the console:
                                                              default is black, light mode is white.
  -?, -h, --help                                              Show help and usage information

執行僅指定 --file 選項的子命令 read,並取得其他三個選項的預設值。

scl read --file sampleQuotes.txt

每個字元預設延遲 42 毫秒會導致朗讀速度變慢。 您可以藉由將 --delay 設定為較低的數字來加快速度。

scl read --file sampleQuotes.txt --delay 0

您可以使用 --fgcolor--light-mode 來設定文字色彩:

scl read --file sampleQuotes.txt --fgcolor red --light-mode

--delay 提供無效值會收到錯誤訊息:

scl read --file sampleQuotes.txt --delay forty-two
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.

--file 提供無效值會得到例外狀況:

scl read --file nofile
Unhandled exception: System.IO.FileNotFoundException:
Could not find file 'C:\bin\Debug\net6.0\nofile'.

新增子命令與自訂驗證

本節會建立應用程式的最終版本。 完成之後,應用程式將會有下列命令與選項:

  • 具有名為 --file 之全域* 選項的根命令
    • quotes 命令
      • read 命令,其中包含名為 --delay--fgcolor--light-mode 的選項
      • add 命令,其中包含名為 quotebyline 的引數
      • 具有名為 --search-terms 選項的 delete 命令

* 全域選項可供其獲指派的命令使用,並以遞迴方式提供給其中所有的子命令。

以下是使用其選項與引數叫用每個可用命令的命令列輸入範例:

scl quotes read --file sampleQuotes.txt --delay 40 --fgcolor red --light-mode
scl quotes add "Hello world!" "Nancy Davolio"
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
  1. Program.cs 中,以下列程式碼取代用於建立 --file 選項的程式碼:

    var fileOption = new Option<FileInfo?>(
        name: "--file",
        description: "An option whose argument is parsed as a FileInfo",
        isDefault: true,
        parseArgument: result =>
        {
            if (result.Tokens.Count == 0)
            {
                return new FileInfo("sampleQuotes.txt");
    
            }
            string? filePath = result.Tokens.Single().Value;
            if (!File.Exists(filePath))
            {
                result.ErrorMessage = "File does not exist";
                return null;
            }
            else
            {
                return new FileInfo(filePath);
            }
        });
    

    此程式碼會使用 ParseArgument<T> 來提供自訂剖析、驗證與錯誤處理。

    若沒有此程式碼,則遺失的檔案會回報例外狀況與堆疊追蹤。 使用此程式碼時,只會顯示指定的錯誤訊息。

    此程式碼也會指定預設值,這就是其將 isDefault 設定為 true 的原因。 若您未將 isDefault 設定為 true,則未針對 --file 提供任何輸入時,不會呼叫 parseArgument 委派。

  2. 在建立 lightModeOption 的程式碼之後,新增 adddelete 命令的選項與引數:

    var searchTermsOption = new Option<string[]>(
        name: "--search-terms",
        description: "Strings to search for when deleting entries.")
        { IsRequired = true, AllowMultipleArgumentsPerToken = true };
    
    var quoteArgument = new Argument<string>(
        name: "quote",
        description: "Text of quote.");
    
    var bylineArgument = new Argument<string>(
        name: "byline",
        description: "Byline of quote.");
    

    AllowMultipleArgumentsPerToken 設定可讓您在第一個選項名稱後指定清單中的元素時,省略 --search-terms 選項名稱。 其會讓下列命令列輸入的範例相等:

    scl quotes delete --search-terms David "You can do"
    scl quotes delete --search-terms David --search-terms "You can do"
    
  3. 以下列程式碼取代用於建立根命令與 read 命令的程式碼:

    var rootCommand = new RootCommand("Sample app for System.CommandLine");
    rootCommand.AddGlobalOption(fileOption);
    
    var quotesCommand = new Command("quotes", "Work with a file that contains quotes.");
    rootCommand.AddCommand(quotesCommand);
    
    var readCommand = new Command("read", "Read and display the file.")
        {
            delayOption,
            fgcolorOption,
            lightModeOption
        };
    quotesCommand.AddCommand(readCommand);
    
    var deleteCommand = new Command("delete", "Delete lines from the file.");
    deleteCommand.AddOption(searchTermsOption);
    quotesCommand.AddCommand(deleteCommand);
    
    var addCommand = new Command("add", "Add an entry to the file.");
    addCommand.AddArgument(quoteArgument);
    addCommand.AddArgument(bylineArgument);
    addCommand.AddAlias("insert");
    quotesCommand.AddCommand(addCommand);
    

    此程式碼會進行下列變更:

    • read 命令中移除 --file 選項。

    • --file 選項新增為根命令的全域選項。

    • 建立 quotes 命令,並將其新增至根命令。

    • read 命令新增至 quotes 命令,而不是新增至根命令。

    • 建立 adddelete 命令,並將其新增至 quotes 命令。

    結果是下列命令階層:

    • 根命令
      • quotes
        • read
        • add
        • delete

    應用程式現在會實作建議的模式,其中父命令 (quotes) 會指定區域或群組,以及其子命令 (readadddelete) 是動作。

    會將全域選項套用至命令,並以遞迴方式套用至子命令。 由於 --file 位於根命令上,因此其會自動用於應用程式的所有子命令中。

  4. SetHandler 程式碼之後,為新的子命令新增 SetHandler 程式碼:

    deleteCommand.SetHandler((file, searchTerms) =>
        {
            DeleteFromFile(file!, searchTerms);
        },
        fileOption, searchTermsOption);
    
    addCommand.SetHandler((file, quote, byline) =>
        {
            AddToFile(file!, quote, byline);
        },
        fileOption, quoteArgument, bylineArgument);
    

    因為子命令 quotes 不是分葉命令,所以沒有處理常式。 子命令 readadddeletequotes 下的分葉命令,且 SetHandler 會針對每個命令呼叫。

  5. 針對 adddelete 新增處理常式。

    internal static void DeleteFromFile(FileInfo file, string[] searchTerms)
    {
        Console.WriteLine("Deleting from file");
        File.WriteAllLines(
            file.FullName, File.ReadLines(file.FullName)
                .Where(line => searchTerms.All(s => !line.Contains(s))).ToList());
    }
    internal static void AddToFile(FileInfo file, string quote, string byline)
    {
        Console.WriteLine("Adding to file");
        using StreamWriter? writer = file.AppendText();
        writer.WriteLine($"{Environment.NewLine}{Environment.NewLine}{quote}");
        writer.WriteLine($"{Environment.NewLine}-{byline}");
        writer.Flush();
    }
    

完成的應用程式看起來像這樣:

using System.CommandLine;

namespace scl;

class Program
{
    static async Task<int> Main(string[] args)
    {
        var fileOption = new Option<FileInfo?>(
            name: "--file",
            description: "An option whose argument is parsed as a FileInfo",
            isDefault: true,
            parseArgument: result =>
            {
                if (result.Tokens.Count == 0)
                {
                    return new FileInfo("sampleQuotes.txt");

                }
                string? filePath = result.Tokens.Single().Value;
                if (!File.Exists(filePath))
                {
                    result.ErrorMessage = "File does not exist";
                    return null;
                }
                else
                {
                    return new FileInfo(filePath);
                }
            });

        var delayOption = new Option<int>(
            name: "--delay",
            description: "Delay between lines, specified as milliseconds per character in a line.",
            getDefaultValue: () => 42);

        var fgcolorOption = new Option<ConsoleColor>(
            name: "--fgcolor",
            description: "Foreground color of text displayed on the console.",
            getDefaultValue: () => ConsoleColor.White);

        var lightModeOption = new Option<bool>(
            name: "--light-mode",
            description: "Background color of text displayed on the console: default is black, light mode is white.");

        var searchTermsOption = new Option<string[]>(
            name: "--search-terms",
            description: "Strings to search for when deleting entries.")
            { IsRequired = true, AllowMultipleArgumentsPerToken = true };

        var quoteArgument = new Argument<string>(
            name: "quote",
            description: "Text of quote.");

        var bylineArgument = new Argument<string>(
            name: "byline",
            description: "Byline of quote.");

        var rootCommand = new RootCommand("Sample app for System.CommandLine");
        rootCommand.AddGlobalOption(fileOption);

        var quotesCommand = new Command("quotes", "Work with a file that contains quotes.");
        rootCommand.AddCommand(quotesCommand);

        var readCommand = new Command("read", "Read and display the file.")
            {
                delayOption,
                fgcolorOption,
                lightModeOption
            };
        quotesCommand.AddCommand(readCommand);

        var deleteCommand = new Command("delete", "Delete lines from the file.");
        deleteCommand.AddOption(searchTermsOption);
        quotesCommand.AddCommand(deleteCommand);

        var addCommand = new Command("add", "Add an entry to the file.");
        addCommand.AddArgument(quoteArgument);
        addCommand.AddArgument(bylineArgument);
        addCommand.AddAlias("insert");
        quotesCommand.AddCommand(addCommand);

        readCommand.SetHandler(async (file, delay, fgcolor, lightMode) =>
            {
                await ReadFile(file!, delay, fgcolor, lightMode);
            },
            fileOption, delayOption, fgcolorOption, lightModeOption);

        deleteCommand.SetHandler((file, searchTerms) =>
            {
                DeleteFromFile(file!, searchTerms);
            },
            fileOption, searchTermsOption);

        addCommand.SetHandler((file, quote, byline) =>
            {
                AddToFile(file!, quote, byline);
            },
            fileOption, quoteArgument, bylineArgument);

        return await rootCommand.InvokeAsync(args);
    }

    internal static async Task ReadFile(
                FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
    {
        Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
        Console.ForegroundColor = fgColor;
        var lines = File.ReadLines(file.FullName).ToList();
        foreach (string line in lines)
        {
            Console.WriteLine(line);
            await Task.Delay(delay * line.Length);
        };

    }
    internal static void DeleteFromFile(FileInfo file, string[] searchTerms)
    {
        Console.WriteLine("Deleting from file");
        File.WriteAllLines(
            file.FullName, File.ReadLines(file.FullName)
                .Where(line => searchTerms.All(s => !line.Contains(s))).ToList());
    }
    internal static void AddToFile(FileInfo file, string quote, string byline)
    {
        Console.WriteLine("Adding to file");
        using StreamWriter? writer = file.AppendText();
        writer.WriteLine($"{Environment.NewLine}{Environment.NewLine}{quote}");
        writer.WriteLine($"{Environment.NewLine}-{byline}");
        writer.Flush();
    }
}

建置專案,然後嘗試下列命令。

使用 read 命令將不存在的檔案提交至 --file,您會收到錯誤訊息,而不是例外狀況與堆疊追蹤:

scl quotes read --file nofile
File does not exist

嘗試執行子命令 quotes,您會收到指示您使用 readadddelete 的訊息:

scl quotes
Required command was not provided.

Description:
  Work with a file that contains quotes.

Usage:
  scl quotes [command] [options]

Options:
  --file <file>   An option whose argument is parsed as a FileInfo [default: sampleQuotes.txt]
  -?, -h, --help  Show help and usage information

Commands:
  read                          Read and display the file.
  delete                        Delete lines from the file.
  add, insert <quote> <byline>  Add an entry to the file.

執行子命令 add,然後查看文字檔的結尾,以查看新增的文字:

scl quotes add "Hello world!" "Nancy Davolio"

使用檔案開頭的搜尋字串執行子命令 delete,然後查看文字檔的開頭,以查看已移除文字的位置:

scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"

注意

若您是在 bin/debug/net6.0 資料夾中執行,則該資料夾會是您從 adddelete 命令找到含有變更的檔案位置。 專案資料夾中的檔案複本會保持不變。

下一步

在此教學課程中,您已建立使用 System.CommandLine 的簡單命令列應用程式。 若要深入了解程式庫,請參閱 System.CommandLine 概觀