Calling Git Commands from Within a Swift Macos Application

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



Leave a reply



Submit