当前位置: 首页 > 科技观察

在Swift中编写命令行应用程序

时间:2023-03-15 14:49:56 科技观察

这是探索在Swift中编写Linux应用程序的系列文章中的一篇。在前面的例子中,我们使用popen和wget命令的组合来调用自然语言翻译服务来实现类似谷歌翻译的翻译功能。本文中的程序将基于我们之前所做的工作。但是与之前每次执行只能翻译一个句子不同的是,这次我们需要实现一个具有交互功能的shell程序来翻译在控制台输入的每一个句子。就像下面的截图:翻译器会显示它接受什么语言(源语言)和翻译的目标语言。例如:en->es英文翻译成西班牙文es->it西班牙文翻译成意大利文it->ru意大利文翻译成俄文默认翻译程序是en->es,提供两个命令:to和fromto切换语言。例如,键入es会将翻译目标语言设置为西班牙语。输入quit退出程序。如果用户输入的字符串不是命令,翻译器会将输入逐字发送到翻译网络服务。然后打印出返回的结果。注意事项如果您是系统或运维程序员并且以前从未接触过Swift,那么在您的代码中需要注意以下几点。我认为您会发现Swift为这两种类型的工程师提供了很多有用的功能,并且将成为Linux开发生态系统的一个受欢迎的补充。letvariable=valueconstantassignmenttuples(元组)switch-case支持字符串switch-case必须包含使用时的所有case(逻辑完整性)计算属性importGlibc可以导入标准C函数guard语句可以使用NSThread和NSNotificationCenter这些Apple'sFoundation框架中的类.在不同的线程或不同的对象中发送消息触发特定代码的执行编程我们的翻译程序可以拆分为一个主程序,两个类和一个globals.swift文件。如果你打算效仿,你应该使用Swift的包管理器,并将目录结构调整为如下所示:translator/Sources/main.swift/Sources/CommandInterpreter.swift/Sources/.../Package.swiftmain。swift文件是Swift应用程序的入口点,应该是唯一包含可执行代码的文件(这里,“分配变量”或“声明类”之类的东西不是“可执行代码”)。main.swift:importFoundationimportGlibcletinterpreter=CommandInterpreter()lettranslator=Translator()//Listenforeventstotranslatenc.addObserverForName(INPUT_NOTIFICATION,object:nil,queue:nil){(_)inlettc=translationCommandtranslator.translate(tc.text,from:tc.from,to:tc.to){translation,erroringuarderror==nil&&translation!=nilelse{print("Translationfailure:\(error!.code)")return}print(translation!)}}解释器.start()select(0,nil,nil,nil,nil)上面的代码意味着我们的程序不接受命令行参数。具体过程说明:分别创建CommandInterpreter和Translator类实例,为InputNotification通知添加观察者(这里使用的常量INPUT_NOTIFICATION常量定义在globals.swift中)并添加收到通知时执行的代码调用Interpreter类的开始instance方法在程序有其他线程运行时调用select锁定主线程。(译注:也就是防止主线程提前结束)CommandInterpreter类CommandInterpreter类主要负责从终端读取输入的字符串,分析输入的类型并单独处理。考虑到你可能是Swift的新手,我在代码中对语言特性进行了注释。//ImportstatementsimportFoundationimportGlibc//EnumerationsenumCommandType{caseNonecaseTranslatecaseSetFromcaseSetTocaseQuit}//StructsstructCommand{vartype:CommandTypevardata:String}//ClassesclassCommandInterpreter{//Read-onlycomputedpropertyvarprompt:String{return"\(translationCommand.from)->\(translationCommand.to)"}//Classconstantletdelim:Character="\n"init(){}funcstart(){letreadThread=NSThread(){varinput:String=""print("Tosetinputlanguage,type'fromLANG'")print("Tosetoutputlanguage,type'toLANG'")print("Type'quit'toexit")self.displayPrompt()whiletrue{letc=Character(UnicodeScalar(UInt32(fgetc(stdin))))ifc==self.delim{letcommand=self.parseInput(input)self.doCommand(command)input=""//Clearinputself.displayPrompt()}else{input.append(c)}}}readThread.start()}funcdisplayPrompt(){打印(“\(self.prompt):”,终止符:"")}funcparseInput(input:String)->Command{varcommandType:CommandTypevarcommandData:String=""//Splittingastringlettokens=input.characters.split{$0==""}.map(String.init)//guardstatementtovalidatethatherearetokensguardtokens.count>0else{returnCommand(type:CommandType.None,data:"")}switchtokens[0]{case"quit":commandType=.Quitcase"from":commandType=.SetFromcommandData=tokens[1]案例“to”:commandType=.SetTocommandData=tokens[1]默认值:commandType=.TranslatecommandData=input}returnCommand(type:commandType,data:commandData)}funcdoCommand(command:Command){switchcommand.type{case.Quit:退出(0)case.SetFrom:translationCommand.from=command.datacase.SetTo:translationCommand.to=command.datacase.Translate:translationCommand.text=command.datanc.postNotificationName(INPUT_NOTIFICATION,object:nil)case.None:break}}}CommandInterpreter类的实时发布非常直播调用start函数时,通过NSThread创建一个线程,在线程中,通过blockfgetc的回调参数stdin获取终端的输入。当遇到换行符RETURN(用户按下Enter)时,输入的字符串将被解析并映射到一个Command对象中。然后将它传递给doCommand函数来完成剩下的工作。我们的doCommand函数是一个简单的switch-case语句。对于.Quit命令,只需调用exit(0)即可终止程序。.SetFrom和.SetTo命令的功能是不言自明的。说到.Translate命令,Foundation的消息系统就派上用场了。doCommand函数本身不执行任何翻译功能,它只是发送一条应用程序级别的消息,即InputNotification。任何监听这个消息的代码都会被调用(比如我们之前的主线程):,from:tc.from,to:tc.to){translation,erroringuarderror==nil&&translation!=nilelse{print("Translationfailure:\(error!.code)")return}print(translation!)}}我是这里文中提到,转换NSNotification的userInfo字典时会出现SILGen崩溃。这里我们使用一个名为translationCommand的全局变量来绕过这个崩溃。这段代码中:为了代码简洁,将translationCommand的内容赋值给tc,调用Translator对象的translate方法,传入相关参数实现翻译后的回调。调用一个Swift漂亮的guard语句来检测是否有错误并返回并打印出翻译后的文本。TranslatorTranslator类在本文中首次引入,我们这里直接复用:importGlibcimportFoundationimportCcURLimportCJSONCclassTranslator{letBUFSIZE=1024init(){}functranslate(text:String,from:String,to:String,completion:(translation:String?,error:NSError?)->Void){letcurl=curl_easy_init()guardcurl!=nilelse{completion(translation:nil,error:NSError(domain:"translator",code:1,userInfo:nil))return}letescapedText=curl_easy_escape(curl,text,Int32(strlen(text)))guardescapedText!=nilelse{completion(translation:nil,error:NSError(domain:"translator",code:2,userInfo:nil))return}letlangPair=from+"%7c"+toletwgetCommand="wget-qO-http://api.mymemory.translated.net/get\\?q\\="+String.fromCString(escapedText)!+"\\&langpair\\="+langPairletpp=popen(wgetCommand,"r")varbuf=[CChar](count:BUFSIZE,repeatedValue:CChar(0))varresponse:String=""whilefgets(&buf,Int32(BUFSIZE),pp)!=nil{responresponse=response+String.fromCString(buf)!}lettranslation=getTranslatedText(response)guardtranslation.error==nilelse{completion(translation:nil,error:translation.error)return}completion(translation:translation.translation,error:nil)}privatefuncgetTranslatedText(jsonString:String)->(error:NSError?,translation:String?){letobj=json_tokener_parse(jsonString)guardobj!=nilelse{return(NSError(domain:"translator",code:3,userInfo:nil),nil)}letresponseData=json_object_object_get(obj,“responseData”)域:“翻译器”,代码:3,你serInfo:nil),nil)}lettranslatedTextStr=json_object_get_string(translatedTextObj)return(nil,String.fromCString(translatedTextStr)!)}}整合这些部分为了组合上述组件,我们需要创建两个额外的文件:globals.swift和Package.swiftglobals.swift:importFoundationletINPUT_NOTIFICATION="InputNotification"letnc=NSNotificationCenter.defaultCenter()structTranslationCommand{varfrom:Stringvarto:Stringvartext:String}vartranslationCommand:TranslationCommand=TranslationCommand(from:"en",to:"es",text:"")Package.swift:importPackageDescriptionletpackage=Package(name:"translator",dependencies:[.Package(url:"https://github.com/iachievedit/CJSONC",majorVersion:1),.Package(url:"https://github.com/PureSwift/CcURL",majorVersion:1)])如果一切配置正确,最后执行swiftbuild,一个很有特色的翻译程序就完成了。swiftbuild克隆https://github.com/iachievedit/CJSONC使用版本1.0.0ofpackageCJSONC克隆https://github.com/PureSwift/CcURL使用版本1.0.0ofpackageCcURLCompilingSwiftModule'translator'(4sources)LinkingExecutable:.build/debug/translatorTryityourselfThere目前的翻译程序还有很多可以优化的地方。以下是您可以尝试的事项列表:接受命令行参数以设置默认源语言和目标语言接受非交互模式的命令行参数添加交换命令以交换源语言和目标语言添加帮助命令合并从和到命令。实现一行可以同时设置两者,比如fromentoes现在输入from命令和to命令时,没有同时输入对应的语言会崩溃。修复此BUG并实现转义字符\的处理,实现程序的“命令”也可以翻译(如退出命令:quit)通过localizedDescription添加错误提示的本地化支持在Translator类中实现但是当发生错误时,使用throws来处理异常结论尝试自己做还有很多翻译程序可以优化。这里是您可以尝试的列表:接受命令行参数以设置默认源语言和目标语言接受非交互模式的命令行参数添加交换命令以交换源语言和目标语言添加帮助命令合并来自和至命令。实现一行可以同时设置两者,比如fromentoes现在输入from命令和to命令时,没有同时输入对应的语言会崩溃。修复此BUG并实现转义符\的处理,实现程序的“命令”也可以翻译(如退出命令:quit)通过localizedDescription添加本地化支持错误提示,实现在Translator类中但是发生错误时,通过throws处理异常结语我从不掩饰自己是狂热的Swift爱好或者,我坚信它很可能和Perl、Python、等语言一样擅长运维Ruby,在系统编程的任务上也可以和C、C++、Java媲美。我知道现在比起那些单文件的脚本语言,Swift比较痛苦的一个地方就是它必须是编译成一个二进制文件。我真诚地希望这会有所改善,这样我就可以停止专注于语言方面的东西,而去研究新的、很酷的东西。我真诚地希望这会有所改进,这样我就可以不再专注于语言方面的东西,而去研究新的、很酷的东西。