Ottieni l’output del terminale dopo un comando swift

Eseguo alcuni comandi nel terminale con questo codice:

system("the command here") 

E dopo voglio sapere qual è il risultato dell’esecuzione di questo comando, ad esempio se corro

 system("git status") 

Voglio leggere le informazioni reali sui cambiamenti nel mio repo. C’è un modo per farlo in fretta?

NSTask è class per eseguire un altro programma come sottoprocesso. È ansible acquisire l’output del programma, l’output degli errori, lo stato di uscita e molto altro.

Espandendo la mia risposta al comando xcode 6 swift system () , ecco una semplice funzione di utilità per eseguire un comando in modo sincrono e restituire l’output, l’output di errore e il codice di uscita (ora aggiornato per 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) } 

Esempio di utilizzo:

 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) } 

Oppure, se sei interessato solo all’output, ma non ai messaggi di errore o al codice di uscita:

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

Output e output di errore vengono restituiti come una matrice di stringhe, una stringa per ogni riga.

Il primo argomento per runCommand() deve essere il percorso completo di un file eseguibile, ad esempio "/usr/bin/git" . Puoi avviare il programma usando una shell (che è anche ciò che system() fa):

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

Il vantaggio è che l’eseguibile “git” viene automaticamente trovato tramite il percorso di ricerca predefinito. Lo svantaggio è che devi citare / escape gli argomenti correttamente se contengono spazi o altri caratteri che hanno un significato speciale nella shell.


Aggiornamento per 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) } 

system genera un nuovo processo in modo da non poterne acquisire l’output. L’equivalente che ti dà un modo per farlo sarebbe popen , che potresti usare in questo modo:

 import Darwin let fp = popen("ping -c 4 localhost", "r") var buf = Array(count: 128, repeatedValue: 0) while fgets(&buf, CInt(buf.count), fp) != nil, let str = String.fromCString(buf) { print(str) } fclose(fp) 

Tuttavia, non farlo in questo modo. Usa NSTask come descrive Martin .

modifica: in base alla tua richiesta di eseguire più comandi in parallelo, ecco un codice probabilmente poco saggio:

 import Darwin let commands = [ "tail /etc/hosts", "ping -c 2 localhost", ] let fps = commands.map { popen($0, "r") } var buf = Array(count: 128, repeatedValue: 0) let results: [String] = fps.map { fp in var result = "" while fgets(&buf, CInt(buf.count), fp) != nil, let str = String.fromCString(buf) { result += str } return result } fps.map { fclose($0) } println("\n\n----\n\n".join(map(zip(commands,results)) { "\($0):\n\($1)" })) 

(seriamente, usa NSTask )