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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
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.
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.
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()
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.