神刀安全网

Command Line Programs on OS X Tutorial

Command Line Programs on OS X Tutorial Not so long ago, before the advent of graphical user interfaces, command-line programs were the primary method for interacting with computers. 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 OS X 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 arguments or run in interactive mode without arguments.

Getting Started

Swift seems like an odd choice for creating a command-line program, as languages like C, Perl, Ruby or Java are a 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.

In this command line programs for OS X tutorial, you’ll create a classic compiled project.

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

Command Line Programs on OS X Tutorial

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

Command Line Programs on OS X Tutorial

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

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 that file that isn’t a method or class declaration is the first one to execute. 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 .

Press Cmd + N to create a new file. Under OS X , select Source/Swift File and press Next :

Command Line Programs on OS X Tutorial

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

Add the following code to ConsoleIO.swift :

class ConsoleIO {   class func printUsage() {     let executableName = (Process.arguments[0] as NSString).lastPathComponent       print("usage:")     print("/(executableName) -a string1 string2")     print("or")     print("/(executableName) -p string")     print("or")     print("/(executableName) -h to show usage information")     print("Type /(executableName) without an option to enter interactive mode.")   } }

This code creates a class ConsoleIO and a class 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 Process enum. Process is a small wrapper 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 and add the following code to it:

class Panagram {   func staticMode() {     ConsoleIO.printUsage()   } }

This creates a class Panagram 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 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 .

Command-Line Arguments

When you start a command-line program, everything you type after the name is passed as an argument to the program. Arguments can be separated with whitespace characters. Usually, you’ll run into two kind of arguments: options and strings .

Options start with a dash followed by a character, or two dashes followed by a word. For example, many programs have the option -h or --help , the first being simply a shortcut for the second. To keep things simple, Panagram will only support the short version of options.

Open ConsoleIO.swift and add the following enum above the ConsoleIO class:

enum OptionType: String {   case Palindrome = "p"   case Anagram = "a"   case Help = "h"   case Unknown     init(value: String) {     switch value {     case "a": self = .Anagram     case "p": self = .Palindrome     case "h": self = .Help     default: self = .Unknown     }   } }

This creates an enum with String as its base type so you can pass the command-line arguments directly to init(_:) . Panagram has three options: -p to detect palindromes, -a for anagrams and -h to show the usage information. Everything else will be handled as an error.

Next, add the following method to the ConsoleIO class:

func getOption(option: String) -> (option:OptionType, value: String) {   return (OptionType(value: option), option) }

The above method accepts a String as its argument and returns a tuple of OptionType and String .

Open Panagram.swift and add the following property to the class:

let consoleIO = ConsoleIO()

Then replace the content of staticMode() with the following:

//1 let argCount = Process.argc //2 let argument = Process.arguments[1] //3 let (option, value) = consoleIO.getOption(argument.substringFromIndex(argument.startIndex.advancedBy(1))) //4 print("Argument count: /(argCount) Option: /(option) value: /(value)")

Here’s what’s going on in the code above:

  1. You first get the number of arguments passed to the program. Since the executable path is always passed in, this value will always be greater than or equal to 1.
  2. Next, take the first “real” argument from the arguments array.
  3. Then you parse the argument and convert it to an OptionType .
  4. Finally, you log the parsing results to the console.

In main.swift , replace the line panagram.staticMode() with the following:

