Commands

SSHKit has two ways to run a remote command.

Collected (batch)

Use execute when the command will produce a bounded amount of output and you want it all together.

let result = try await connection.execute("uname -a")
let stdout = String(data: result.standardOutput, encoding: .utf8) ?? ""
print(stdout)
print("exit:", result.exitStatus)

SSHCommandResult has standardOutput, standardError, exitStatus, and an optional exitSignal. Non-zero exit is not thrown — inspect exitStatus after the call.

Streamed

Use openCommand when you need to consume output as it arrives or write to the command’s stdin.

let command = try await connection.openCommand("tail -F /var/log/app.log")

for await event in command.events {
    switch event {
    case .standardOutput(let data):
        if let text = String(data: data, encoding: .utf8) {
            print(text, terminator: "")
        }
    case .standardError:
        break // PTY-backed channels can emit this
    case .closed(let status, let signal):
        print("closed status=\(status) signal=\(signal ?? "-")")
        return
    }
}

command.events is an AsyncStream<SSHCommandEvent> that finishes when the channel closes. You can write to stdin while the stream is live (command.write(...)) and call command.sendEOF() or command.close() when done.

Exit status & signals

The .closed event carries both an Int32 exit status and an optional exitSignal string. The two are mutually exclusive at the wire level: