How to run a git command from macOS app in Swift?
You are passing a single argument to the git
command: "clone https://github.com/user/repo.git"
. Instead, you need to pass "clone" and the URL as separate arguments:
shell("git", "clone", "https://github.com/user/repo.git")
swift command line tool for git commands, but its no
You are using deprecated methods in your code and there are some other things missing.
First we should set the shell to use
func run(with args: String...){
let task = Process()
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
Then instead of using the deprecated launchPath
we build a string with the full command and set it as the arguments for the task
let arguments = "/usr/bin/git \(args.joined(separator: " "))"
task.arguments = ["-c", arguments]
I also think it is a good idea to handle any errors by checking standard error
let pipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = pipe
task.standardError = errorPipe
Instead of using the deprecated launch
method use run
and read both standard out and standard error
do {
try task.run()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if !data.isEmpty {
if let output = String(data: data, encoding: .utf8) {
print(output)
}
}
let error = errorPipe.fileHandleForReading.readDataToEndOfFile()
if !error.isEmpty {
if let errorMessage = String(data: error, encoding: .utf8) {
print(errorMessage)
}
}
} catch {
print(error)
}
For a simple command it might be worth having the handling of standard output and standard error in an if/else
so feel free to change that but for more complicated commands dealing for example with multiple files it might produce both output and errors
Swift Execute command line command in Sandbox mode
Yeah, I already thought so, but I was thinking about if it is possible to explicitly ask the user to give the application the permission for this specific folder
You are thinking in the right direction. Clearly sandboxes apps would be pretty useless if they couldn't access files and folders, and the key to them doing so is the user granting them permission. This permission is granted using the standard open and save dialogs, when the user selects a file/folder the app is granted permission to read/write/create it.
Need access to a specific file/folder? Customise the open dialog to guide the user to select that particular file/folder, and check the result to make sure they have - handling the situation when they choose not to.
Wish your app to retain across launches the access right granted by the user to to a file/folder? Then you need to create and store a security scoped bookmark.
Time to do some reading, if you get stuck ask a new question and someone will undoubtedly help you. Remember to show what you've found out, code you've written, etc.
HTH
How do I run a terminal command in a Swift script? (e.g. xcodebuild)
If you don't use command outputs in Swift code, following would be sufficient:
#!/usr/bin/env swift
import Foundation
@discardableResult
func shell(_ args: String...) -> Int32 {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
shell("ls")
shell("xcodebuild", "-workspace", "myApp.xcworkspace")
Updated: for Swift3/Xcode8
How to launch a Command Line Tool from app in Swift?
I solved this by going to the .entitlements file within Xcode and selecting 'NO' for both 'App Sandbox' and 'com.apple.security.files.user'.
How to run terminal command in swift from any directory?
There is a deprecated property currentDirectoryPath
on Process
.
On the assumption you won't want to use a deprecated property, after reading its documentation head over to the FileManager
and look at is provisions for managing the current directory and their implications.
Or just use cd
as you've considered – you are launching a shell (zsh
) with a shell command line as an argument. A command line can contain multiple commands separated by semicolons so you can prepend a cd
to your command
value.
The latter approach avoids changing your current process' current directory.
HTH
Execute a terminal command from a Cocoa app
You can use NSTask
. Here's an example that would run '/usr/bin/grep foo bar.txt
'.
int pid = [[NSProcessInfo processInfo] processIdentifier];
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *file = pipe.fileHandleForReading;
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/grep";
task.arguments = @[@"foo", @"bar.txt"];
task.standardOutput = pipe;
[task launch];
NSData *data = [file readDataToEndOfFile];
[file closeFile];
NSString *grepOutput = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (@"grep returned:\n%@", grepOutput);
NSPipe
and NSFileHandle
are used to redirect the standard output of the task.
For more detailed information on interacting with the operating system from within your Objective-C application, you can see this document on Apple's Development Center: Interacting with the Operating System.
Edit: Included fix for NSLog problem
If you are using NSTask to run a command-line utility via bash, then you need to include this magic line to keep NSLog working:
//The magic line that keeps your log where it belongs
task.standardOutput = pipe;
An explanation is here: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask
How to launch a terminal app on PATH in Swift on macOS?
A comment from @MartinR brought me to the right idea. Don't run the terminal command directly, but from a new shell. This way it will do the PATH resolution for you:
let shellProcess = new Process();
shellProcess.launchPath = "/bin/bash";
shellProcess.arguments = [
"-l",
"-c",
// Important: this must all be one parameter to make it work.
"mysqlsh --py -e 'print(\"Call from shell\")",
];
shellProcess.launch();
This example uses the MySQL shell as example, which is in /usr/local/bin
(unlike git, which is in /usr/bin
). Git worked already in the beginning, while mysqlsh did not. From the comment you can also see that it is important to make the mysqlsh call a complete and single parameter entry. If you split that then /bin/bash -c
will only execute mysqlsh
and not pass on the given shell parameters.
executing a shell script located in app bundle in MacOS with swift
There are some issues with your code that needs to be fixed.
Firstly you are using Process incorrectly, the property executableURL
is meant for the executable, that is the shell in this case, you want to use for running your script so for zsh it should be set to
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
Secondly it seems after some trial and error that we can not execute the script directly, I assume this is because even if we set the script as executable using chmod this is lost when the script is copied to the bundle. So the script needs to be run as "source save.sh"
To set the script to be run we use the arguments
property
task.arguments = ["-c", "source \(scriptURL.path"]
So together your shell
function becomes
func shell(_ scriptURL: URL) throws {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
task.arguments = ["-c", "source \(scriptURL.path)"]
try task.run()
}
Related Topics
iOS 12 Wkwebview Not Working with Redirects
Difference Between Firinstanceid.Instanceid().Token() and Messaging.Messaging().Fcmtoken
Swift - How to Deal with Uncaught Exception
Cannot Convert Value of Type 'X' to Expected Argument Type 'X'
Which Optimization Level Should I Choose for Release
Decoding a Nested Array in Swift 4
Remove from Array of Anycancellable When Publisher Finishes
Variable with Getter/Setter Cannot Have Initial Value, on Overridden Stored Property
Swiftui Scrollview Only Scroll in One Direction
Cannot Assign to Value: 'self' Is Immutable
Viewwilllayoutsubviews in Swift
Scrolltoitem at Indexpath at .Top Hides Cell Under Header When Sectionheaderspintovisiblebounds
Why How to Make Same-Type Requirement in Swift with Generics? Is There Any Way
Get Path of a File in a Data Set Located in Assets.Xcassets
Xcode Failed to Resolve Dependency Firebase - Googleappmeasurement Does Not Match Requirement