swift Process() use which go/docker not return values

I refer to this article: https://developer.apple.com/forums/thread/129752

extension Cmd {
  static func launch(tool: URL, arguments: [String], completionHandler: @escaping (Int32, Data) -> Void) throws {
    let group = DispatchGroup()
    let pipe = Pipe()
    var standardOutData = Data()

    group.enter()
    let proc = Process()
    proc.executableURL = tool
    proc.arguments = arguments
    proc.standardOutput = pipe.fileHandleForWriting
    proc.terminationHandler = { _ in
      proc.terminationHandler = nil
      group.leave()
    }

    group.enter()
    DispatchQueue.global().async {
      // Doing long-running synchronous I/O on a global concurrent queue block
      // is less than ideal, but I’ve convinced myself that it’s acceptable
      // given the target ‘market’ for this code.

      let data = pipe.fileHandleForReading.readDataToEndOfFile()
      pipe.fileHandleForReading.closeFile()
      DispatchQueue.main.async {
        standardOutData = data
        group.leave()
      }
    }

    group.notify(queue: .main) {
      completionHandler(proc.terminationStatus, standardOutData)
    }

    try proc.run()

    // We have to close our reference to the write side of the pipe so that the
    // termination of the child process triggers EOF on the read side.

    pipe.fileHandleForWriting.closeFile()
  }
}

run shell :

try Cmd.launch(tool: URL(fileURLWithPath: "/usr/bin/which"), arguments: ["docker"]) { (status, outputData) in
        let output = String(data: outputData, encoding: .utf8) ?? ""
        print("done, status: \(status), output: \(output)")
      }

the result print:

done, status: 1, output: 

i had remove sandbox and use this code in my mac app. and return value is empty string.

I tried running this script using https://github.com/kareman/SwiftShell and got the same result

I refer to this article: https://developer.apple.com/forums/thread/129752

So the Apple Recommended answer on that thread links to Running a Child Process with Standard Input and Output, which holds the code that I stand by.


Having said that, this is not the cause of the problem you’re seeing. Consider this code:

launch(tool: URL(fileURLWithPath: "/usr/bin/which"), arguments: ["vmrun"]) { result, output in
    print(result, String(data: output, encoding: .utf8) ?? "-")
}

It prints:

success(1) 

that is, which failed. If you then change vmrun [1] to open, it prints:

success(0) /usr/bin/open

So the code works, it’s which that’s failing. Why is that?


It turns out that which is failing because it inherits its PATH from your app and your app’s path doesn’t include the extra locations set up by path_helper (see its man page).

Contrast this:

% /usr/bin/printenv
…
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/Library/Apple/usr/bin:/Users/quinn/bin
…

with the output you get when you run /usr/bin/printenv via Process:

PATH=/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin

Note Xcode is in the path because I ran my app from Xcode. If I ran it from the Finder, it wouldn’t be there.

What to do about this? Presumably you want to Finder the same docker that the user would have found if they’d entered docker in their shell. The problem here is that different users have different shells and there’s no guarantee that all of those shells set up PATH in the same way.

One way around this is to run the shell’s built-in which command. For example:

let shell = URL(fileURLWithPath: "/bin/sh")
launch(tool: shell, arguments: ["-l", "-c", "which vmrun"]) { result, output in
    print(result, String(data: output, encoding: .utf8) ?? "-")
}

Note The -l argument tells the shell to act as a login shell, and thus set up PATH as it would on login.

This assumes that the user is using the default shell. To remove that dependency, use this code:

let pw = getpwuid(getuid())!
let shellPath = String(cString: pw.pointee.pw_shell)
let shell = URL(fileURLWithPath: shellPath)

IMPORTANT This is the way that I found to get around this problem. There may be better, more Unix-y ways that I’ve missed. I encourage you to do further research here.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Note that I don’t have docker installed so I’m using vmrun, from VMware Fusion, as a stand-in.

swift Process() use which go/docker not return values
 
 
Q