Login


A C# Command-Line Parser

By Jonathan Wood on 3/22/2011
Language: C#
Technology: .NETWinForms
Platform: Windows
License: CPOL
Views: 16,763
General Programming » Text Handling » Parsing » A C# Command-Line Parser

Screenshot of Demo Project

Download Source Code Download Source Code

Introduction

These days, most computer users don't think too much about command-line arguments. At least, not compared to years ago when users ran programs by typing the program name at the prompt.

But command-line arguments still play an important role. In Windows, when you drag a file and drop it on an application's icon, Windows will launch that application and set the command line to include the name of the file that was dropped. The application can then examine the command line and open any file names it finds.

In addition, you can specify command-line arguments in a Windows shortcut in order to customize the default behavior of an application that supports command-line arguments. While most Windows applications allow you to set options within the application, it can be useful to set options before the application has started in order to control behavior during startup.

In .NET desktop applications, you can use the Environment.CommandLine property to access the command line of your application. However, if your application supports a variety of arguments and flags, then you need to parse them out. .NET also provides the Environment.GetCommandLineArgs() method. This method will separate the command line into an array of strings. However, it is somewhat limited. It basically breaks the command line into tokens delimited by whitespace. It also supports double quotes to allow tokens that include whitespace as part of the token.

So I decided to write a command-line parser that was a little more powerful.

A Custom Command-Line Parser

My custom command-line parser takes a command line (or any other string, for that matter), and parses it into separate arguments. The code supports two types of arguments: regular arguments and flags.

A regular argument is any text that is delimited by whitespace or enclosed in quotes (either double or single quotes). For example, if a filename was specified, it would normally be a regular argument. If the filename included spaces then it would need to be wrapped in quotes.

A flag (or switch) argument specifies an option setting. For example, an application might accept an argument like /offline or /o to specify that the program should run in an offline mode. A flag is indicated by an argument that starts with a forward slash (/) or hyphen (-). For simplicity, note that my code requires flags to contain only letters or digits.

In addition, my code also allows flags to have arguments. For example, let's say your application accepts the /log flag to specify that information should be written to a log file. You might also want to allow the command line to specify the name of the log file. My code supports the syntax /log:filename.ext, which signifies a flag with the value log and a flag argument with the value filename.ext. So a colon (:) immediately after a flag indicates the text that follows is an argument for that flag. (Note that there can be no spaces before or after the colon.)

My code also support quotes around arguments for flags, although quotes around the flags themselves is not supported.

My CommandLine Class

Listing 1 shows my CommandLine class. The constructor takes a string argument, which would normally be Environment.CommandLine, and then parses it according to the rules described above.

As the constructor parses the command line, it populates two public lists, Arguments and Flags. Arguments is a list of strings that represent regular command-line arguments. And Flags is a list of CommandLineFlag objects, which describe any flag arguments along with any arguments for those flags.

Listing 1: The CommandLine Class

/// <summary>
/// Represents a command-line flag argument
/// </summary>
public class CommandLineFlag
{
    public string Flag { get; set; }
    public string Argument { get; set; }
}

/// <summary>
/// Command line parser class
/// </summary>
class CommandLine
{
    // List of command-line arguments
    public List<string> Arguments { get; set; }

    // List of command-line flags
    public List<CommandLineFlag> Flags { get; set; }

