 
     // Similar to above, but can throw, and throws with most of
     // what you'd probably need in a scripting environment
-    func spawn(_ command: String, arguments: [String] = [], environmentVariables: [String] = []) throws -> String {
-        let script = [environmentVariables.joined(separator: " "),
-                      command,
-                      arguments.joined(separator: " ")].filter { !$0.isEmpty }.joined(separator: " ")
-
-        var env = ProcessInfo.processInfo.environment
-        let task = Process()
-        task.launchPath = env["SHELL"]
-        task.arguments = ["-l", "-c", script]
-        task.currentDirectoryPath = FileManager.default.currentDirectoryPath
+    func spawn(_ command: String, arguments: [String] = [], environmentVariables: [String: String] = [:]) throws -> String {
+        let task = makeTask(for: command, with: arguments, environmentVariables: environmentVariables)
 
         let stdout = Pipe()
         task.standardOutput = stdout

                                       task: task)
    }

    private func mergeEnvs(localEnv: [String: String], processEnv: [String: String]) -> [String: String] {
        return localEnv.merging(processEnv, uniquingKeysWith: { (_, envString) -> String in
            envString


internal class ShellExecutor {
    func execute(_ command: String, arguments: [String] = [], environmentVariables: [String: String] = [:]) -> String {
        let task = makeTask(for: command, with: arguments, environmentVariables: environmentVariables)

        let pipe = Pipe()
        task.standardOutput = pipe
