Process icon indicating copy to clipboard operation
Process copied to clipboard

Cannot get live output

Open ccjensen opened this issue 8 years ago • 9 comments

In some cases I want to parse the output from a long running Process and selectively output it to the terminal, but it seems that is not currently supported? Any suggestions on where to look if I wanted to add it?

ccjensen avatar Feb 06 '17 17:02 ccjensen

Hello @ccjensen thanks for the interest,

I think this will be in the posix spawn call https://github.com/oarrabi/Process/blob/291b63cf8233042537e5b8933c4d9d59c2b99375/Sources/Process/Run.swift#L36

I have researched this before and I remember that interactive was not possible but not 100% sure.

Would be awesome to do more research on it :)

nsomar avatar Feb 06 '17 20:02 nsomar

I believe I had this working when I was working on the VaporToolbox and porting it to use posix spawn from system in the swift 2->3 transition. I believe this has been moved to https://github.com/vapor/console/blob/88e7b2347636c534ef3e8e537ffd31e551e9a161/Sources/Console/Terminal/Terminal.swift

I’ll have a look if I can dig this up again.

finestructure avatar Feb 11 '17 10:02 finestructure

@feinstruktur That would be super helpful, I banged my head around this but couldnt figure it out :S

nsomar avatar Feb 11 '17 13:02 nsomar

I’ve had a look through vapor console and extracted the function that runs the command and prints to the console just like system used to. I haven’t looked into how this would be be integrated but it’s something that can be dropped in if you need the functionality:

// adopted from (with minor edits): https://github.com/vapor/console/blob/88e7b2347636c534ef3e8e537ffd31e551e9a161/Sources/Console/Terminal/Terminal.swift

import Foundation

public enum ExecuteError: Swift.Error {
    case spawnProcess
    case execute(code: Int)
    case fileOrDirectoryNotFound
}

private var _pids: [UnsafeMutablePointer<pid_t>] = []

public func execute(program: String, arguments: [String]) throws {
    let stdin = FileHandle.standardInput
    let stdout = FileHandle.standardOutput
    let stderr = FileHandle.standardError
    
    try execute(
        program: program,
        arguments: arguments,
        input: stdin.fileDescriptor,
        output: stdout.fileDescriptor,
        error: stderr.fileDescriptor
    )
}

public func execute(program: String, arguments: [String], input: Int32? = nil, output: Int32? = nil, error: Int32? = nil) throws {
    var pid = UnsafeMutablePointer<pid_t>.allocate(capacity: 1)
    pid.initialize(to: pid_t())
    defer {
        pid.deinitialize()
        pid.deallocate(capacity: 1)
    }

    let args = [program] + arguments
    let argv: [UnsafeMutablePointer<CChar>?] = args.map{ $0.withCString(strdup) }
    defer { for case let arg? in argv { free(arg) } }

    var environment: [String: String] = ProcessInfo.processInfo.environment

    let env: [UnsafeMutablePointer<CChar>?] = environment.map{ "\($0.0)=\($0.1)".withCString(strdup) }
    defer { for case let arg? in env { free(arg) } }

    #if os(macOS)
        var fileActions: posix_spawn_file_actions_t? = nil
    #else
        var fileActions = posix_spawn_file_actions_t()
    #endif

    posix_spawn_file_actions_init(&fileActions);
    defer {
        posix_spawn_file_actions_destroy(&fileActions)
    }

    if let input = input {
        posix_spawn_file_actions_adddup2(&fileActions, input, 0)
    }

    if let output = output {
        posix_spawn_file_actions_adddup2(&fileActions, output, 1)
    }

    if let error = error {
        posix_spawn_file_actions_adddup2(&fileActions, error, 2)
    }

    _pids.append(pid)
    let spawned = posix_spawnp(pid, argv[0], &fileActions, nil, argv + [nil], env + [nil])
    if spawned != 0 {
        throw ExecuteError.spawnProcess
    }

    var result: Int32 = 0
    _ = waitpid(pid.pointee, &result, 0)
    result = result / 256

    waitpid(pid.pointee, nil, 0)

    if result == ENOENT {
        throw ExecuteError.fileOrDirectoryNotFound
    } else if result != 0 {
        throw ExecuteError.execute(code: Int(result))
    }
}

You can drop this into a file in this repo.

finestructure avatar Feb 13 '17 16:02 finestructure

@feinstruktur This looks cool! Thanks for sharing. This works interactively since it does not capture the stdout and stderr. Is that correct?

nsomar avatar Feb 14 '17 09:02 nsomar

Yes, it essentially routes everything through to the terminal and I'm using it in cases where I want to show intermediate output. If you plug that in and run a longer running command with intermediate output, like for instance find / -name "*.sh", through it you'll see what I mean.

I haven't tried but I'm sure you can grab stdout and stdout for reading in order to still return them as a result.

I would offer to try and integrate that but I'm not sure if/when I'd find the time!

finestructure avatar Feb 14 '17 09:02 finestructure

@feinstruktur I have added this on my todo list. I think that if we provide our own dup of the stdin, then the interactiveness will be lost. I will try this flavour of posix spawn and report to this issue what I found.

Thank you for the hint :)

nsomar avatar Feb 14 '17 09:02 nsomar

@nsomar Any updates?

mominul avatar Jul 23 '17 05:07 mominul

@mominul I didnt really have a chance to look into this. I am afraid I wont have enough time to look at this anytime soon :(

nsomar avatar Jul 23 '17 11:07 nsomar