如何使用Swift来实现一个命令行工具的方法
本文即简单介绍了如何在Swift中开发命令行工具,以及与Shell命令的交互。水文一篇,不喜勿喷。
主要是使用该工具来解析微信的性能监控组件Matrix的OOMLog。
基本模块
这里,仅简单介绍了常见的基本模块。
Process
Process类可以用来打开另外一个子进程,并监控其运行情况。
- launchPath:指定了执行路径。如可以设置为/usr/bin/env,这个命令可以用于打印本机上所有的环境变量;也可以用于执行shell命令,如果你接了参数的话。本文的Demo就用它来执行输入的命令。
- arguments:参数,以数组形式传递即可。
- launch:调用launch函数即可启动process,用于执行命令。
- waitUntilExit:一般执行Shell命令,需要等待命令返回。
- terminationStatus:当前process的结束状态,正常为0.
- standardOutput:standardOutput对应于终端的标准输出。standardError则是错误输出。
Pipe
Pipe这个类就是操作系统的管道,在这里用来接受子进程的输出。这里,可以用于将process的输出传递至管道指定的地方,如一个output变量,或者文件也可以。
- fileHandleForReading:pipe从哪里读取内容?
- fileHandleForWriting:pipe将内容写到哪里?
CommandLine
用于获取脚本参数而已。
print(CommandLine.argc)//2 print(CommandLine.arguments)//["./test.swift","hello"]
封装Shell命令
仅执行Shell命令
这里提供了两种调用Shell命令的封装函数,个人更倾向于第二种,直接将Shell命令及参数封装成一个字符串传入即可。
@discardableResult funcrunShell(_command:String)->Int32{ lettask=Process() task.launchPath="/bin/bash" task.arguments=["-c",command] task.launch() task.waitUntilExit() returntask.terminationStatus } @discardableResult funcrunShellWithArgs(_args:String...)->Int32{ lettask=Process() task.launchPath="/usr/bin/env" task.arguments=args task.launch() task.waitUntilExit() returntask.terminationStatus }
使用如下:
runShell("pwd") runShell("ls-l") runShellWithArgs("pwd") runShellWithArgs("ls","-l")
需要Shell命令的输出内容
这里就需要使用到Pipe了。
@discardableResult funcrunShellAndOutput(_command:String)->(Int32,String?){ lettask=Process() task.launchPath="/bin/bash" task.arguments=["-c",command] letpipe=Pipe() task.standardOutput=pipe task.standardError=pipe task.launch() letdata=pipe.fileHandleForReading.readDataToEndOfFile() letoutput=String(data:data,encoding:.utf8) task.waitUntilExit() return(task.terminationStatus,output) } @discardableResult funcrunShellWithArgsAndOutput(_args:String...)->(Int32,String?){ lettask=Process() task.launchPath="/usr/bin/env" task.arguments=args letpipe=Pipe() task.standardOutput=pipe task.standardError=pipe task.launch() letdata=pipe.fileHandleForReading.readDataToEndOfFile() letoutput=String(data:data,encoding:.utf8) task.waitUntilExit() return(task.terminationStatus,output) }
使用如下:
let(ret1,output1)=runShellAndOutput("ls-l") ifletoutput11=output1{ print(output11) } let(ret2,output2)=runShellWithArgsAndOutput("ls","-l") ifletoutput22=output2{ print(output2) }
如何解析Matrix的OOMLog
Matrix的OOMLog格式如下,其实就是一个大JSON:
{ "head":{ "protocol_ver":1, "phone":"iPhone10,1", "os_ver":"13.4", "launch_time":1589361495000, "report_time":1589362109100, "app_uuid":"" }, "items":[ { "tag":"iOS_MemStat", "info":"", "scene":"", "name":"Malloc12.54MiB", "size":146313216, "count":1, "stacks":[ { "caller":"f07199ac8a903127b17f0a906ffb0237@84128", "size":146313216, "count":1, "frames":[ { "uuid":"a0a7d67af0f3399a8f006f92716d8e6f", "offset":67308 }, { "uuid":"a0a7d67af0f3399a8f006f92716d8e6f", "offset":69836 }, { "uuid":"f07199ac8a903127b17f0a906ffb0237", "offset":84128 }, { "uuid":"b80198f7beb93e79b25c7a27d68bb489", "offset":14934312 }, { "uuid":"1a46239df2fc34b695bc9f38869f0c85", "offset":1126304 }, { "uuid":"1a46239df2fc34b695bc9f38869f0c85", "offset":123584 }, { "uuid":"1a46239df2fc34b695bc9f38869f0c85", "offset":1135100 }] } ] } ] }
解析的思路其实非常简单,将JSON转为Model,然后根据所需,提取对应的信息即可。
uuid是mach-o的唯一标识,offset则是符号相对于mach-o基地址的偏移量。拿到dSYM文件,使用atos命令即可进行符号化。
guardletrawLogModel=MatrixOOMLogParser.parse()else{exit(-1)} print("______StarttoprocessMatrixOOMLog...") letgroup=DispatchGroup() varmetaLog="" foriteminbodyInfo.items{ guardletstacks=item.stackselse{continue} group.enter() DispatchQueue.global().async{ varlog="______item______name:\(item.name),size:\(item.size),count:\(item.count)\n" metaLog+=log forstackinstacks{ letoutputs=stack.frames.map({(frame:MatrixOOMLogModelFrame)->Stringin //letuuid=frame.uuid letoffset=frame.offset letinstructionAddress=loadAddress+offset let(_,output)=runShellAndOutput("xcrunatos-o\(dwarf)-archarm64-l0x1\(instructionAddress.hexValue)") returnoutput??"" }) log+=outputs.joined() print(log) } group.leave() } } group.wait() print("\n\(metaLog)\n") print("______FinishedprocessingMatrixOOMLog...")
MatrixOOMLogParser.parse()就是将JSON转为Model,这里用的就是Swift里边的Codable。
这里有一个需要注意的点,MacCLI没有Bundle的概念,只有一个bin文件。所以对于原始的JSON文件,只能通过外部bundle的方式来添加。通过New->Target单独建立一个bundle。需要在Xcode->BuildPhases->CopyFiles中添加该bundle名,然后即可通过Bundle(url:mockDataBundleURL)来加载该bundle并获取其中的log文件了。
因为atos的执行时间较长,所以大量的符号化操作会非常耗时。一般来说,这段代码执行六七分钟左右,可以将一个Matrix的OOMLog完全符号化。而符号化之后的记录如何分析,就是另外一个话题了。
参考资料
HowdoIrunanterminalcommandinaswiftscript?(e.g.xcodebuild)
到此这篇关于如何使用Swift来实现一个命令行工具的文章就介绍到这了,更多相关Swift命令行内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!