I'm trying to use Swift to run a command in the (macOS) terminal. Specifically, to create an empty file. Here's what I have for running in a shell:
import Foundation
func shell(_ args: String...) ->Int32{
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
Note that I have tried /usr/bin/env/ for the launch path with no good result.
When I call shell("touch /path/to/new/file.txt") it returns an error like:
/bin/bash: touch /Users/my_home_dir/Desktop/file.txt: No such file or directory
127
If I change the launch path it gives a very verbose but unhelpful console message Which reminds me of Python typical output <'class 'demo' at 0x577e040ab4f'>
I've even tried running python in the terminal and creating a file with open().
I am open to any new ways to create files in Swift (which would be great), and any ways to do the above so that it actually works.
Thanks in advance 😉
Calling that command (/bin/bash touch ~/Desktop/touch.txt) directly in Terminal result in the same error.
/usr/bin/touch: cannot execute binary file
If you only want to call "touch" you can set the launch path to /usr/bin/touch and only pass ~/Desktop/touch.txt for the argument.
If you want to call general bash/shell commands you pass -c followed by the shell command joined as a string so that it corresponds to: /bin/bash -c "touch ~/Desktop/touch.txt".
Related
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.
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 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.
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
I really need to be able to have a .sh file ran from swift! I looked into t and for some reason people want me to run the shell program in the swift file but it doesn't end up working. I'm trying to run one command, really. The shell command does need argument which is going to be passed into from the app, though. The command I'm trying to run is:
chmod a-x /Applications/the input here
I also must be able to take the users input to put the password. How would I input the password?
EDIT
I have already tried:
import Foundation
let task = NSTask();
task.launchPath = "/bin/chmod"
task.arguments = ["a-x /Applications/application.app"]
task.launch()
Does anyone have any tips?
Figured it out! I did not know that the arguments had to be like this:
task.arguments = ["a-x", "/Applications/application.app"]