用Swift来写命令行程序

移动开发 iOS
试试自己动手 现在的翻译程序还有很多可以优化的地方。下面是一个你可以尝试的列表: 接受命令行参数来设置默认的源语言和目标语言 接受命令行参数来实现非交互模式 添加 swap 命令来交换源语言和目标语言 添加 help 命令 整合 from 命令和 to 命令。实现一行可以同时设置两者, 比如 from en to es 现在当输入 from 命令和 to 命令时,没有同时输入对应的语言时会崩溃,修复这个BUG 实现对转义符 \ 的处理,实现程序的“命令”也可以被翻译(比如退出命令:quit) 通过 localizedDescript

这是探索 Swift 写 Linux 程序的系列文章中的一篇。

在上一个例子中,我们通过组合使用 popen 和 wget 命令来调用 自然语言翻译服务 ,来实现像 Google 翻译 那样的翻译功能。本文的程序会基于之前我们已经完成的工作来进行。但与之前每次执行都只能翻译一句话所不同的是,这次我们要实现一个具备交互功能的 shell 程序,来翻译在控制台输入的每一句话。像下面的截图一样:

翻译程序会显示它接受什么语言(源语言)以及翻译的目标语言。比如:

en->es 英语翻译为西班牙语
es->it 西班牙语翻译为意大利语
it->ru 意大利语翻译为俄罗斯语

翻译程序默认是 en->es ,并提供了两个命令: to 和 from 来实现语言的切换。比如,输入 to es 将会把翻译的目标语言设置为西班牙语。输入 quit 可以退出程序。

如果用户输入的字符串不是命令的话,翻译程序会把输入逐字地发送到翻译的 web 服务。然后把返回的结果打印出来。
需要注意的几点

如果你是系统或者运维程序员,并且以前也没接触过 Swift 的话,下面是一些你在代码里需要注意的事情。我想你会发现 Swift 为两种类型的工程师都提供了很多有用的特性,并且会成为 Linux 开发体系中一股很受欢迎的新力量。

let variable = value 常量赋值
元组(tuples)
switch-case 支持字符串
switch-case 使用时必须包含所有情况(逻辑完备性)
计算型 属性
import Glibc 可以导入标准的 C 函数
guard 语句
可以使用 NSThread 和 NSNotificationCenter 这些苹果的 Foundation 框架中的类。
在不同的线程或不同的对象里通过发送消息来触发特定代码的执行

程序设计

我们的翻译程序可以拆分成一个主程序、两个类以及一个 globals.swift 文件。如果你打算跟着做,那你应该使用 Swift 的包管理器 ,然后调整你的目录结构为下面这样:

  1. translator/Sources/main.swift  
  2.           /Sources/CommandInterpreter.swift  
  3.           /Sources/...  
  4.           /Package.swift 

main.swift 文件是 Swift 应用程序的入口并且应该是唯一一个包含可执行代码的文件(在这里,像「变量赋值」,或者「声明一个类」不属于「可执行的代码」)。

main.swift :

  1. import Foundation  
  2. import Glibc  
  3.    
  4. let interpreter = CommandInterpreter()  
  5. let translator  = Translator()  
  6.    
  7. // Listen for events to translate  
  8. nc.addObserverForName(INPUT_NOTIFICATION, object:nil, queue:nil) {  
  9.   (_) in  
  10.   let tc = translationCommand 
  11.   translator.translate(tc.text, from:tc.from, to:tc.to){  
  12.     translation, error in  
  13.     guard error == nil && translation != nil else {  
  14.       print("Translation failure:  \(error!.code)")  
  15.       return  
  16.     }  
  17.     print(translation!)  
  18.   }  
  19. }  
  20.    
  21. interpreter.start()  
  22.    
  23. select(0, nil, nil, nil, nil) 

上面的代码表示我们的程序不接受命令行参数。具体的流程说明:

分别创建 CommandInterpreter 和 Translator 类的实例
为 InputNotification 通知添加观察者(这里用到的常量 INPUT_NOTIFICATION 常量定义在 globals.swift )
添加当收到通知的时候要执行的代码
调用 Interpreter 类实例的 start 方法
调用 select 来实现当程序有其他线程在运行的时候,锁定主线程。(译注:也就是防止主线程提前结束)

CommandInterpreter 类

