Reputation: 6891
If you go to /usr/bin
you will see hundreds of executables or links to executables.
My application (Mac app written in Obj-C in Xcode) relies on some of these executables. Unfortunately, the executables must be installed manually - I have to check for them and then prompt the user to install them.
My code is:
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/executable"];
NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
NSFileHandle *file = [pipe fileHandleForReading];
[task setArguments:[NSArray arrayWithObjects:sender, target, nil]];
[task launch];
I was wondering if it possible to copy the executable inside my app somewhere and then call it from there. That way, users wouldn't have to go through getting it for themselves.
And would it be allowed by the Mac App Store?
Upvotes: 4
Views: 3042
Reputation: 6384
I updated hbowie's answer to return the value received from the shell as a string:
func safeShell(_ strExecutable:String, _ arrArguments:Array<String>) throws -> String {
let task = Process()
let pipe = Pipe()
let bundle = Bundle.main
let execURL = bundle.url(forResource: strExecutable, withExtension: nil)
//no point in going on if we can't find the program
guard execURL != nil else {
print("\(strExecutable) executable could not be found!")
return ""
}
task.executableURL = execURL!
task.arguments = arrArguments
task.standardOutput = pipe
task.standardError = pipe
//task.arguments = ["-c", command]
//task.executableURL = URL(fileURLWithPath: "/bin/zsh") //<--updated
do {
try task.run() //<--updated
}
catch{ throw error }
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
}
Use example:
let strOutput = try myAppManager.safeShell("pandoc", ["--version"])
print(strOutput)
Returns:
pandoc 2.16.2
Compiled with pandoc-types 1.22.1, texmath 0.12.3.2, skylighting 0.12.1,
citeproc 0.6, ipynb 0.1.0.2
User data directory: /Users/stevesuranie/Library/Containers/MartianTribe.MD2HTML/Data/.local/share/pandoc
Copyright (C) 2006-2021 John MacFarlane. Web: https://pandoc.org
This is free software; see the source for copying conditions. There is no
warranty, not even for merchantability or fitness for a particular purpose.
Upvotes: 0
Reputation: 21
Just to update and clarify, I found the following to work in Xcode 11 and Swift 5.
First, dragged and dropped the desired executable (pandoc, in my case) into my Xcode project. I placed this in a group/folder named 'bin'.
Made sure to select the desired Targets in which I wished to have the executable included!
Then used the following code:
let task = Process()
let bundle = Bundle.main
let execURL = bundle.url(forResource: "pandoc", withExtension: nil)
guard execURL != nil else {
print("Pandoc executable could not be found!")
return
}
task.executableURL = execURL!
task.arguments = ["--version"]
do {
try task.run()
print("Pandoc executed successfully!")
} catch {
print("Error running Pandoc: \(error)")
}
Upvotes: 1
Reputation: 975
In case you wonder, in Swift 3 you can do this:
let bundle = Bundle.main
let task = Process()
let path = bundle.path(forResource: "executableName", ofType: "")
task.launchPath = path
Upvotes: 1
Reputation: 319
In Swift, you can do this:
let bundle = NSbundle.mainBundle()
let task = NSTask()
task.launchPath = bundle.pathForResource("executableName", ofType: "")
Make sure the bundled executable is in the top level of the directory tree, not in a sub-directory like /lib, or it might not work.
Upvotes: 0
Reputation: 6891
Ok, so I set out to find the answer for myself by playing around. And, I'm happy to say that I figured it out!
So, just copy the executable
into the Xcode project wherever you want. Then, change
[task setLaunchPath:@"/usr/bin/executable"];
to
[task setLaunchPath:[[NSBundle mainBundle] pathForResource:@"executable" ofType:@""]];
And ta da!
Upvotes: 0