Run simple Shell command on SwiftUI project - swift

I'm just looking for run a simple command (eg. "open -a Notes") using Process in a SwiftUI project and I'm having an issue running the command. I guess my path is off but I'm not 100% sure what it should be.
What's below is in the action of a button and I've disabled the sandbox option too.
Any help is appreciated!
let process = Process()
process.launchPath = "/bin/zsh"
process.arguments = ["open -a Notes"]
process.launch()
process.waitUntilExit()

Related

/usr/bin/sudo: Operation not permitted error when trying to run sudo commands via Swift process

In XCUI based test automation framework, I am trying to run terminal commands via Swift code which involves few internal cli tools and sudo commands. Either sudo or internal commands are getting recognised when executed via Swift process().
Below is the swift function implemented to call and run shell script commands
import Foundation
func runShell(_ command: String) -> String {
let task = Process()
task.launchPath = "/bin/zsh"
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
print(runShell("sh /Users/../script.sh"))
Ran shell with file
runShell("sh /Users/../script.sh"))
Output:
/Users/../script.sh: line 18: /usr/bin/sudo: Operation not permitted
/Users/../script.sh: line 19: /usr/bin/python3: command not found
Tried running the same shell file via applescript with terminal window open
runShell("sudo su -l admin -c \"osascript /Users/../script.sh\"")
Output via Applescript:
zsh:1: operation not permitted: sudo
The above shell script and apple script works fine when it is executed directly via terminal instead of Swift. Please suggest a solution to run the shell script via Swift with maximum access and privileges.
Create entitlements file and set Sandbox value to No
Menu: File > New > File
Use the Property List template
Name the file as MYPROJECT.entitlements
Open the file via Project navigator
Add new row as below
Key: App Sandbox Type: Boolean Value: No
Select Project in navigator to open project settings
Select the target and navigate to Build Settings
In the list, find CODE_SIGN_ENTITLEMENTS
Set its value to the location of the MYPROJECT.entitlements file.
Do a Clean Build Folder, select a suitable build platform and perform a Product Build or Run.

Why my arguments are being blocked when running a shell command?

I am looking to run a command on command line from my SWIFT app in MAC.
var resString = "open \(app.getLocation()) --args 25 40"
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", resString]
task.launch()
print (resString)
when I print the resString on console, I. get the following
open [path to app on my local drive] --args 25 40
which executes normally when I copy paste in the command line. but on the other side, the app is opened but arguments are being ignored.
I also tried this https://stackoverflow.com/a/53725745/12197482 still didn't work, the app is launched but the arguments don't get through
EDIT: Here's the funniest thing that's really frustrating, I created a small script that have a command, which contains
#! /bin/bash
open ./SimulatorDebug.app --args arg1 arg2
I ran it from terminal again, args are passed correctly with no issues. I tried to run at from my app and the same issue happened again, app run play BUT NO ARGS are being passed which is I find really weird.
Try using /bin/sh as executableURL instead:
task.executableURL = URL(fileURLWithPath: "/bin/sh")
That should do it.
You might also want to try using task.run() instead, and waiting for completion:
try task.run()
task.waitUntilExit()
I wrote System to make this much easier. Feel free to use it or copy/paste the code:
https://github.com/eneko/System/blob/master/Sources/System/System.swift#L107
Updated with additional info
Since you are trying to open a macOS app, there is no need to use sh or bash, you can invoke open directly.
To test this, I've written a sample macOS app in Xcode that all it does is display arguments on screen. Then, I proceeded to launch the app from a macOS Playground, as seen in the screenshot:
Here is the code to launch the app with arguments:
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/open")
process.arguments = [
"/Applications/ArgumentTest/ArgumentTest.app",
"-n",
"--args",
"Open",
"From",
"Playground"
]
try process.run()
-n is used to launch an instance of the app, even if the app is already running. Might not be needed in your case.
Hope this helps!

How to run and interact with a program such as "bash -c ssh ..." using swift 4 and playgrounds?

