如何使用Swift来实现一个命令行工具的方法
本文即简单介绍了如何在Swift中开发命令行工具,以及与Shell命令的交互。水文一篇,不喜勿喷。
主要是使用该工具来解析微信的性能监控组件Matrix的OOM Log。
基本模块
这里,仅简单介绍了常见的基本模块。
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 func runShell(_ command: String) -> Int32 { let task = Process() task.launchPath = "/bin/bash" task.arguments = ["-c", command] task.launch() task.waitUntilExit() return task.terminationStatus } @discardableResult func runShellWithArgs(_ args: String...) -> Int32 { let task = Process() task.launchPath = "/usr/bin/env" task.arguments = args task.launch() task.waitUntilExit() return task.terminationStatus }
使用如下:
runShell("pwd") runShell("ls -l") runShellWithArgs("pwd") runShellWithArgs("ls", "-l")
需要Shell命令的输出内容
这里就需要使用到Pipe了。
@discardableResult func runShellAndOutput(_ command: String) -> (Int32, String?) { let task = Process() task.launchPath = "/bin/bash" task.arguments = ["-c", command] let pipe = Pipe() task.standardOutput = pipe task.standardError = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8) task.waitUntilExit() return (task.terminationStatus, output) } @discardableResult func runShellWithArgsAndOutput(_ args: String...) -> (Int32, String?) { let task = Process() task.launchPath = "/usr/bin/env" task.arguments = args let pipe = Pipe() task.standardOutput = pipe task.standardError = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8) task.waitUntilExit() return (task.terminationStatus, output) }
使用如下:
let (ret1, output1) = runShellAndOutput("ls -l") if let output11 = output1 { print(output11) } let (ret2, output2) = runShellWithArgsAndOutput("ls", "-l") if let output22 = output2 { print(output2) }
如何解析Matrix的OOM Log
Matrix的OOM Log格式如下,其实就是一个大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": "Malloc 12.54 MiB", "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 命令即可进行符号化。
guard let rawLogModel = MatrixOOMLogParser.parse() else { exit(-1) } print("______ Start to process Matrix OOM Log ...") let group = DispatchGroup() var metaLog = "" for item in bodyInfo.items { guard let stacks = item.stacks else { continue } group.enter() DispatchQueue.global().async { var log = "______ item ______ name: \(item.name), size: \(item.size), count: \(item.count) \n" metaLog += log for stack in stacks { let outputs = stack.frames.map({ (frame: MatrixOOMLogModelFrame) -> String in // let uuid = frame.uuid let offset = frame.offset let instructionAddress = loadAddress + offset let (_, output) = runShellAndOutput("xcrun atos -o \(dwarf) -arch arm64 -l 0x1 \(instructionAddress.hexValue)") return output ?? "" }) log += outputs.joined() print(log) } group.leave() } } group.wait() print("\n\(metaLog)\n") print("______ Finished processing Matrix OOM Log ...")
MatrixOOMLogParser.parse() 就是将JSON转为Model,这里用的就是Swift里边的Codable。
这里有一个需要注意的点,Mac CLI没有Bundle的概念,只有一个bin文件。所以对于原始的JSON文件,只能通过外部bundle的方式来添加。通过 New->Target 单独建立一个bundle。需要在 Xcode -> Build Phases -> Copy Files 中添加该bundle名,然后即可通过 Bundle(url: mockDataBundleURL) 来加载该bundle并获取其中的log文件了。
因为atos的执行时间较长,所以大量的符号化操作会非常耗时。一般来说,这段代码执行六七分钟左右,可以将一个Matrix的OOM Log完全符号化。而符号化之后的记录如何分析,就是另外一个话题了。
参考资料
How do I run an terminal command in a swift script? (e.g. xcodebuild)
到此这篇关于如何使用Swift来实现一个命令行工具的文章就介绍到这了,更多相关Swift 命令行内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!
相关文章
- 这篇文章主要给大家介绍了关于swift中利用runtime交换方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。...2020-06-30
- 这篇文章主要介绍了C#命令行编译器配置方法,本文讲解了配置C#命令行编译器、配置其它.NET命令行工具、通过csc命令行编译器来编译C#文件实例等内容,需要的朋友可以参考下...2020-06-25
- 有时候,我们需要一个显示文字,又想这些文字与边界之间有自定义的边距,所以下面这篇文章主要给大家介绍了关于Swift设置UILabel内边距的相关资料,需要的朋友可以参考下...2021-10-14
- 这篇文章主要给大家介绍了关于swift中@UIApplicationMain的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。...2020-06-30
- 这篇文章主要为大家详细介绍了Swift实现多个TableView侧滑与切换效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-30
- 给大家详细讲解了IOS开发中swift语言xcworkspace多项目管理的方法和介绍,一起参考一下。...2020-06-30
Swift 中如何使用 Option Pattern 改善可选项的 API 设计
这篇文章主要介绍了Swift 中如何使用 Option Pattern 改善可选项的 API 设计,帮助大家更好的进行ios开发,感兴趣的朋友可以了解下...2020-10-23- 这篇文章主要介绍了如何使用Swift来实现一个命令行工具,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
- 这篇文章主要给大家介绍了关于Swift中命名空间的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
浅析Swift中struct与class的区别(汇编角度底层分析)
这篇文章主要介绍了Swift中struct与class的区别 ,本文从汇编角度分析struct与class的区别,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-06-30- 最近有一个需求,就是将图片先等比例缩放到指定大小,然后将空余出来空间填充为黑色,返回指定大小的图片。本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧...2021-11-02
- 这篇文章主要给大家介绍了关于Swift中优雅的处理闭包导致的循环引用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Swift具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-30
- 这篇文章主要为大家详细介绍了Swift实现倒计时5秒功能,在“登录”和“注册”页面也有相似功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-30
- 这篇文章主要介绍了Swift 使用 Observe 监测页面滚动的实现方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-06-30
- 这篇文章主要介绍了Swift在控件中添加点击手势的方法,本文讲解如何在tableview的headerview中添加点击手势的方法,需要的朋友可以参考下...2020-06-30
- 有很多地方会用到类型擦除,并且它们的作用的各不相同。下面这篇文章主要给大家介绍了关于Swift如何使用类型擦除及自定义的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下...2020-06-30
- 这篇文章主要介绍了Jmeter如何基于命令行运行jmx脚本,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-22
- 这篇文章主要给大家介绍了关于Swift利用CoreData如何存储多种数据类的通讯录的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧。...2020-06-30
- 这篇文章主要介绍了SwiftUI 中创建反弹动画的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-30
- 这篇文章主要介绍了Swift中动态调用实例方法介绍,在Swift中有一类很有意思的写法,可以让我们不直接使用实例来调用这个实例上的方法,而是通过类型取出这个类型的某个实例方法的签名,然后再通过传递实例来拿到实际需要调用的方法,需要的朋友可以参考下...2020-06-30