3 Replies
      Latest reply on Mar 1, 2020 7:47 AM by G.U.T.explorer
      G.U.T.explorer Level 1 Level 1 (0 points)

        How do I access a returned value from a Process(), in this case 'which'...

         

        1.     var sips_path : String?
        2. //MARK: locate sips on local machine
        3.     let which_sips = Process()
        4.     which_sips.executableURL = URL(fileURLWithPath: "which")
        5.     which_sips.arguments = ["sips"]
        6.     do { sips_path = try which_sips.run() }
        7.     catch let error as NSError { sips_path = "/usr/bin/sips"; print("Failed to execute which_sips", error) }

         

        line 8. gets compiler error "Cannot assign value of type '()' to type 'String?'"

         

        I believe, but cannot prove, 'which' returns a string.  .run() throws   and throws are for errors only, right?  So where is the result of calling which?

        It seems I should use a closure to use $0 but it's already in one...

         

        line 9. intends to assign a default path.

        • Re: swift Process() return values
          Claude31 Level 8 Level 8 (9,135 points)

          Where did you get that run returns a string ?

           

          AFAICT, run returns void.

          • Re: swift Process() return values
            eskimo Apple Staff Apple Staff (13,895 points)

            The which command returns its result by printing stdout.  The run method doesn’t return that; it actually returns nothing (Void) because the process executes asynchronously.  To collect the output you have to do two things:

            • Wait for the process to finish

            • Collect its output

            Doing this correctly is tricky for obscure low-level reasons.  Pasted in below is some code I had lying around for this.  If I run it like so:

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

            it prints:

            done, status: 0, output: /usr/bin/sips

            Share and Enjoy

            Quinn “The Eskimo!”
            Apple Developer Relations, Developer Technical Support, Core OS/Hardware
            let myEmail = "eskimo" + "1" + "@apple.com"


            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()
            }
              • Re: swift Process() return values
                G.U.T.explorer Level 1 Level 1 (0 points)

                Wow!  Thanks Eskimo.

                 

                I intended to use 'which' to locate sips as a convenience if other OS's had it somewhere other than /usr/bin/ , which may not be true.  I could just continue to stipulate that the user provide it there.  Since 'which' itself must be in /usr/bin , I guess I'm OK with that stipulation.

                 

                Nevertheless, your answer taught me what I sought, and more.  Should I execute a Process() in the future, that prints to stdout, I'll know how to do it. 

                 

                Thanks again.