開發 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 { // 執行的相關錯誤 } 複製代碼