    // Parsing state variables
    string _cmd;
    int _pos;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="commandLine">Command line to parse</param>
    public CommandLine(string commandLine)
    {
        // Initialize parser for given command line
        BeginParsing(commandLine);

        // Loop until all characters processed
        while (!EndOfText)
        {
            // Skip whitespace
            while (Char.IsWhiteSpace(Peek()))
                MoveAhead();

            // Determine if this is a flag
            if (Peek() == '/' || Peek() == '-')
            {
                // Parse flag
                MoveAhead();
                int start = _pos;
                while (Char.IsLetterOrDigit(Peek()))
                    MoveAhead();
                if (_pos > start)
                {
                    CommandLineFlag flag = new CommandLineFlag();
                    flag.Flag = _cmd.Substring(start, _pos - start);
                    if (Peek() == ':')
                    {
                        // Parse flag argument
                        MoveAhead();
                        flag.Argument = ParseArgument();
                    }
                    Flags.Add(flag);
                }
            }
            else
            {
                // Parse command-line argument
                string arg = ParseArgument();
                if (arg.Length > 0)
                    Arguments.Add(arg);
            }
        }
    }

    /// <summary>
    /// Initializes parser state variables to parse specified
    /// command line.
    /// </summary>
    /// <param name="commandLine">Command line to parse</param>
    protected void BeginParsing(string commandLine)
    {
        Arguments = new List<string>();
        Flags = new List<CommandLineFlag>();
        _cmd = commandLine;
        _pos = 0;
    }

    /// <summary>
    /// Returns true if the current position is at the end of
    /// the string.
    /// </summary>
    protected bool EndOfText
    {
        get { return (_pos >= _cmd.Length); }
    }

    /// <summary>
    /// Safely returns the character at the current position.
    /// </summary>
    protected char Peek()
    {
        return (_pos < _cmd.Length) ? _cmd[_pos] : (char)0;
    }

    /// <summary>
    /// Safely advances to current position to the next
    /// character.
    /// </summary>
    protected char MoveAhead()
    {
        char c = Peek();
        _pos = Math.Min(_pos + 1, _cmd.Length);
        return c;
    }

    /// <summary>
    /// Parses a command-line argument. Supports arguments enclosed
    /// in double or single quotation marks. If no argument could
    /// be parsed, an empty string is returned.
    /// </summary>
    protected string ParseArgument()
    {
        string result;

        if (Peek() == '"' || Peek() == '\'')
        {
            // Parse quoted argument
            char quote = MoveAhead();
            int start = _pos;
            while (!EndOfText && Peek() != quote)
                MoveAhead();
            result = _cmd.Substring(start, _pos - start);
            MoveAhead();    // Eat closing quote
        }
        else
        {
            // Parse argument
            int start = _pos;
            while (!EndOfText && !Char.IsWhiteSpace(Peek()) &&
                Peek() != '/' && Peek() != '-')
                MoveAhead();
            result = _cmd.Substring(start, _pos - start);
        }
        // Return the parsed argument
        return result;
    }
}

Using the Class

Using the class is straight forward enough. The code in Listing 2 provides an example.

This code creates an instance of the CommandLine class, passing Environment.CommandLine as an argument to the constructor. It then loops through the Arguments and Flags collections and populates a ListView control named lvwResults with the results.

Listing 2: Code that Uses the CommandLine Class

CommandLine cmd = new CommandLine(Environment.CommandLine);
lvwResults.Items.Clear();

// Add arguments to list
foreach (string arg in cmd.Arguments)
{
    ListViewItem item = new ListViewItem();
    item.Text = "Argument";
    item.SubItems.Add(arg);
    lvwResults.Items.Add(item);
}

// Add flags to list
foreach (CommandLineFlag flag in cmd.Flags)
{
    ListViewItem item = new ListViewItem();
    item.Text = "Flag";
    item.SubItems.Add(flag.Flag);
    if (flag.Argument != null)
        item.SubItems.Add(flag.Argument);
    lvwResults.Items.Add(item);
}

Conclusion

And that's all there is to that. Again, not that many Windows applications need to access the command line. But if your application needs to support a variety of different types of command-line arguments, this class should help.

End-User License

Use of this article and any related source code or other files is governed by the terms and conditions of The Code Project Open License.

Author Information

Jonathan Wood

I'm a software/website developer working out of the greater Salt Lake City area in Utah. I've developed many websites including Black Belt Coder, Insider Articles, and others.