Error[8]: Undefined offset: 82, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

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

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

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

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

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

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

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

需要注意的几点

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

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

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

translator/Sources/main.swift          /Sources/CommandInterpreter.swift          /Sources/...          /Package.swift

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

main.swift

import Foundationimport Glibclet interpreter = CommandInterpreter()let translator  = Translator()// Listen for events to translatenc.addobserverForname(input_NOTIFICATION,object:nil,queue:nil) {  (_) in  let tc = translationCommand  translator.translate(tc.text,from:tc.from,to:tc.to){    translation,error in    guard error == nil && translation != nil else {      print("Translation failure:  \(error!.code)")      return    }    print(translation!)  }}interpreter.start()select(0,nil,nil)

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

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

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

// import statementsimport Foundationimport Glibc// Enumerationsenum CommandType {case Nonecase Translatecase SetFromcase SetTocase Quit}// Structsstruct Command {  var type:CommandType  var data:String}// Classesclass CommandInterpreter {  // Read-only computed property  var prompt:String {    return "\(translationCommand.from)->\(translationCommand.to)"  }  // Class constant  let delim:Character = "\n"  init() {  }  func start() {    let readThread = NSThread(){      var input:String    = ""      print("To set input language,type 'from LANG'")      print("To set output language,type 'to LANG'")      print("Type 'quit' to exit")      self.displayPrompt()      while true {        let c = Character(UnicodeScalar(UInt32(fgetc(stdin))))        if c == self.delim {          let command = self.parseinput(input)          self.doCommand(command)          input = "" // Clear input          self.displayPrompt()        } else {          input.append(c)        }      }    }    readThread.start()  }  func displayPrompt() {    print("\(self.prompt):  ",terminator:"")  }  func parseinput(input:String) -> Command {    var commandType:CommandType    var commandData:String = ""    // Splitting a string    let tokens = input.characters.split{CommandInterpreter == " "}.map(String.init)    // guard statement to valIDate that there are tokens    guard tokens.count > 0 else {      return Command(type:CommandType.None,data:"")    }    switch tokens[0] {    case "quit":      commandType = .Quit    case "from":      commandType = .SetFrom      commandData = tokens[1]    case "to":      commandType = .SetTo      commandData = tokens[1]    default:      commandType = .Translate      commandData = input    }    return Command(type:commandType,data:commandData)  }  func doCommand(command:Command) {    switch command.type {    case .Quit:      exit(0)    case .SetFrom:      translationCommand.from = command.data    case .SetTo:      translationCommand.to   = command.data    case .Translate:      translationCommand.text = command.data      nc.postNotificationname(input_NOTIFICATION,object:nil)       case .None:      break    }  }}

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

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

// Listen for events to translatenc.addobserverForname(input_NOTIFICATION,error in    guard error == nil && translation != nil else {      print("Translation failure:  \(error!.code)")      return    }    print(translation!)  }}
。任何监听这个消息的代码都会被调用(比如我们之前的主线程):

NSNotification

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

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

