1

Okay so I'm trying to run a Mosquitto publish command in bash from a Swift application for MacOS. Here is my code:

@IBAction func buttonClicked(_ sender: Any) {
        let mosquittoCommand = "mosquitto_pub --cert blahblah.pem --key blahblah.key --cafile blahblah.pem -h 'blah.blah.com' -p 443 -t 'blah/blah/blah/blah' -m '{\"msg\": \"blahblahblah\", \"time\": \"2019-08-07T15:12:00Z\", \"id\": \"blah-blah-blah\", \"localpwd\": \"blahblahblah\"}' --tls-alpn x-amzn-mqtt-ca -i 'blahblahblah'"

        print(shell("cd /Users/Me/Desktop/certs && " + mosquittoCommand))
    }

    func shell(_ command: String) -> String {
        let task = Process()
        task.launchPath = "/usr/bin/env"
        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
    }

I'm getting the following error:

/usr/bin/env: illegal option -- c
usage: env [-iv] [-P utilpath] [-S string] [-u name]
           [name=value ...] [utility [argument ...]]

You'll have to trust me that running the command in a terminal window directly works as expected. The only difference is the escape characters in the mosquitto command to prevent the quotations messing up the command. Perhaps the escape chars are causing the problems?

I have no idea what the error is trying to tell me. Any advice would be greatly appreciated. Thanks.

EDIT - I've made sure chaining some basic commands (pwd, cd... etc) from Swift works. So it's definitely set up correctly to be able to run commands like this, I just don't know why it can't run the Mosquitto publish command.

SuperHanz98
  • 2,090
  • 2
  • 16
  • 33
  • What about using "/bin/bash" as launch path, as in this answer https://stackoverflow.com/a/57394377/1187415 to your previous question? – Martin R Aug 07 '19 at 14:31
  • I did actually get '/usr/bin/env' to work so I rolled with it because that's what others were using. Using `/bin/bash` I now get a different error: `/bin/bash: mosquitto_pub: command not found`. Whether this is better or worse I've no idea. – SuperHanz98 Aug 07 '19 at 14:34
  • It's more reliable to set only the command (full path) of `mosquitto_pub` in `launchPath` (better `executableURL`), specify `currentDirectoryURL` and pass the parameters as array in `arguments`. And consider that `Process` doesn't work if the app is sandboxed. – vadian Aug 07 '19 at 14:35
  • The /usr/bin/env command has no "-c" option. *If* you want to start the program via env then you should remove that option from the arguments. – Martin R Aug 07 '19 at 14:38
  • '-c' seems to work as I expect it to, if I don't use '-c' then I can't chain commands using &&. When I use '-c' it works perfectly. So surely it must have a '-c' option if it's working with it but not without it? – SuperHanz98 Aug 07 '19 at 14:41
  • Are you sure that `/usr/bin/env -c "your command ..."` works in the Terminal? – Martin R Aug 07 '19 at 14:43
  • @MartinR I'm happy to NOT use env if /bin/bash is better, but neither work for me at the moment so theyre equally inconvinient – SuperHanz98 Aug 07 '19 at 14:43
  • @MartinR Ah I see what you mean. When I say 'it works in the terminal' I mean just the 'cd ... & mosquitto_pub...'. The -c used in the swift program is what someone else suggested to allow the whole string to be read as one command. – SuperHanz98 Aug 07 '19 at 14:45
  • @Cutter: Yes, that is an option of the *bash* to execute a command (suggested in the answer to your previous question). – Martin R Aug 07 '19 at 14:46
  • Sorry @MartinR I'm new to bash and swift so if you could maybe leave an answer showing me how I might amend my code I'd really appreciate it. What you're saying is all going straight over my head. – SuperHanz98 Aug 07 '19 at 14:48
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/197621/discussion-between-martin-r-and-cutter). – Martin R Aug 07 '19 at 14:50

1 Answers1

2

The immediate reason for the error message is that /usr/bin/env does not have a -c option, apparently you mixed that up with /bin/bash -c "command ...". Also the env command can only start a single executable, not multiple commands chained together with &&.

The other problem is that the mosquitto_pub binary is not found when running your app from the Finder. As it turned out in the discussion, this program is installed (via Homebrew) in /usr/local/bin. That directory is usually in the search path of a Terminal shell, but not when an application is started from the Finder.

Using an absolute path for the program is one option:

let mosquittoCommand = "/usr/local/bin/mosquitto_pub --cert blahblah.pem ..."

As already said in the comments, it is easier to set the launch path (as an absolute path), the working directory where the command should be executed, and the command arguments as an array:

let task = Process()
task.launchPath = "/usr/local/bin/mosquitto_pub"
task.arguments = [
    "--cert",
    "blahblah.pem",
    // ...
    "-i",
    "blahblahblah"
]
task.currentDirectoryPath = "/Users/Me/Desktop/certs"

This makes quoting the arguments and calling via the shell unnecessary.

Alternatively you can start the program via env (so that it is found at multiple possible locations) but then you have add "/usr/local/bin" to the search path:

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

task.launchPath = "/usr/bin/env"
task.arguments = [
    "mosquitto_pub",
    "--cert",
    "blahblah.pem",
    // ...
    "-i",
    "blahblahblah"
]
task.currentDirectoryPath = "/Users/Me/Desktop/certs"

Finally, you can use

task.currentDirectoryURL = FileManager.default
    .homeDirectoryForCurrentUser
    .appendingPathComponent("certs")

to make the working directory work with any user name.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382