How can one run and interact with a program such as ssh in a bash terminal using swift 4 and playgrounds? Terminal commands can be executed in a Mac swift playground with code such as:
#discardableResult func shell(_ command: String) -> String {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
return output
}
// Example usage:
let t = shell("ls")
print("\(t)") //prints dir listing
However, I would like to be able to interact with programs like ssh versus a programs that aren't interactive such as ls. For example the command:
let t = shell("ssh xxx.xxx.xxx.xxx -T -l root -p 22")
will start ssh and ssh will via terminal prompt for the password. I want to be able to then programmatically provide the password and continue. It seems to me that since SSH is running in the context of terminal that highjacking terminals stdin and stdout should suffice however, I've not been successful with that so far. Here are some resources I found that provide some information on highjacking:
Big nerd on highjacking
Medium article
Any ideas about if and how this could be accomplished?
ssh reads passwords directly from /dev/tty rather than stdin. To programmatically control a tty you need the pty module/APIs which tools like Expect are using.
I know nothing about Swift so not sure if it has a similar pty module. If it does not then you can use Expect to run ssh.

Running terminal commands from Cocoa App in Swift failing: "command not found" but works using Command Line Tool in swift

I created an App that exports all of my iMessages into an app I use for journaling. It accesses the chat.db file in Libary/Messages/ to do this. This part works great
I was required to use frameworks and Command Line Tools won't allow you to bundle Frameworks in macOS. I would have preferred to for this all to be a script and avoid Cocoa altogether, but was required to use a full Cocoa application because of the need for bundled Frameworks
I can enter commands like pwd and get a response back. However, when I try to run the terminal commands for the journaling app, it fails with "command not found"
If I run the exact same command from within terminal, or from within a Swift Command Line Tool in Xcode, it works. However, now that I'm using an actual Cocoa app it won't work.
Here is an example of my code:
let pipe = Pipe()
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", String(format:"%#", "dayone2 new 'Hello'")]
task.standardOutput = pipe
let file = pipe.fileHandleForReading
task.launch()
if let result = NSString(data: file.readDataToEndOfFile(), encoding: String.Encoding.utf8.rawValue) {
return result as String
}
else {
return "--- Error running command - Unable to initialize string from file data ---"
}
and the response:
/bin/sh: dayone2: command not found
Add "--login" as the first task argument:
task.arguments = ["--login", "-c", "dayone2 new 'Hello'"]
and that should fix your error.
Explanation:
When you run Terminal the shell starts up as a login shell, from man sh:
When bash is invoked as an interactive login shell, or as a
non-inter-active shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable.
Among other things the commands in these files typically set the $PATH environment variable, which defines the search path the shell uses to locate a command.
When you run your command line tool in the Terminal it inherits this environment variable and in turn passes it on to the shell it invokes to run your dayone2 command.
When you run a GUI app there is no underlying shell and the $PATH variable is set to the system default. Your error "command not found" indicates that your dayone2 command is not on the default path.
HTH

What is going on when I run a terminal/shell command in Swift?

I've spent a fair amount of researching how to run a particular terminal/shell command from within Swift.
The issue is, I'm afraid to actually run any code unless I know what it does. (I've had very bad luck with executing terminal code in the past.)
I found this question which seems to show me how to run commands, but I'm completely new to Swift and I'd like to know what each line does.
What does each line of this code do?
let task = NSTask()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "rm -rf ~/.Trash/*"]
task.launch()
task.waitUntilExit()
/bin/sh invokes the shell
-c takes the actual shell command as a string
rm -rf ~/.Trash/* remove every file in the Trash
-r means recursive. -f means forced. You can find out more about these options by reading the man page in the terminal:
man rm
As I was writing this question, I found I was able to find many of the answers, so I decided to post the question and answer it to help others like me.
//makes a new NSTask object and stores it to the variable "task"
let task = NSTask()
//Tells the NSTask what process to run
//"/bin/sh" is a process that can read shell commands
task.launchPath = "/bin/sh"
//"-c" tells the "/bin/sh" process to read commands from the next arguments
//"rm -f ~/.Trash/*" can be whatever terminal/shell command you want to run
//EDIT: from #CodeDifferent: "rm -rf ~/.Trash/*" removes all the files in the trash
task.arguments = ["-c", "rm -rf ~/.Trash/*"]
//Run the command
task.launch()
task.waitUntilExit()
The process at "/bin/sh" is described more clearly here.