narner
narner

Reputation: 3221

Running Swift commands from a Mac app

I'm attempting to run a Swift script using the command line from my Swift-based Mac app.

I have the following class method, which takes in arguments, and runs the commands:

func shell(_ args: String...) -> Int32 {
    let task = Process()
    task.launchPath = "/usr/bin/env"
    task.arguments = args
    task.launch()
    task.waitUntilExit()
    return task.terminationStatus
}

I'm able to execute commands such as the following successfully:

    shell("pwd")
    shell("ls")
    shell("swift")

pwd returns all the files in my app's build directory, as expected. This includes a hello.swift file that I manually added, which just prints "Hello, world!". Additionally, running swift does grant access to the Swift environment.

What I'm not having luck with is running commands such as:

shell("swiftc hello.swift")

Instead, I get the following error:

env: swiftc hello.swift: No such file or directory

It looks as though I'm facing a similar situation as these posts:

Swift Process - execute command error

Running shell commands in Swift

But, I'm not sure I fully understand all the implications therein for my specific situation.

Upvotes: 1

Views: 1344

Answers (1)

TheDarkKnight
TheDarkKnight

Reputation: 27611

Just to clarify, before we begin, swiftc is used to compile a swift script into a binary. In contrast, calling swift with a swift script file, will interpret the given file.

env: swiftc hello.swift: No such file or directory

Essentially, this is stating that the env binary is being passed two arguments: swiftc and hello.swift and doesn't know what to do with it.

task.launchPath = "/usr/bin/env"

I'm not sure why you're calling env here, but assuming I understand your goal correctly, we can use bash for the desired outcome.

Let's assume we have the following script.swift file

#!/usr/bin/swift
import Foundation

func shell(_ args: String...) -> Int32 {
    let task = Process()
    task.launchPath = "/bin/bash"
    task.arguments = ["-c"]
    task.arguments = task.arguments! + args
    task.launch()
    task.waitUntilExit()
    return task.terminationStatus
}

_ = shell("pwd")
_ = shell( "swift cmds.swift")

Instead of calling env, it's using bash. In order to pass a string to bash it requires the -c argument, which we prepended with

task.arguments = ["-c"]
task.arguments = task.arguments! + args

The end of the script can be seen calling the file cmds.swift. If we execute script.swift via the interpreter, it's not going to be able to call cmds.swift - essentially calling the interpreter from within itself!

So, we'll compile script.swift to a binary:

swiftc script.swift

This outputs a binary with the name script.

As we've seen, the binary calls cmds.swift, so let's create this with the following script code...

#!/usr/bin/swift  
import Foundation

print("Hello World\n")

Now if we execute the binary that we compiled, we'll see it successfully call the interpreted script and output the path (from pwd) and "Hello World", which came from the calling cmds.swift.

Upvotes: 2

Related Questions