I'm trying to work with zsh
in Swift, and homebrew. I run into this issue every time I run my code. It can't find the command brew
. I'm trying to run the brew list
command through Sswift and get the output or listed packages, and then continue to display that. Is there a way to include the zsh profile with brew in the Swift command, but still produce output?
func run(_ cmd: String) -> String? {
let pipe = Pipe()
let process = Process()
process.launchPath = "/usr/local/Home"
process.arguments = ["-c", String(format:"%@", cmd)]
process.standardOutput = pipe
let fileHandle = pipe.fileHandleForReading
process.launch()
return String(data: fileHandle.readDataToEndOfFile(), encoding: .utf8)
}
func test(){
do {
run("brew list")
} catch {
print("errpr")
}
}
CodePudding user response:
There's a bunch of issues here. I'll tackle them one by one:
process.launchPath = "/usr/local/Home"
The launch path is the actual executable you want this Process
to launch. Unless /usr/local/Home
is some binary or script we don't know about, that's probably not what you want.
Furthermore, there's no involvement of zsh
, anywhere in the code you showed.
String(format:"%@", cmd)
This does precisely nothing. You're creating a new string from a format string that only includes the value of cmd
, which is ... already a String. In Objective C this would have had the effect of copying cmd
, but given that String
is a value type in Swift, this copying has no effect.
run("brew list")
This is a misunderstanding of how program arguments work. At the OS level, program arguments are an array of strings. Not just one large array.
When you do something like brew list
in a shell (like zsh
), it's the shell's job to parse apart this string by spaces, and come up with an array of strings of individual arguments to pass to the OS (via execv
and friends).
There's a few ways to invoke brew
. The more "roundabout"
import Foundation
func run(_ cmd: String) -> String? {
let process = Process()
process.launchPath = "/bin/zsh"
process.arguments = [
"-l", // Login shell
"-c", // Evaluate input from argument
cmd // The commands to evaluate
]
let pipe = Pipe()
process.standardOutput = pipe
let fileHandle = pipe.fileHandleForReading
process.launch()
return String(data: fileHandle.readDataToEndOfFile(), encoding: .utf8)
}
let output = run("brew list")
print(output ?? "<nil>")
Though even with -l
, I can't get this to read my path configuration that I set in ~/.zshrc
. I'm not sure why this is.
Instead, you can just use a more direct way and just invoke brew
directly, with no shell middleman.
import Foundation
func runBrewCommand(_ args: [String]) -> String? {
let process = Process()
process.launchPath = "/opt/homebrew/bin/brew"
process.arguments = args
let pipe = Pipe()
process.standardOutput = pipe
let fileHandle = pipe.fileHandleForReading
process.launch()
return String(data: fileHandle.readDataToEndOfFile(), encoding: .utf8)
}
let output = runBrewCommand(["list"])
print(output ?? "<nil>")