Search Results for

    Show / Hide Table of Contents

    Arguments

    When a command executes, the raw string[] args value can be separated into two different categories: options and arguments.

    Arguments are positional and values are specified based by order.

    Options are named and must be specified using a name. Options are covered in this document.

    Arguments are represented by the CommandArgument type. They have one defining characteristic.

    • Position - the order in which arguments appear on command line (after options have been parsed)
    • Using Attributes
    • Using Builder API

    ArgumentAttribute can be used on properties to define arguments. The argument order must be specified explicitly.

    public class Program
    {
        [Argument(0)]
        [Required]
        public string FirstName { get; }
    
        [Argument(1)]
        public string LastName { get; }  // this one is optional because it doesn't have `[Required]`
    
        private void OnExecute()
        {
            Console.WriteLine($"Hello {FirstName} {LastName}");
        }
    
        public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
    }
    
    public class Program
    {
        public static int Main(string[] args)
        {
            var app = new CommandLineApplication();
    
            var firstNameArg = app.Argument("first name", "the first name of the person")
                .IsRequired();
            var lastNameArg = app.Argument("last name", "the last name of the person");
    
            app.OnExecute(() =>
            {
                Console.WriteLine($"Hello {firstNameArg.Value} {lastNameArg.Value}");
            });
    
            return app.Execute(args);
        }
    }
    

    Variable numbers of arguments

    A common scenario is to allow a command line tool to take in a variable number of arguments. These arguments are collected into a string[] array after all other arguments and options are parsed.

    cat -b 123 file1.txt file2.txt file3.txt
    
    • Using Attributes
    • Using Builder API

    By default, attribute binding will assume multiple values can be set for properties with the [Argument] attribute and settable to string[] or IEnumerable<string>.

    public class Program
    {
        [Option("-b")]
        public int BlankLines { get; }
    
        [Argument(0)]
        public string[] Files { get; } // = { "file1.txt", "file2.txt", "file3.txt" }
    
        private void OnExecute()
        {
            if (Files != null)
            {
                foreach (var file in Files)
                {
                    // do something
                }
            }
        }
    
        public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
    }
    

    To enable this, MultipleValues must be set to true, and the argument must be the last one specified.

    public class Program
    {
        public static int Main(string[] args)
        {
            var app = new CommandLineApplication();
            var blankLines = app.Option<int>("-b <LINES>", "Blank lines", CommandOptionType.SingleValue);
            var files = app.Argument("Files", "Files to count", multipleValues: true);
            app.OnExecute(() =>
            {
                foreach (var file in files.Values)
                {
                    // do something
                }
            });
            return app.Execute(args);
        }
    }
    

    Pass-thru arguments

    Another common scenario is to create a command line tool which wraps another tool. These kinds of command lines need to collect arguments which are passed to the to the tool they wrap. For example, the Unix command time or the Windows command cmd take some arguments, and pass the rest on to the command they invoke.

    Note

    Example:

    time -l ls -a -l ./
    

    In this example, -l is an option on time. This starts a timer which then invokes ls with additional arguments. -l is also an option on ls.

    Normally, unrecognized arguments is an error. You must set UnrecognizedArgumentHandling to StopParsingAndCollect to allow the parser to collect unrecognized arguments and options.

    The double-dash convention --

    It is common for apps which pass-thru arguments to allow the caller to use -- to distinguish between the options on the parent command and all remaining arguments.

    bash -c -- ls -a -l
    

    In this example, the presence of -- forces bash to stop parsing and treat everything after -- as an argument to be passed to the inner command.

    The double dash command is enabled by setting AllowArgumentSeparator.

    • Using Attributes
    • Using Builder API

    By default, attribute binding will set a string[] or IList<string> property named RemainingArguments or RemainingArgs to include all values. See RemainingArgsPropertyConvention for more details.

    using System;
    using System.Diagnostics;
    using System.Linq;
    using McMaster.Extensions.CommandLineUtils;
    
    [Command(
        UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.StopParsingAndCollect,
        AllowArgumentSeparator = true)]
    public class Program
    {
        public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
    
        [Option("-m", Description = "Show time in milliseconds")]
        public bool Milliseconds { get; }
    
        public string[] RemainingArguments { get; } // = { "ls", "-a", "-l" }
    
        private void OnExecute()
        {
            var timer = Stopwatch.StartNew();
            if (RemainingArguments != null && RemainingArguments.Length > 0)
            {
                var process = new Process
                {
                    StartInfo =
                    {
                        FileName = RemainingArguments[0],
                        Arguments = ArgumentEscaper.EscapeAndConcatenate(RemainingArguments.Skip(1)),
                    }
                };
                process.Start();
                process.WaitForExit();
            }
    
            timer.Stop();
    
            if (Milliseconds)
            {
                Console.WriteLine($"Time = {timer.Elapsed.TotalMilliseconds} ms");
            }
            else
            {
                Console.WriteLine($"Time = {timer.Elapsed.TotalSeconds}s");
            }
        }
    }
    

    When UnrecognizedArgumentHandling is set to Stop,

    using System;
    using System.Diagnostics;
    using System.Linq;
    using McMaster.Extensions.CommandLineUtils;
    
    public class Program
    {
        public static int Main(string[] args)
        {
            var app = new CommandLineApplication
            {
                AllowArgumentSeparator = true,
                UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.StopParsingAndCollect,
            };
    
            var showMilliseconds = app.Option<int>("-m", "Show time in milliseconds", CommandOptionType.NoValue);
    
            app.OnExecute(() =>
            {
                var timer = Stopwatch.StartNew();
                if (app.RemainingArguments != null && app.RemainingArguments.Count > 0)
                {
                    var process = new Process
                    {
                        StartInfo =
                    {
                        FileName = app.RemainingArguments[0],
                        Arguments = ArgumentEscaper.EscapeAndConcatenate(app.RemainingArguments.Skip(1)),
                    }
                    };
                    process.Start();
                    process.WaitForExit();
                }
    
                timer.Stop();
    
                if (showMilliseconds.HasValue())
                {
                    Console.WriteLine($"Time = {timer.Elapsed.TotalMilliseconds} ms");
                }
                else
                {
                    Console.WriteLine($"Time = {timer.Elapsed.TotalSeconds}s");
                }
            });
    
            return app.Execute(args);
        }
    }
    
    • Improve this Doc
    In This Article
    • Variable numbers of arguments
    • Pass-thru arguments
      • The double-dash convention --
    Back to top