if Process.argc < 2 {   //Handle interactive mode } else {   panagram.staticMode() }

If your program is invoked with fewer than 2 arguments, then you’re going to start interactive mode. Otherwise you use the non-interactive static mode.

You now need to figure out how to pass arguments to your command-line tool from within Xcode . To do this, click on the Scheme named Panagram in the Toolbar :

Command Line Programs on OS X Tutorial

Select Edit Scheme… in the popover that appears:

Command Line Programs on OS X Tutorial

Ensure Run is selected, click the Arguments tab, then click the + sign under Arguments Passed On Launch . Add -p as argument and click Close :

Command Line Programs on OS X Tutorial

Now run your project, and you’ll see the following output in the console:

Argument count: 2 Option: Palindrome value: p Program ended with exit code: 0

So far, you’ve added a basic option system to your tool, learned how to handle command-line arguments and how to pass arguments from within Xcode.

Next up, you’ll build up the main functionality of Panagram.

Anagrams and Palindromes

Before you can write any code to detect palindromes or anagrams, you should be clear on what they are!

Palindromes are words or sentences that read the same backwards and forwards. Here are some examples:

  • level
  • noon
  • A man, a plan, a canal – Panama!

As you can see, capitalization and punctuation are often ignored.

Anagrams are words or sentences that are built using the characters of other words or sentences. Some examples are:

  • silent<-> listen
  • Bolivia<-> Lobivia (it’s a cactus from Bolivia)

You’ll encapsulate the detection logic inside a small extension to String .

Create a new file StringExtension.swift and add the following code to it:

extension String { }

Time for a bit of design work. First, how to detect an anagram:

  1. Ignore capitalization and whitespace for both strings.
  2. Check that both strings contain the same characters, and that all characters appear the same number of times.

Add the following method to the String extension:

func isAnagramOfString(s: String) -> Bool {   //1   let lowerSelf = self.lowercaseString.stringByReplacingOccurrencesOfString(" ", withString: "")   let lowerOther = s.lowercaseString.stringByReplacingOccurrencesOfString(" ", withString: "")   //2   return lowerSelf.characters.sort() == lowerOther.characters.sort() }

Taking a closer look at the algorithm above:

  1. First, you remove capitalization and whitespace from both Strings.
  2. Then you sort and compare the characters.

Detecting palindromes is simple as well:

  1. Ignore all capitalization and whitespace.
  2. Reverse the string and compare; if it’s the same, then you have a palindrome.

Add the following method to detect palindromes:

func isPalindrome() -> Bool {   //1   let f = self.lowercaseString.stringByReplacingOccurrencesOfString(" ", withString: "")   //2   let s = String(f.characters.reverse())   //3   return  f == s }

The logic here is quite straightforward:

  1. Remove capitalization and whitespace.
  2. Create a second string with the reversed characters.
  3. If they are equal, it is a palindrome.

Time to pull this all together and help Panagram do its job.

Open Panagram.swift and replace the print(_:) statement inside staticMode() with the following:

//1 switch option {   case .Anagram:   //2   if argCount != 4 {     if argCount > 4 {       print("Too many arguments for option /(option.rawValue)")     } else {       print("Too few arguments for option /(option.rawValue)")     }       ConsoleIO.printUsage()   } else {     //3     let first = Process.arguments[2]     let second = Process.arguments[3]       if first.isAnagramOfString(second) {       print("/(second) is an anagram of /(first)")     } else {       print("/(second) is not an anagram of /(first)")     }   }   case .Palindrome:   //4   if argCount != 3 {     if argCount > 3 {       print("Too many arguments for option /(option.rawValue)")     } else {       print("Too few arguments for option /(option.rawValue)")     }   } else {     //5     let s = Process.arguments[2]     let isPalindrome = s.isPalindrome()     print("/(s) is /(isPalindrome ? "" : "not ")a palindrome")   }   //6   case .Help:   ConsoleIO.printUsage()   case .Unknown:   //7   print("Unknown option /(value)")   ConsoleIO.printUsage() }

Going through this step-by-step:

  1. First, switch to see what should be detected.
  2. In the case of an anagram, there must be four command-line arguments passed in. The first is the executable path, the second the a option and finally the two strings to check. If you don’t have four arguments, then print an error message.
  3. If the argument count is good, store the two strings in local variables, check the strings and print the result.
  4. In the case of a palindrome, you must have three arguments.
  5. Check for the palindrome and print the result.
  6. If the -h option was passed in, then print the usage information.
  7. If an unknown option is passed, print the usage to the console.

Run your project with some modified arguments inside the Scheme . For example, you could use the -p option as shown below:

Command Line Programs on OS X Tutorial

You have a basic version of Panagram working, but you can make it even more useful by tying in to the input and output streams.

Input and Output

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 progess or some error messages if something goes wrong.

Unix-based systems such as OS X 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.

stderr can be used to log error messages, the internal program state, or any other information the user doesn’t need to see. This can make debugging of a shipped application much easier.

Your next task is to change Panagram to use these different output streams.

First, open ConsoleIO.swift and add the following enum to the top of the file, outside the scope of the Panagram class:

enum OutputType {   case Error   case Standard }

This defines the output method to use when writing messages.

Next add the following function to the ConsoleIO class:

func writeMessage(message: String, to: OutputType = .Standard) {   switch to {   case .Standard:     print("/u{001B}[;m/(message)")   case .Error:     fputs("/u{001B}[0;31m/(message)/n", stderr)   } }

This function has two parameters; the first is the actual message to print, and the second is where to write it. This defaults to .Standard .

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

What are those cryptic strings, though? These are control characters that cause Terminal to change the color of the following string. In this case you’ll print error messages in red ( /u{001B}[0;31m ). The sequence /u{001B}[;m used in the standard case resets the terminal color back to the default.

To use this new function, open Panagram.swift and change the print lines in staticMode() to use the new writeMessage(_:to:) method.

The switch statement should now look like the following:

case .Anagram:   if argCount != 4 {     if argCount > 4 {       consoleIO.writeMessage("Too many arguments for option /(option.rawValue)", to: .Error)     } else {       consoleIO.writeMessage("too few arguments for option /(option.rawValue)", to: .Error)     }     ConsoleIO.printUsage()   } else {     let first = Process.arguments[2]     let second = Process.arguments[3]       if first.isAnagramOfString(second) {       consoleIO.writeMessage("/(second) is an anagram of /(first)")     } else {       consoleIO.writeMessage("/(second) is not an anagram of /(first)")     }   } case .Palindrome:   if argCount != 3 {     if argCount > 3 {       consoleIO.writeMessage("Too many arguments for option /(option.rawValue)", to: .Error)     } else {       consoleIO.writeMessage("too few arguments for option /(option.rawValue)", to: .Error)     }   } else {     let s = Process.arguments[2]     let isPalindrome = s.isPalindrome()     consoleIO.writeMessage("/(s) is /(isPalindrome ? "" : "not ")a palindrome")   } case .Help:   ConsoleIO.printUsage() case .Unknown:   consoleIO.writeMessage("Unkonwn option /(value)", to: .Error)   ConsoleIO.printUsage() }

As you see, only error messages need the to: parameter. That’s one of the benefits of Swift’s default values for parameters.

Run your project; you should see something similar to this in the Xcode console. For example, using the arguments a, listen and silent you’ll see the following:

[;mlisten is an anagram of silent

The [;m at the beginning of the output looks a bit awkward. That’s because the Xcode console doesn’t support using control characters to colorize the output. To see this in action, you’ll have to launch Panagram in Terminal:

Command Line Programs on OS X Tutorial

Note: The following section walks you how to launch your app in Terminal directly from Xcode. The downside is that you can’t debug your app in Xcode this way; therefore, the next section is optional and you can skip directly to the Handle Input section if you wish.

Launching Outside Xcode

There are different ways to launch your program from inside Terminal. You could find the build product using the Finder and start it directly via Terminal, or you could be lazy and tell Xcode to do this for you. In this section you’ll discover the lazy way.

First, edit your current Scheme and uncheck Debug executable . Now click on the Executable drop down and select Other :

Command Line Programs on OS X Tutorial

In the window that appears, go to Applications/Utilities , select Terminal.app , then click Choose :

Command Line Programs on OS X Tutorial

Now select the Arguments tab, remove all entries under Arguments Passed On Launch , then add one new entry in that section:

${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}

Command Line Programs on OS X Tutorial

Finally, click Close .

This instructs Xcode to open Terminal when you run your project and pass through the path to your program. Terminal will then launch your program as you’d expect.

Handle Input

stdin is attached to the keyboard and is therefore a way for you to collect input from users interactively. When Panagram is started without arguments, it will open in interactive mode and prompt the user for the options it would otherwise have collected from its arguments.

First, you need a way to get input from the keyboard.

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

func getInput() -> String {     // 1   let keyboard = NSFileHandle.fileHandleWithStandardInput()     // 2   let inputData = keyboard.availableData     // 3   let strData = NSString(data: inputData, encoding: NSUTF8StringEncoding)!     // 4   return strData.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet()) }

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 create a function interactiveMode() as follows:

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) = consoleIO.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.isAnagramOfString(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. 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. To do this, open ConsoleIO.swift again and add the following line to OptionType :

case Quit = "q"

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

case "q": self = .Quit

Now go back to Panagram.swift and 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:

Finally, open main.swift and replace the comment //Handle interactive mode with the following:

panagram.interactiveMode()

Run your project and enjoy your finished command-line program!

Command Line Programs on OS X Tutorial

Where to Go From Here?

You can download the final project for this command line programs on OS X tutorial

here .

If you want to write more command-line programs in the future, take a look at how to redirect stderr to a log file and also look at ncurses , which is a C library for writing “GUI-style” programs for the terminal.

You can also check out this great article on scripting with Swift.

I hope you enjoyed this command line programs on OS X tutorial; if you have any questions or comments, feel free to join the forum discussion below!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Command Line Programs on OS X Tutorial

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址