Problems encountered
When developing a Mac App, you need to execute a shell script. Below are several ways to implement this requirement and a discussion of related issues
- use
NSTask(called Process in Swfit)
performshell
- use
NSAppleScript
To borrowappleScript
performdo shell script echo "echo test"
completeshell
Script executionNSUserAppleScript
Provides implementationAppleScript
Multithreading scheme of the
There are several other apis that can execute shells. Since previous development only covered the three apis provided above, unfamiliar apis will not be discussed here
The following are discussions and examples of the three apis
NSTask
A subProcess is generated when the script is executed, which is represented by support for multithreaded invocation (multiple shell scripts can be executed asynchronously and concurrently). Unlike appleScript, NSTask does not trigger an authorization popup for the user to enter a native password when executing a shell script requiring authorization (SUdo).
Pass in the path of a shell script.
Note: After manually creating a shell script on Mac, you need to execute the chmod +x shell script path to authorize the script. Otherwise, the script cannot be executed
typealias RunShellScriptResult = (_ executeResult: String) - > ()private func runShellScript(_path: String? , onComplete: @escaping RunShellScriptResult) {
guard let path = path, FileManager.default.fileExists(atPath: path) else {
onComplete("Path does not exist!")
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()
// Get the run result
task.terminationHandler = { task in
print("Perform\(path)")
onComplete(string ?? "")}}Copy the code
Pipe is used to accept the result of executing a shell script.
NSAppleScript
AppleScript can do a lot of things on its own. We can open the script editor on the Mac and edit our own Apple scripts. I will not elaborate on this here. We use it to execute shell scripts that require sudo authorization, prompting for authorization.
Unlike NSTask/Process, the argument passed is a string of the contents of the shell script.
Do shell script “echo Command “with Administrator PRIVILEGES will add the authorization pop-up prompt.
/// Run the script command /// /// -parameters: /// -command: command line content /// -needauthorize: Whether sudo authorization is required when executing the script /// - 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(" run \n\(command)\n :") print(error) return (false, nil) } return (true, result.stringValue) }Copy the code
Problem solved by NSUserAppleScript
NSAppleScript solves the authorization problem, but it executes on the main thread. In other words, he does not support multithreaded execution. If we need to execute several shell scripts at the same time, and the previous shell is a time-consuming operation like ping, you can just wait.
I took this quote from a reference article written by Meow God
One of the nice things in NSUserAppleScriptTask is the callback handling at the end. Scripts are executed asynchronously, so your user interface is not locked by a (long) script. Be careful what you do in the closing callback, because it doesn’t run on the main thread, so you can’t make updates to your user interface there.
do {
// The URL here is the path of the shell script
let task = try NSUserAppleScriptTask.init(url: url)
task.execute(withAppleEvent: nil) { (result, error) in
varmessage = result? .stringValue ??""
// Error. DebugDescription is also part of the execution result. It is used when a timeout or the execution shell itself returns an error and we need to print the content.
message = message.count= =0 ? error.debugDescription : message
}
} catch {
// Execution related errors
}
Copy the code