wiki:swift.shell

A class is using the shell

Starting the task and passing arguments

    func askForSwap(_ terminationHandler: @escaping (_ result: [String], _ error: Error? ) -> Void ) {
        
        let request = Request()
        
        request.process.launchPath = "/usr/bin/ssh"
        request.process.arguments = [
            hostname,
            "/usr/sbin/swap -s"
        ]
        
        askBareMetal(request: request, terminationHandler)
        
    }

Input, error and output pipes

    class Request {
        let process = Process()
        var outputPipe = Pipe()
        var outputString: String = ""
        var outputHandler: ((_ s: String) -> Void)? = nil
        var errorHandler: ((_ s: String) -> Void)? = nil
        var errorPipe = Pipe()
        var errorString: String = ""
        var inputPipe = Pipe()
        
        var outputEOF: Bool = false
        var errorEOF: Bool = false
    }
    func askBareMetal(request: Request, _ terminationHandler: @escaping (_ result: [String], _ error: Error? ) -> Void ) {
        
        let completitionHandler: () -> Void = {
            var result: [String] = []
            for line in request.outputString.components(separatedBy: CharacterSet.newlines) {
                if line.count > 0 {
                    result.append(line)
                }
            }
            if request.errorString.count > 0 {
                print("\(self.hostname) error: \(request.errorString)")
                terminationHandler(result, LdapError())
            } else {
                terminationHandler(result, nil)
            }
        }
        
        captureOutput(request, completitionHandler)
        captureError(request, completitionHandler)
        
        request.process.standardInput = request.inputPipe
        
        // launch the process
        let operationQueue: OperationQueue = OperationQueue()
        operationQueue.addOperation( {
            request.process.launch()
        })
        
    }
    func captureOutput(_ request: Request, _ terminationHandler: @escaping () -> Void ) {
        request.process.standardOutput = request.outputPipe
        request.outputEOF = false
        request.outputString = ""
        
        request.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
        NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: request.outputPipe.fileHandleForReading , queue: nil) {
            notification in
            let data = request.outputPipe.fileHandleForReading.availableData
            if let outputString = String(data: data, encoding: String.Encoding.utf8) {
                if outputString.count > 0 {
                    request.outputHandler?(outputString)
                    request.outputString += outputString
                    request.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
                } else {
                    if request.errorEOF {
                        terminationHandler()
                    } else { request.outputEOF = true }
                }
            } else {
                print("cannot convert data to string")
            }
        }
    }

Parallel requests

Open shell: Hold the line

    func send(_ request: Request, _ s: String){
        if let data = s.data(using: .ascii) {
            request.inputPipe.fileHandleForWriting.write(data)
        }
        request.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
    }
        request.outputHandler = {
            s in
            if s.hasSuffix("ready\n") {
                bareMetal.send(request, "exit\n")
            } else {
                self.resultView.string += "o: \(s)"
            }
        }
    func sendEnd(_ request: Request) {
        request.inputPipe.fileHandleForWriting.closeFile()
    }

Writing in remote files

        let file = "file"
        let name = "Joerg"
        let question = "how are you"
        let text =
            """
            line 1
            hello \(name), \(question)?
            line 2
            """
        bareMetal.send(request,
            """
            cat > \(file) <<EOF
            \(text)
            line 3
            EOF\n
            """
        )

Sudo

        bareMetal.send(request, "sudo -S tail /var/log/auth.log ; echo ready\n")
    func captureError(_ request: Request, _ terminationHandler: @escaping () -> Void ) {
        request.process.standardError = request.errorPipe
        request.errorEOF = false
        
        request.errorPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
        NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: request.errorPipe.fileHandleForReading , queue: nil) {
            notification in
            let data = request.errorPipe.fileHandleForReading.availableData
            if let errorString = String(data: data, encoding: String.Encoding.utf8) {
                if errorString.count > 0 &&
                    // banner is hashes and blanks: don't bother, put away
                    errorString.replacingOccurrences(of: "#", with: "").trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0 {
                    if let sudoPassword = self.sudoPassword, errorString.hasPrefix("[sudo] password") {
                        self.send(request, "\(sudoPassword)\n")
                    } else {
                        request.errorHandler?(errorString)
                        request.errorString += errorString
                    }
                    request.errorPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
                } else {
                    if request.outputEOF {
                        terminationHandler()
                    } else { request.errorEOF = true }
                }
            } else {
                print("cannot convert data to string")
            }
        }
    }

Common pitfalls and their solutions

Last modified 4 months ago Last modified on Apr 24, 2018, 12:42:17 PM