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
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Handle Input Interactively

Now that you have a basic version of Panagram working, you can make it even more useful by adding the ability to type in the arguments interactively via the input stream.

In this section, you will add code so when Panagram is started without arguments, it will open in interactive mode and prompt the user for the input it needs.

First, you need a way to get input from the keyboard. stdin is attached to the keyboard and is therefore a way for you to collect input from users interactively.

Open ConsoleIO.swift and add the following method to the class:

func getInput() -> String {
  // 1
  let keyboard = FileHandle.standardInput
  // 2
  let inputData = keyboard.availableData
  // 3
  let strData = String(data: inputData, encoding: String.Encoding.utf8)!
  // 4
  return strData.trimmingCharacters(in: CharacterSet.newlines)
}

Taking each numbered section in turn:

  1. First, grab a handle to stdin.
  2. Next, read any data on the stream.
  3. Convert the data to a string.
  4. Finally, remove any newline characters and return the string.

Next, open Panagram.swift and add the following method to the class:

func interactiveMode() {
  //1
  consoleIO.writeMessage("Welcome to Panagram. This program checks if an input string is an anagram or palindrome.")
  //2
  var shouldQuit = false
  while !shouldQuit {
    //3
    consoleIO.writeMessage("Type 'a' to check for anagrams or 'p' for palindromes type 'q' to quit.")
    let (option, value) = getOption(consoleIO.getInput())
     
    switch option {
    case .anagram:
      //4
      consoleIO.writeMessage("Type the first string:")
      let first = consoleIO.getInput()
      consoleIO.writeMessage("Type the second string:")
      let second = consoleIO.getInput()
        
      //5
      if first.isAnagramOf(second) {
        consoleIO.writeMessage("\(second) is an anagram of \(first)")
      } else {
        consoleIO.writeMessage("\(second) is not an anagram of \(first)")
      }
    case .palindrome:
      consoleIO.writeMessage("Type a word or sentence:")
      let s = consoleIO.getInput()
      let isPalindrome = s.isPalindrome()
      consoleIO.writeMessage("\(s) is \(isPalindrome ? "" : "not ")a palindrome")
    default:
      //6
      consoleIO.writeMessage("Unknown option \(value)", to: .error)
    }
  }
}

Taking a look at what's going on above:

  1. First, print a welcome message.
  2. shouldQuit breaks the infinite loop that is started in the next line.
  3. Prompt the user for input and convert it to one of the two options, if possible.
  4. If the option was for anagrams, prompt the user for the two strings to compare.
  5. Write the result out. The same logic flow applies to the palindrome option.
  6. If the user enters an unknown option, print an error and start the loop again.

At the moment, you have no way to interrupt the while loop. In Panagram.swift add the following line to the OptionType enum:

case quit = "q"

Next, add the following line to the enum's init(_:):

case "q": self = .quit

In the same file, add a .quit case to the switch statement inside interactiveMode():

case .quit:
  shouldQuit = true

Then, change the .unknown case definition inside staticMode() as follows:

case .unknown, .quit:

Open main.swift and replace the comment //TODO: Handle interactive mode with the following:

panagram.interactiveMode()

To test interactive mode, you must not have any arguments defined in the Scheme.

So, remove the two arguments you defined earlier. Select Edit Scheme... from the toolbar menu. Select each argument and then click the - sign under Arguments Passed On Launch. Once all arguments are deleted, click Close:

Build and run, and you'll see the following output in the Console:

Welcome to Panagram. This program checks if an input string is an anagram or palindrome.
Type 'a' to check for anagrams or 'p' for palindromes type 'q' to quit.

Try out the different options. Type an option letter (do not prefix with a hyphen) followed by Return. You will be prompted for the arguments. Enter each value followed by Return. In the Console you should see something similar to this:

a
Type the first string:
silent
Type the second string:
listen
listen is an anagram of silent
Type 'a' to check for anagrams or 'p' for palindromes type 'q' to quit.
p
Type a word or sentence:
level
level is a palindrome
Type 'a' to check for anagrams or 'p' for palindromes type 'q' to quit.
f
Error: Unknown option f
Type 'a' to check for anagrams or 'p' for palindromes type 'q' to quit.
q
Program ended with exit code: 0

Launching Outside Xcode

Normally, a command-line program is launched from a shell utility like Terminal (vs. launching it from an IDE like Xcode). The following section walks you through launching your app in Terminal.

There are different ways to launch your program via Terminal. You could find the compiled binary using the Finder and start it directly via Terminal. Or, you could be lazy and tell Xcode to do this for you. First, you'll learn the lazy way.

Launch your app in Terminal from Xcode

Create a new scheme that will open Terminal and launch Panagram in the Terminal window. Click on the scheme named Panagram in the toolbar and select New Scheme:

Name the new scheme Panagram on Terminal:

Ensure the Panagram on Terminal scheme is selected as the active scheme. Click the scheme and select Edit Scheme... in the popover.

Ensure that the Info tab is selected and then click on the Executable drop down and select Other. Now, find the Terminal.app in your Applications/Utilities folder and click Choose. Now that Terminal is your executable, uncheck Debug executable.

Your Panagram on Terminal scheme's Info tab should look like this:

Note: The downside is that you can't debug your app in Xcode this way because now the executable that Xcode launches during a run is Terminal and not Panagram.

Next, select the Arguments tab, then add one new argument:

${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}

passed_arguments2

Finally, click Close.

Now, make sure you have the scheme Panagram on Terminal selected, then build and run your project. Xcode will open Terminal and pass through the path to your program. Terminal will then launch your program as you'd expect.

Finished_Program

Launch your app directly from Terminal

Open Terminal from your Applications/Utilities folder.

In the Project Navigator select your product under the Products group. Copy your debug folder's Full Path from Xcode's Utility area as shown below (do not include "Panagram"):

Open a Finder window and select the Go/Go to Folder... menu item and paste the full path you copied in the previous step into the dialog's text field:

Click Go and Finder navigates to the folder containing the Panagram executable:

Drag the Panagram executable from Finder to the Terminal window and drop it there. Switch to the Terminal window and hit Return on the keyboard. Terminal launches Panagram in interactive mode since no arguments were specified:

Finished_Program

Note: To run Panagram directly from Terminal in static mode, perform the same steps as described above for the interactive mode, but before hitting Return type the relevant arguments. For example: -p level or -a silent listen etc..

Contributors

Gabriel Miro

Tech Editor

Fahim Farook

Final Pass Editor

Michael Briscoe

Team Lead

Over 300 content creators. Join our team.