CommandInterpreter 类主要负责从终端读入输入的字符串,并且分析输入的类型并分别进行处理。考虑到你可能刚接触 Swift,我在代码里对涉及到语言特性的地方进行了注释。

  1. // Import statements  
  2. import Foundation  
  3. import Glibc  
  4.    
  5. // Enumerations  
  6. enum CommandType {  
  7. case None  
  8. case Translate  
  9. case SetFrom  
  10. case SetTo  
  11. case Quit  
  12. }  
  13.    
  14. // Structs  
  15. struct Command {  
  16.   var type:CommandType  
  17.   var data:String  
  18. }  
  19.    
  20. // Classes  
  21. class CommandInterpreter {  
  22.    
  23.   // Read-only computed property  
  24.   var prompt:String {  
  25.     return "\(translationCommand.from)->\(translationCommand.to)"  
  26.   }  
  27.    
  28.   // Class constant  
  29.   let delim:Character = "\n" 
  30.    
  31.   init() {  
  32.   }  
  33.    
  34.   func start() {  
  35.     let readThread = NSThread(){  
  36.       var input:String    = "" 
  37.         
  38.       print("To set input language, type 'from LANG'")  
  39.       print("To set output language, type 'to LANG'")  
  40.       print("Type 'quit' to exit")  
  41.       self.displayPrompt()  
  42.    
  43.       while true {  
  44.         let c = Character(UnicodeScalar(UInt32(fgetc(stdin))))  
  45.         if c == self.delim {  
  46.           let command = self.parseInput(input)  
  47.           self.doCommand(command)  
  48.           input = "" // Clear input  
  49.           self.displayPrompt()  
  50.         } else {  
  51.           input.append(c)  
  52.         }  
  53.       }  
  54.     }  
  55.       
  56.     readThread.start()  
  57.   }  
  58.    
  59.   func displayPrompt() {  
  60.     print("\(self.prompt):  ", terminator:"")  
  61.   }  
  62.    
  63.   func parseInput(input:String) -> Command {  
  64.     var commandType:CommandType  
  65.     var commandData:String = "" 
  66.       
  67.     // Splitting a string  
  68.     let tokens = input.characters.split{$0 == " "}.map(String.init)  
  69.    
  70.     // guard statement to validate that there are tokens  
  71.     guard tokens.count > 0 else {  
  72.       return Command(type:CommandType.None, data:"")  
  73.     }  
  74.    
  75.     switch tokens[0] {  
  76.     case "quit":  
  77.       commandType = .Quit  
  78.     case "from":  
  79.       commandType = .SetFrom  
  80.       commandData = tokens[1]  
  81.     case "to":  
  82.       commandType = .SetTo  
  83.       commandData = tokens[1]  
  84.     default:  
  85.       commandType = .Translate  
  86.       commandData = input 
  87.     }  
  88.     return Command(type:commandType,data:commandData)  
  89.   }  
  90.    
  91.   func doCommand(command:Command) {  
  92.     switch command.type {  
  93.     case .Quit:  
  94.       exit(0)  
  95.     case .SetFrom:  
  96.       translationCommand.from = command.data  
  97.     case .SetTo:  
  98.       translationCommand.to   = command.data  
  99.     case .Translate:  
  100.       translationCommand.text = command.data  
  101.       nc.postNotificationName(INPUT_NOTIFICATION, object:nil)     
  102.     case .None:  
  103.       break  
  104.     }  
  105.   }  

CommandInterpreter 类的实现逻辑非常直观。当 start 函数被调用的时候,通过 NSThread 来创建一个线程,线程中再通过 block fgetc 的回调参数 stdin 来获取终端的输入。当遇到换行符 RETURN (用户按了回车)后,输入的字符串会被解析并映射成一个 Command 对象。然后传递给 doCommand 函数进行剩下的处理。

我们的 doCommand 函数就是一个简单的 switch-case 语句。对于 .Quit 命令则就简单调用 exit(0) 来终止程序。 .SetFrom 和 .SetTo 命令的功能是显而易见的。当遇到 .Translate 命令时,Foundation 的消息系统就派上用场了。 doCommand 函数自己并不完成任何的翻译功能,它只是简单的 发送 一个应用程序级别的消息,也就是 InputNotification 。任何监听这个消息的代码都会被调用(比如我们之前的主线程):

  1. // Listen for events to translate  
  2. nc.addObserverForName(INPUT_NOTIFICATION, object:nil, queue:nil) {  
  3.   (_) in  
  4.   let tc = translationCommand 
  5.   translator.translate(tc.text, from:tc.from, to:tc.to){  
  6.     translation, error in  
  7.     guard error == nil && translation != nil else {  
  8.       print("Translation failure:  \(error!.code)")  
  9.       return  
  10.     }  
  11.     print(translation!)  
  12.   }  

我在 这篇文章 中提到,在对 NSNotification 的 userInfo 字典做类型转换时会有一个 SILGen 的闪退 crash,在这里我们用一个叫做 translationCommand 的全局变量来绕过这个 crash。在这段代码里:

为了代码的简洁,把 translationCommand 的内容赋值给 tc
调用 Translator 对象的 translate 方法,并传入相关的参数
实现翻译完成后的回调
用一个 Swift 漂亮的 guard 语句来检测是否有错并返回
打印出翻译的文本

Translator

Translator 类最开始是在 这篇文章 中介绍的,我们在这里直接重用:

  1. import Glibc  
  2. import Foundation  
  3. import CcURL  
  4. import CJSONC  
  5.    
  6. class Translator {  
  7.    
  8.   let BUFSIZE = 1024 
  9.    
  10.   init() {  
  11.   }  
  12.    
  13.   func translate(text:String, from:String, to:String,  
  14.                         completion:(translation:String?, error:NSError?) -> Void) {  
  15.    
  16.     let curl = curl_easy_init()  
  17.    
  18.     guard curl != nil else {  
  19.       completion(translation:nil,  
  20.                  error:NSError(domain:"translator", code:1, userInfo:nil))  
  21.       return  
  22.     }  
  23.    
  24.     let escapedText = curl_easy_escape(curl, text, Int32(strlen(text)))  
  25.    
  26.     guard escapedText != nil else {  
  27.       completion(translation:nil,  
  28.                  error:NSError(domain:"translator", code:2, userInfo:nil))  
  29.       return  
  30.     }  
  31.       
  32.     let langPair = from + "%7c" + to  
  33.     let wgetCommand = "wget -qO- http://api.mymemory.translated.net/get\\?q\\=" + String.fromCString(escapedText)! + "\\&langpair\\=" + langPair  
  34.       
  35.     let pp      = popen(wgetCommand, "r")  
  36.     var buf     = [CChar](count:BUFSIZE, repeatedValue:CChar(0))  
  37.       
  38.     var response:String = "" 
  39.     while fgets(&buf, Int32(BUFSIZE), pp) != nil {  
  40.       responseresponse = response + String.fromCString(buf)!  
  41.     }  
  42.       
  43.     let translation = getTranslatedText(response)  
  44.    
  45.     guard translation.error == nil else {  
  46.       completion(translation:nil, error:translation.error)  
  47.       return  
  48.     }  
  49.    
  50.     completion(translation:translation.translation, error:nil)  
  51.   }  
  52.    
  53.   private func getTranslatedText(jsonString:String) -> (error:NSError?, translation:String?) {  
  54.    
  55.     let obj = json_tokener_parse(jsonString)  
  56.    
  57.     guard obj != nil else {  
  58.       return (NSError(domain:"translator", code:3, userInfo:nil),  
  59.              nil)  
  60.     }  
  61.    
  62.     let responseData = json_object_object_get(obj, "responseData")  
  63.    
  64.     guard responseData != nil else {  
  65.       return (NSError(domain:"translator", code:3, userInfo:nil),  
  66.               nil)  
  67.     }  
  68.    
  69.     let translatedTextObj = json_object_object_get(responseData,  
  70.                                                    "translatedText")  
  71.    
  72.     guard translatedTextObj != nil else {  
  73.       return (NSError(domain:"translator", code:3, userInfo:nil),  
  74.               nil)  
  75.     }  
  76.    
  77.     let translatedTextStr = json_object_get_string(translatedTextObj)  
  78.    
  79.     return (nil, String.fromCString(translatedTextStr)!)  
  80.              
  81.   }  
  82.    

整合各个部分

要把上面介绍的组件结合到一起,我们还需要创建额外的两个文件: globals.swift 和 Package.swift 。

globals.swift :

  1.  
  2.  
  3. import Foundation  
  4.    
  5. let INPUT_NOTIFICATION   = "InputNotification" 
  6. let nc = NSNotificationCenter.defaultCenter()  
  7.    
  8. struct TranslationCommand {  
  9.   var from:String  
  10.   var to:String  
  11.   var text:String  
  12. }  
  13.    
  14. var translationCommand:TranslationCommand = TranslationCommand(from:"en",  
  15.                                                                to:"es",  
  16.                                                                text:"")  
  17.  
  18. Package.swift :  
  19.  
  20. import PackageDescription  
  21.    
  22. let package = Package(  
  23.   name:  "translator",  
  24.   dependencies: [  
  25.     .Package(url:  "https://github.com/iachievedit/CJSONC", majorVersion: 1),  
  26.     .Package(url:  "https://github.com/PureSwift/CcURL", majorVersion: 1)  
  27.   ]  

如果一切都配置正确的话,最后执行 swift build ,一个极具特色的翻译程序就完成了。

  1. swift build  
  2. Cloning https://github.com/iachievedit/CJSONC  
  3. Using version 1.0.0 of package CJSONC  
  4. Cloning https://github.com/PureSwift/CcURL  
  5. Using version 1.0.0 of package CcURL  
  6. Compiling Swift Module 'translator' (4 sources)  
  7. Linking Executable:  .build/debug/translator 

试试自己动手

现在的翻译程序还有很多可以优化的地方。下面是一个你可以尝试的列表:

  • 接受命令行参数来设置默认的源语言和目标语言
  • 接受命令行参数来实现非交互模式
  • 添加 swap 命令来交换源语言和目标语言
  • 添加 help 命令
  • 整合 from 命令和 to 命令。实现一行可以同时设置两者, 比如 from en to es
  • 现在当输入 from 命令和 to 命令时,没有同时输入对应的语言时会崩溃,修复这个BUG
  • 实现对转义符 \ 的处理,实现程序的“命令”也可以被翻译(比如退出命令:quit)
  • 通过 localizedDescription 对错误提示添加本地化的支持
  • 在 Translator 类中实现但有错误发生时,通过 throws 来处理异常

结束语

试试自己动手

现在的翻译程序还有很多可以优化的地方。下面是一个你可以尝试的列表:

  • 接受命令行参数来设置默认的源语言和目标语言
  • 接受命令行参数来实现非交互模式
  • 添加 swap 命令来交换源语言和目标语言
  • 添加 help 命令
  • 整合 from 命令和 to 命令。实现一行可以同时设置两者, 比如 from en to es
  • 现在当输入 from 命令和 to 命令时,没有同时输入对应的语言时会崩溃,修复这个BUG
  • 实现对转义符 \ 的处理,实现程序的“命令”也可以被翻译(比如退出命令:quit)
  • 通过 localizedDescription 对错误提示添加本地化的支持
  • Translator 类中实现但有错误发生时,通过 throws 来处理异常

结束语

我从来不掩饰我是一个狂热的 Swift 爱好者,我坚信它很可能既能像 Perl、Python 和 Ruby 这样语言一样出色的完成运维工作,也能像 C、C++ 和 Java 一样出色的完成系统编程的任务。我知道现在和那些个单文件脚本语言相比,Swift 比较蛋疼的一点就是必须得编译成二进制文件。我真诚的希望这一点能够改善,这样我就能不再关注语言层面的东西而是去做一些新,酷酷的东西。

我真诚的希望这一点能够改善,这样我就能不再关注语言层面的东西而是去做一些新,酷酷的东西。

责任编辑:陈琳 来源: SwiftGG
相关推荐

2010-07-15 10:58:23

Perl命令行程序

2019-04-16 06:50:34

2023-03-31 08:44:55

Go开发命令

2015-07-15 10:32:44

Node.js命令行程序

2022-09-23 09:50:45

Python

2022-09-27 13:07:41

clickPython命令行

2020-02-13 10:57:59

Python数据设计

2022-10-26 09:02:28

SourcerySwift

2022-02-08 13:24:49

LinuxLinux命令

2018-05-04 09:15:35

PythonPlumbum命令行

2022-02-08 17:19:05

Linux命令grep

2019-12-09 09:23:04

Linux命令sort

2009-07-14 14:03:56

Swing程序

2010-03-24 14:14:42

Python GUI

2010-03-16 12:16:23

Python小程序

2014-02-12 10:11:08

扫描病毒扫描Clam Antivi

2020-12-10 16:16:08

工具代码开发

2020-12-11 06:44:16

命令行工具开发

2009-05-30 09:26:38

AndroidGoogle移动OS

2010-07-26 09:14:22

Perl命令行
点赞
收藏

51CTO技术栈公众号