3

I am looking for a solutuion to run shell commands in a Swift script.

Here is my code:

func shellEnv(_ command: String) -> String {
    let task = Process()
    let pipe = Pipe()
    
    task.standardOutput = pipe
    task.standardError = pipe
    task.arguments = ["-c", command]
    task.launchPath = "/bin/zsh"
    task.launch()
    
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: .utf8)!
    
    return output
}

It works with built-in commands but it cannot deal with commands like "brew", "node" and other commands that were installed manually.

So how can I solve it?

3
  • And why can’t it deal with brew for instance, what happens? Commented Jul 8, 2021 at 11:08
  • 1
    I think you need to pass “-l” to make it a “login shell”, one which loads your personal confit files, including the ones that set your PATH Commented Jul 8, 2021 at 11:09
  • @Alexander Aww. That's a great shortcut compared to my clumsy answer. I'm still a shell newbie... Commented Jul 8, 2021 at 11:19

1 Answer 1

2

You need to set the PATH environment variable for the task. This has been set in your terminal, which is why you are able to do brew and node and other cool things directly, without specifying their full path.

You can see what this is set to in your terminal by doing:

echo $PATH

and it will print something like (for me it has a bunch more things. This is only an excerpt):

/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

If you copy and paste the entire output of echo $PATH, and put it into the environment property of task, then you will be able to use the same commands in your swift script as in your terminal.

task.environment = ["PATH": "<paste the output here>"]

As Alexander said in the comments, another way is to add the l option. Here is a MCVE:

#!/usr/bin/swift

// main.swift
import AppKit

func shellEnv(_ command: String) -> String {
    let task = Process()
    let pipe = Pipe()
    
    task.standardOutput = pipe
    task.standardError = pipe
    task.arguments = ["-cl", command]
    task.launchPath = "/bin/zsh"
    task.launch()
    
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: .utf8)!
    
    return output
}

print(shellEnv("brew list")) // assuming you have brew

To run, chmod +x main.swift then ./main.swift, and you will see all your homebrew packages listed.

Sign up to request clarification or add additional context in comments.

9 Comments

@IvanEFan Did you try Alexander's simpler solution? Use -cl instead of -c.
I have tried that but I still get "command not found" :(
@IvanEFan Works for me though. Can you describe the full list of steps, and a minimal reproducible example, to reproduce the problem?
@IvanEFan I added my own minimal reproducible example. Can you try that? Does that work for you?
Is your project perhaps sandboxed? Maybe this is about another level of access, I have no problem running the code in my small command line tool project (but with -l then)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.