开发 Mac App
的过程当中,须要执行一段 shell
脚本. 下面是实现这个需求的几种方法和相关问题的讨论html
NSTask(Swfit 中叫作 Process)
执行 shell
NSAppleScript
借用 appleScript
执行 do shell script echo "echo test"
完成 shell
脚本的执行
NSUserAppleScript
提供了执行 AppleScript
的多线程方案还有其余的几种 api
均可以执行 shell
。由于以前的开发只涉及到上面提供的三种,不熟悉的 api
这里不作讨论shell
下面是三种 API 的讨论和实例swift
执行脚本时会生成一个
subProcess
,体现为支持多线程调用(能够异步并发执行多个shell
脚本)。和appleScript
的 区别在于,当执行须要受权的shell
脚本(sudo
)时,NSTask
不会触发受权的弹窗让用户去输入本机密码。api
传入一个 shell
脚本所在的路径。markdown
注: Mac 上手动建立一个
shell
脚本以后,须要执行chmod +x shell脚本路径
来对脚本受权,不然没法执行多线程
typealias RunShellScriptResult = (_ executeResult: String) -> () private func runShellScript(_ path: String?, onComplete: @escaping RunShellScriptResult) { guard let path = path, FileManager.default.fileExists(atPath: path) else { onComplete("路径不存在!") return } let task = Process() task.launchPath = path task.arguments = [""] let outPipe = Pipe() task.standardOutput = outPipe task.launch() let fileHandle = outPipe.fileHandleForReading let data = fileHandle.readDataToEndOfFile() let string = String.init(data: data, encoding: .utf8) task.waitUntilExit() // 获取运行结果 task.terminationHandler = { task in print("执行 \(path)") onComplete(string ?? "") } } 复制代码
Pipe
用于接受执行 shell
脚本的结果。并发
AppleScript
自己能够作不少事。咱们能够在 Mac
系统之下打开脚本编辑器,编辑本身的苹果脚本。关于这块,这里不做赘述。 咱们使用它的目的,是为了执行须要 sudo
受权的 shell
脚本,能够弹出受权的提示。app
和 NSTask/Process
不一样,此时传入的参数是 shell
脚本的内容的字符串。异步
do shell script "echo command" with administrator privileges
会增长受权弹窗提示。编辑器
/// 执行脚本命令
///
/// - Parameters:
/// - command: 命令行内容
/// - needAuthorize: 执行脚本时,是否须要 sudo 受权
/// - Returns: 执行结果
private func runCommand(_ command: String, needAuthorize: Bool) -> (isSuccess: Bool, executeResult: String?) {
let scriptWithAuthorization = """
do shell script "\(command)" with administrator privileges
"""
let scriptWithoutAuthorization = """
do shell script "\(command)"
"""
let script = needAuthorize ? scriptWithAuthorization : scriptWithoutAuthorization
let appleScript = NSAppleScript(source: script)
var error: NSDictionary? = nil
let result = appleScript!.executeAndReturnError(&error)
if let error = error {
print("执行 \n\(command)\n命令出错:")
print(error)
return (false, nil)
}
return (true, result.stringValue)
}
复制代码
NSAppleScript
虽然解决了受权的问题,可是他是执行在主线程上的。换言之,他不支持多线程执行。若是咱们须要同时执行几个 shell
脚本,而前一个 shell
又是相似于 ping
这类的耗时操做,那你就只能干等着。
我从 喵神写的参考文章 摘录了下面这段话
NSUserAppleScriptTask
中很好的一个东西就是结束时候的回调处理。脚本是异步执行的,因此你的用户界面并不会被一个 (比较长) 的脚本锁住。要当心你在结束回调中作的事情,由于它并非跑在主线程上的,因此你不能在那儿对你的用户界面作更新。
do { // 这里的 URL 是 shell 脚本的路径 let task = try NSUserAppleScriptTask.init(url: url) task.execute(withAppleEvent: nil) { (result, error) in var message = result?.stringValue ?? "" // error.debugDescription 也是执行结果的一部分,有时候超时或执行 shell 自己返回错误,而咱们又须要打印这些内容的时候,就须要用到它。 message = message.count == 0 ? error.debugDescription : message } } catch { // 执行的相关错误 } 复制代码