Command Line Programs on macOS Tutorial

Discover how easy it is to make your own terminal-based apps with this command line programs on macOS tutorial. Updated for Xcode 9 and Swift 4! By Eric Soto.

Leave a rating/review
Save for later
Share
Update 7/21/17: This command line programs on macOS tutorial has been updated for Xcode 9 and Swift 4.

The typical Mac user interacts with their computer using a Graphical User Interface (GUI). GUIs, as the name implies, are based on the user visually interacting with the computer via input devices such as the mouse by selecting or operating on screen elements such as menus, buttons etc.

Not so long ago, before the advent of the GUI, command-line interfaces (CLI) were the primary method for interacting with computers. CLIs are text-based interfaces, where the user types in the program name to execute, optionally followed by arguments.

Despite the prevalence of GUIs, command-line programs still have an important role in today’s computing world. Command-line programs such as ImageMagick or ffmpeg are important in the server world. In fact, the majority of the servers that form the Internet run only command-line programs.

Even Xcode uses command-line programs! When Xcode builds your project, it calls xcodebuild, which does the actual building. If the building process was baked-in to the Xcode product, continuous integration solutions would be hard to achieve, if not impossible!

In this Command Line Programs on macOS tutorial, you will write a command-line utilty named Panagram. Depending on the options passed in, it will detect if a given input is a palindrome or anagram. It can be started with predefined arguments, or run in interactive mode where the user is prompted to enter the required values.

Typically, command-line programs are launched from a shell (like the bash shell in macOS) embedded in a utility application like Terminal in macOS. For the sake of simplicity and ease of learning, in this tutorial, most of the time you will use Xcode to launch Panagram. At the end of the tutorial you will learn how to launch Panagram from the terminal.

Getting Started

Swift seems like an odd choice for creating a command-line program since languages like C, Perl, Ruby or Java are the more traditional choice. But there are some great reasons to choose Swift for your command-line needs:

  • Swift can be used as an interpreted scripting language, as well as a compiled language. This gives you the advantages of scripting languages, such as zero compile times and ease of maintenance, along with the choice of compiling your app to improve execution time or to bundle it for sale to the public.
  • You don’t need to switch languages. Many people say that a programmer should learn one new language every year. This is not a bad idea, but if you are already used to Swift and its standard library, you can reduce the time investment by sticking with Swift.

For this tutorial, you’ll create a classic compiled project.

Open Xcode and go to File/New/Project. Find the macOS group, select Application/Command Line Tool and click Next:

For Product Name, enter Panagram. Make sure that Language is set to Swift, then click Next.

Choose a location on your disk to save your project and click Create.

In the Project Navigator area you will now see the main.swift file that was created by the Xcode Command Line Tool template.

Many C-like languages have a main function that serves as the entry point — i.e. the code that the operating system will call when the program is executed. This means the program execution starts with the first line of this function. Swift doesn’t have a main function; instead, it has a main file.

When you run your project, the first line inside the main file that isn’t a method or class declaration is the first one to be executed. It’s a good idea to keep your main.swift file as clean as possible and put all your classes and structs in their own files. This keeps things streamlined and helps you to understand the main execution path.

The Output Stream

In most command-line programs, you’d like to print some messages for the user. For example, a program that converts video files into different formats could print the current progress or some error message if something went wrong.

Unix-based systems such as macOS define two different output streams:

  • The standard output stream (or stdout) is normally attached to the display and should be used to display messages to the user.
  • The standard error stream (or stderr) is normally used to display status and error messages. This is normally attached to the display, but can be redirected to a file.
Note: When launching a command-line program from either Xcode or from Terminal, by default, stdout and stderr are the same and messages for both are written to the console. It is a common practice to redirect stderr to a file so error messages scrolled off the screen can be viewed later. Also this can make debugging of a shipped application much easier by hiding information the user doesn’t need to see, but still keep the error messages for later inspection.

With the Panagram group selected in the Project navigator, press Cmd + N to create a new file. Under macOS, select Source/Swift File and press Next:

Save the file as ConsoleIO.swift. You’ll wrap all the input and output elements in a small, handy class named ConsoleIO.

Add the following code to the end of ConsoleIO.swift:

class ConsoleIO {
}

Your next task is to change Panagram to use the two output streams.

In ConsoleIO.swift add the following enum at the top of the file, above the ConsoleIO class implementation and below the import line:

enum OutputType {
  case error
  case standard
}

This defines the output stream to use when writing messages.

Next, add the following method to the ConsoleIO class (between the curly braces for the class implementation):

func writeMessage(_ message: String, to: OutputType = .standard) {
  switch to {
  case .standard:
    print("\(message)")
  case .error:
    fputs("Error: \(message)\n", stderr)
  }
}

This method has two parameters; the first is the actual message to print, and the second is the destination. The second parameter defaults to .standard.

The code for the .standard option uses print, which by default writes to stdout. The .error case uses the C function fputs to write to stderr, which is a global variable and points to the standard error stream.

Add the following code to the end of the ConsoleIO class:

func printUsage() {

  let executableName = (CommandLine.arguments[0] as NSString).lastPathComponent
        
  writeMessage("usage:")
  writeMessage("\(executableName) -a string1 string2")
  writeMessage("or")
  writeMessage("\(executableName) -p string")
  writeMessage("or")
  writeMessage("\(executableName) -h to show usage information")
  writeMessage("Type \(executableName) without an option to enter interactive mode.")
}

This code defines the printUsage() method that prints usage information to the console. Every time you run a program, the path to the executable is implicitly passed as argument[0] and accessible through the global CommandLine enum. CommandLine is a small wrapper in the Swift Standard Library around the argc and argv arguments you may know from C-like languages.

Note: It is common practice to print a usage statement to the console when the user tries to start a command-line program with incorrect arguments.

Create another new Swift file named Panagram.swift (following the same steps as before) and add the following code to it:

class Panagram {

  let consoleIO = ConsoleIO()

  func staticMode() {
    consoleIO.printUsage()
  }

}

This defines a Panagram class that has one method. The class will handle the program logic, with staticMode() representing non-interactive mode — i.e. when you provide all data through command line arguments. For now, it simply prints the usage information.

Now, open main.swift and replace the print statement with the following code:

let panagram = Panagram()
panagram.staticMode()
Note: As explained above, for main.swift these are the first lines of code that will be executed when the program is launched.

Build and run your project; you’ll see the following output in Xcode’s Console:

usage:
Panagram -a string1 string2
or
Panagram -p string
or
Panagram -h to show usage information
Type Panagram without an option to enter interactive mode.
Program ended with exit code: 0

So far, you’ve learned what a command-line tool is, where the execution starts, how to send messages to stdout and stderr and how you can split your code into logical units to keep main.swift organized.

In the next section, you’ll handle command-line arguments and complete the static mode of Panagram.

Contributors

Gabriel Miro

Tech Editor

Fahim Farook

Final Pass Editor

Michael Briscoe

Team Lead

Over 300 content creators. Join our team.