Swift MAC App, Run Terminal Command Without Knowing the Path (So It Looks in Every Path in $Path)

Swift mac app, run terminal command without knowing the path (so it looks in every path in $PATH)?

You can execute the command via env:

env utility argument ...

Example:

let path = "/usr/bin/env"
let arguments = ["ls", "-l", "/"]
let task = Process.launchedProcess(launchPath: path, arguments: arguments)
task.waitUntilExit()

env locates the given utility using the $PATH variable and
then executes it with the given arguments. It has additional
options to specify a different search path and additional
environment variables.

(This is not a feature of Swift but of macOS and many other operating systems.)

When started from the Finder (double-click) the PATH may be different
from the PATH in your shell environment. If necessary, you can add
additional directories:

var env = task.environment ?? [:]
if let path = env["PATH"] {
env["PATH"] = "/usr/local/bin:" + path
} else {
env["PATH"] = "/usr/local/bin"
}
task.environment = env

execlp and friends also locate the executable using $PATH but offer only the "raw" C interface.

How to access the Terminal's $PATH variable from within my mac app, it seems to uses a different $PATH

Adding bash Shell Path

The default shell paths can be found in /etc/paths and /etc/path.d/. One way to read the shell paths is to use the path_helper command. Extending the code example above and using bash as the shell:

let taskShell = Process()
var envShell = ProcessInfo.processInfo.environment
taskShell.launchPath = "/usr/bin/env"
taskShell.arguments = ["/bin/bash","-c","eval $(/usr/libexec/path_helper -s) ; echo $PATH"]
let pipeShell = Pipe()
taskShell.standardOutput = pipeShell
taskShell.standardError = pipeShell
taskShell.launch()
taskShell.waitUntilExit()
let dataShell = pipeShell.fileHandleForReading.readDataToEndOfFile()
var outputShell: String = NSString(data: dataShell, encoding: String.Encoding.utf8.rawValue) as! String
outputShell = outputShell.replacingOccurrences(of: "\n", with: "", options: .literal, range: nil)
print(outputShell)

let task = Process()
var env = ProcessInfo.processInfo.environment
var path = env["PATH"]! as String
path = outputShell + ":" + path
env["PATH"] = path
task.environment = env
task.launchPath = "/usr/bin/env"
task.arguments = ["/bin/bash", "-c", "echo $PATH"]
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
var output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
output = output.replacingOccurrences(of: "\n", with: "", options: .literal, range: nil)
print(output)

Note:

  1. This code example calls the shell in non-interactive mode. This means that it won't execute any user specific profiles; such as /Users/*userid*/.bash_profile.
  2. The paths can be listed multiple times in the PATH environment variable. They will be traversed from left to right.

References

There are a couple of threads on application and shell PATH's for OS X which provide more context
How to set system wide environment variables on OS X Mavericks
and
Setting the system wide path environment variable in mavericks

How to access path sent by `open` command to Swift GUI app

The reason you can't find the arguments is because you didn't pass any arguments to the app in your command.

Unlike MyApp myFileURL, open -a MyApp myFileURL doesn't actually pass myFileURL to MyApp.

To actually pass any arguments to MyApp, you need to specifically use --args.

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.

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

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

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

Swift Run command line using processing(), but return Error Domain=NSPOSIXErrorDomain Code=13 Permission denied

I think you've misunderstood some features of how Process works. The documentation

A process operates within an environment defined by the current values
for several items: the current directory, standard input, standard
output, standard error, and the values of any environment variables.
By default, an Process object inherits its environment from the
process that launches it.

So the process will run within the current directory of the parent process. It looks like you are trying to change the current directory to

/Users/donghanhu/Documents/TestFolder

By using the launchPath of the Process, but launchPath should be set to the executable you want to run in the subprocess. In this case I think you want the launchProcess to be "/bin/ls" because you are trying to run an ls command.

So if you want a Process that will use ls to list the content of the folder /Users/donghanhu/Documents/TestFolder it would be:

import Foundation

let task = Process()
task.launchPath = "/bin/ls"
task.arguments = ["/Users/donghanhu/Documents/TestFolder"]
do {
try task.run()
} catch {
print("something went wrong, error: \(error)")
}
task.waitUntilExit()

Run homebrew package from Swift Command Line Tool

So I've found multiple solutions :

Solution 1 : Adding $PATH to env variables

Edit Scheme > Arguments > Environment Variables : Add PATH and set its value to ${PATH}

Solution 2 : Add Homebrew path to env variables

Edit Scheme > Arguments > Environment Variables : Add PATH and set its value to PATH=${PATH}:/opt/homebrew/bin:/opt/homebrew/sbin

Solution 2 : Launch in terminal

Edit Scheme > Options : Set Console to Use Terminal . But I found this option very buggy with random errors.



Related Topics



Leave a reply



Submit