import Glibcimport Foundationimport CcURLimport CJsONCclass Translator {  let BUFSIZE = 1024  init() {  }  func translate(text:String,from:String,to:String,completion:(translation:String?,error:NSError?) -> VoID) {    let curl = curl_easy_init()    guard curl != nil else {      completion(translation:nil,error:NSError(domain:"translator",code:1,userInfo:nil))      return    }    let escapedText = curl_easy_escape(curl,text,Int32(strlen(text)))    guard escapedText != nil else {      completion(translation:nil,code:2,userInfo:nil))      return    }    let langPair = from + "%7c" + to    let wgetCommand = "wget -qO- http://API.mymemory.translated.net/get\?q\=" + String.fromCString(escapedText)! + "\&langpair\=" + langPair    let pp      = popen(wgetCommand,"r")    var buf     = [CChar](count:BUFSIZE,repeatedValue:CChar(0))    var response:String = ""    while fgets(&buf,Int32(BUFSIZE),pp) != nil {      response = response + String.fromCString(buf)!    }    let translation = getTranslatedText(response)    guard translation.error == nil else {      completion(translation:nil,error:translation.error)      return    }    completion(translation:translation.translation,error:nil)  }  private func getTranslatedText(JsonString:String) -> (error:NSError?,translation:String?) {    let obj = Json_tokener_parse(JsonString)    guard obj != nil else {      return (NSError(domain:"translator",code:3,userInfo:nil),nil)    }    let responseData = Json_object_object_get(obj,"responseData")    guard responseData != nil else {      return (NSError(domain:"translator",nil)    }    let translatedTextObj = Json_object_object_get(responseData,"translatedText")    guard translatedTextObj != nil else {      return (NSError(domain:"translator",nil)    }    let translatedTextStr = Json_object_get_string(translatedTextObj)    return (nil,String.fromCString(translatedTextStr)!)  }}
类最开始是在这篇文章中介绍的,我们在这里直接重用:

globals.swift 整合各个部分

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

import Foundationlet input_NOTIFICATION   = "inputNotification"let nc = NSNotificationCenter.defaultCenter()struct TranslationCommand {  var from:String  var to:String  var text:String}var translationCommand:TranslationCommand = TranslationCommand(from:"en",to:"es",text:"")

Package.swift

import PackageDescriptionlet package = Package(  name:  "translator",dependencIEs: [    .Package(url:  "https://github.com/iachIEvedit/CJsONC",majorVersion: 1),.Package(url:  "https://github.com/Pureswift/CcURL",majorVersion: 1)  ])

swift build

如果一切都配置正确的话,最后执行

swift buildcloning https://github.com/iachIEvedit/CJsONCUsing version 1.0.0 of package CJsONCcloning https://github.com/Pureswift/CcURLUsing version 1.0.0 of package CcURLCompiling Swift Module 'translator' (4 sources)linking Executable:  .build/deBUG/translator
,一个极具特色的翻译程序就完成了。

cmdline_translator

如果你打算从现成的代码开始学习,可以从Github上获取本站的代码,然后找到swap目录。

试试自己动手

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

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

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

总结

以上是内存溢出为你收集整理的用 Swift 来写命令行程序全部内容,希望文章能够帮你解决用 Swift 来写命令行程序所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
用 Swift 来写命令行程序_app_内存溢出

用 Swift 来写命令行程序

用 Swift 来写命令行程序,第1张

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

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

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

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

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

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

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

需要注意的几点

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

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

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

translator/Sources/main.swift          /Sources/CommandInterpreter.swift          /Sources/...          /Package.swift

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

main.swift

import Foundationimport Glibclet interpreter = CommandInterpreter()let translator  = Translator()// Listen for events to translatenc.addobserverForname(input_NOTIFICATION,object:nil,queue:nil) {  (_) in  let tc = translationCommand  translator.translate(tc.text,from:tc.from,to:tc.to){    translation,error in    guard error == nil && translation != nil else {      print("Translation failure:  \(error!.code)")      return    }    print(translation!)  }}interpreter.start()select(0,nil,nil)

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

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

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

// import statementsimport Foundationimport Glibc// Enumerationsenum CommandType {case Nonecase Translatecase SetFromcase SetTocase Quit}// Structsstruct Command {  var type:CommandType  var data:String}// Classesclass CommandInterpreter {  // Read-only computed property  var prompt:String {    return "\(translationCommand.from)->\(translationCommand.to)"  }  // Class constant  let delim:Character = "\n"  init() {  }  func start() {    let readThread = NSThread(){      var input:String    = ""      print("To set input language,type 'from LANG'")      print("To set output language,type 'to LANG'")      print("Type 'quit' to exit")      self.displayPrompt()      while true {        let c = Character(UnicodeScalar(UInt32(fgetc(stdin))))        if c == self.delim {          let command = self.parseinput(input)          self.doCommand(command)          input = "" // Clear input          self.displayPrompt()        } else {          input.append(c)        }      }    }    readThread.start()  }  func displayPrompt() {    print("\(self.prompt):  ",terminator:"")  }  func parseinput(input:String) -> Command {    var commandType:CommandType    var commandData:String = ""    // Splitting a string    let tokens = input.characters.split{CommandInterpreter == " "}.map(String.init)    // guard statement to valIDate that there are tokens    guard tokens.count > 0 else {      return Command(type:CommandType.None,data:"")    }    switch tokens[0] {    case "quit":      commandType = .Quit    case "from":      commandType = .SetFrom      commandData = tokens[1]    case "to":      commandType = .SetTo      commandData = tokens[1]    default:      commandType = .Translate      commandData = input    }    return Command(type:commandType,data:commandData)  }  func doCommand(command:Command) {    switch command.type {    case .Quit:      exit(0)    case .SetFrom:      translationCommand.from = command.data    case .SetTo:      translationCommand.to   = command.data    case .Translate:      translationCommand.text = command.data      nc.postNotificationname(input_NOTIFICATION,object:nil)       case .None:      break    }  }}

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

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

// Listen for events to translatenc.addobserverForname(input_NOTIFICATION,error in    guard error == nil && translation != nil else {      print("Translation failure:  \(error!.code)")      return    }    print(translation!)  }}
。任何监听这个消息的代码都会被调用(比如我们之前的主线程):

NSNotification

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

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

import Glibcimport Foundationimport CcURLimport CJsONCclass Translator {  let BUFSIZE = 1024  init() {  }  func translate(text:String,from:String,to:String,completion:(translation:String?,error:NSError?) -> VoID) {    let curl = curl_easy_init()    guard curl != nil else {      completion(translation:nil,error:NSError(domain:"translator",code:1,userInfo:nil))      return    }    let escapedText = curl_easy_escape(curl,text,Int32(strlen(text)))    guard escapedText != nil else {      completion(translation:nil,code:2,userInfo:nil))      return    }    let langPair = from + "%7c" + to    let wgetCommand = "wget -qO- http://API.mymemory.translated.net/get\?q\=" + String.fromCString(escapedText)! + "\&langpair\=" + langPair    let pp      = popen(wgetCommand,"r")    var buf     = [CChar](count:BUFSIZE,repeatedValue:CChar(0))    var response:String = ""    while fgets(&buf,Int32(BUFSIZE),pp) != nil {      response = response + String.fromCString(buf)!    }    let translation = getTranslatedText(response)    guard translation.error == nil else {      completion(translation:nil,error:translation.error)      return    }    completion(translation:translation.translation,error:nil)  }  private func getTranslatedText(JsonString:String) -> (error:NSError?,translation:String?) {    let obj = Json_tokener_parse(JsonString)    guard obj != nil else {      return (NSError(domain:"translator",code:3,userInfo:nil),nil)    }    let responseData = Json_object_object_get(obj,"responseData")    guard responseData != nil else {      return (NSError(domain:"translator",nil)    }    let translatedTextObj = Json_object_object_get(responseData,"translatedText")    guard translatedTextObj != nil else {      return (NSError(domain:"translator",nil)    }    let translatedTextStr = Json_object_get_string(translatedTextObj)    return (nil,String.fromCString(translatedTextStr)!)  }}
类最开始是在这篇文章中介绍的,我们在这里直接重用:

globals.swift 整合各个部分

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

import Foundationlet input_NOTIFICATION   = "inputNotification"let nc = NSNotificationCenter.defaultCenter()struct TranslationCommand {  var from:String  var to:String  var text:String}var translationCommand:TranslationCommand = TranslationCommand(from:"en",to:"es",text:"")

Package.swift

import PackageDescriptionlet package = Package(  name:  "translator",dependencIEs: [    .Package(url:  "https://github.com/iachIEvedit/CJsONC",majorVersion: 1),.Package(url:  "https://github.com/Pureswift/CcURL",majorVersion: 1)  ])

swift build

如果一切都配置正确的话,最后执行

swift buildcloning https://github.com/iachIEvedit/CJsONCUsing version 1.0.0 of package CJsONCcloning https://github.com/Pureswift/CcURLUsing version 1.0.0 of package CcURLCompiling Swift Module 'translator' (4 sources)linking Executable:  .build/deBUG/translator
,一个极具特色的翻译程序就完成了。

cmdline_translator

如果你打算从现成的代码开始学习,可以从Github上获取本站的代码,然后找到swap目录。

试试自己动手

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

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

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

总结

以上是内存溢出为你收集整理的用 Swift 来写命令行程序全部内容,希望文章能够帮你解决用 Swift 来写命令行程序所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1090735.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存