Get Terminal Output After a Command Swift

Get terminal output after a command swift

NSTask is class to run another program as a subprocess. You can
capture the program's output, error output, exit status and much more.

Expanding on my answer to xcode 6 swift system() command,
here is a simple utility function to run a command synchronously,
and return the output, error output and exit code (now updated for Swift 2):

func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {

var output : [String] = []
var error : [String] = []

let task = NSTask()
task.launchPath = cmd
task.arguments = args

let outpipe = NSPipe()
task.standardOutput = outpipe
let errpipe = NSPipe()
task.standardError = errpipe

task.launch()

let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String.fromCString(UnsafePointer(outdata.bytes)) {
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
output = string.componentsSeparatedByString("\n")
}

let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String.fromCString(UnsafePointer(errdata.bytes)) {
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
error = string.componentsSeparatedByString("\n")
}

task.waitUntilExit()
let status = task.terminationStatus

return (output, error, status)
}

Sample usage:

let (output, error, status) = runCommand("/usr/bin/git", args: "status")
print("program exited with status \(status)")
if output.count > 0 {
print("program output:")
print(output)
}
if error.count > 0 {
print("error output:")
print(error)
}

Or, if you are only interested in the output, but not in
the error messages or exit code:

let output = runCommand("/usr/bin/git", args: "status").output

Output and error output are returned as an array of strings, one
string for each line.

The first argument to runCommand() must be the full path to an
executable, such as "/usr/bin/git". You can start the program using a shell (which is what system() also does):

let (output, error, status) = runCommand("/bin/sh", args: "-c", "git status")

The advantage is that the "git" executable is automatically found
via the default search path. The disadvantage is that you have to
quote/escape arguments correctly if they contain spaces or other
characters which have a special meaning in the shell.


Update for Swift 3:

func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {

var output : [String] = []
var error : [String] = []

let task = Process()
task.launchPath = cmd
task.arguments = args

let outpipe = Pipe()
task.standardOutput = outpipe
let errpipe = Pipe()
task.standardError = errpipe

task.launch()

let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: outdata, encoding: .utf8) {
string = string.trimmingCharacters(in: .newlines)
output = string.components(separatedBy: "\n")
}

let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: errdata, encoding: .utf8) {
string = string.trimmingCharacters(in: .newlines)
error = string.components(separatedBy: "\n")
}

task.waitUntilExit()
let status = task.terminationStatus

return (output, error, status)
}

How to handle tabulated terminal output after NSTask command execution with Swift

I suppose the right answer is to actually know your expected output and then parse out the stuff you want to keep (regex, etc. to find the lines that match what you want "I might have a line of initial output I can throw out. Then I always have 4 columns, separated by 3 spaces where data in X position looks like \d{3}"). That would probably be better so you can handle a variety of situations and address unexpected results.

But - you could also do a kind of bad approximation of that by just arbitrarily saying:

  • I have a carriage returns that denotes "rows"
  • I have three spaces that denote "columns"
  • "real" data will always have the same number of columns
  • We don't know how many columns there will be, but we're going to make the assumption that we'll find the "max" and then that will define our number (EX: "real data" in this example has 4 columns, while "junk" data has one)
  • Throw out any rows that don't have the matching number of columns

    let stdOut = "this is very special output and we are proud of it.\n0x0032 099 099 000\n0x0032 099 099 000\n0x0013 099 099 000\n0x0013 122 100 010\n0x0032 100 100 010\n0x0032 100 100 010\n0x0013 100 100 010\n0x0032 100 100 000"

    var rowsAndCols: [[String]] = [[]]

    let rowDelimiter = "\n" //rows are split by carriage return
    let colDelimiter = " " //cols are split by 3 spaces

    let rows = stdOut.componentsSeparatedByString(rowDelimiter)

    for row in rows {
    let cols = row.componentsSeparatedByString(colDelimiter)
    rowsAndCols.append(cols)
    }

    //count the # of columns and get the max
    let maxCols = rowsAndCols.map({$0.count}).maxElement()

    //remove any rows whose # columns is lower than the "max" found
    rowsAndCols = rowsAndCols.filter({$0.count == maxCols})

    //show our whole output
    print(rowsAndCols)

    let someVal = rowsAndCols[0][0]
    print(someVal)

How do I run a terminal command in a Swift script? (e.g. xcodebuild)

If you don't use command outputs in Swift code, following would be sufficient:

#!/usr/bin/env swift

import Foundation

@discardableResult
func shell(_ args: String...) -> Int32 {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}

shell("ls")
shell("xcodebuild", "-workspace", "myApp.xcworkspace")

Updated: for Swift3/Xcode8



Related Topics



Leave a reply



Submit