¿Cómo ejecuto un comando de terminal en un script rápido? (por ejemplo, xcodebuild)

Quiero reemplazar mis scripts CI bash con swift. No puedo entender cómo invocar un comando de terminal normal como ls o xcodebuild

 #!/usr/bin/env xcrun swift import Foundation // Works println("Test") // Works ls // Fails xcodebuild -workspace myApp.xcworkspace // Fails 

 $ ./script.swift ./script.swift:5:1: error: use of unresolved identifier 'ls' ls // Fails ^ ... etc .... 

Si no utiliza salidas de comando en código Swift, lo siguiente sería suficiente:

 #!/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") 

Actualizado: para Swift3 / Xcode8

El problema aquí es que no puedes mezclar y combinar Bash y Swift. Ya sabe cómo ejecutar el script Swift desde la línea de comandos, ahora necesita agregar los métodos para ejecutar los comandos del Shell en Swift. En resumen del blog PracticalSwift :

 func shell(launchPath: String, arguments: [String]) -> String? { let task = Process() task.launchPath = launchPath task.arguments = arguments let pipe = Pipe() task.standardOutput = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: String.Encoding.utf8) return output } 

El siguiente código Swift ejecutará xcodebuild con argumentos y luego emitirá el resultado.

 shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]); 

En cuanto a la búsqueda de los contenidos del directorio (que es lo que hace en Bash), sugiero usar NSFileManager y escanear el directorio directamente en Swift, en lugar de la salida de Bash, lo que puede ser difícil de analizar.

Función de utilidad en Swift 3.0

Esto también devuelve el estado de finalización de tareas y espera su finalización.

 func shell(launchPath: String, arguments: [String] = []) -> (String? , Int32) { let task = Process() task.launchPath = launchPath task.arguments = arguments let pipe = Pipe() task.standardOutput = pipe task.standardError = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8) task.waitUntilExit() return (output, task.terminationStatus) } 

Si desea utilizar el entorno bash para llamar a comandos, use la siguiente función bash, que utiliza una versión fija de Legoless. Tuve que eliminar una nueva línea final del resultado de la función de shell.

Swift 3.0: (Xcode8)

 import Foundation func shell(launchPath: String, arguments: [String]) -> String { let task = Process() task.launchPath = launchPath task.arguments = arguments let pipe = Pipe() task.standardOutput = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: String.Encoding.utf8)! if output.characters.count > 0 { //remove newline character. let lastIndex = output.index(before: output.endIndex) return output[output.startIndex ..< lastIndex] } return output } func bash(command: String, arguments: [String]) -> String { let whichPathForCommand = shell(launchPath: "/bin/bash", arguments: [ "-l", "-c", "which \(command)" ]) return shell(launchPath: whichPathForCommand, arguments: arguments) } 

Por ejemplo, para obtener la twig actual de git de trabajo del directorio de trabajo actual:

 let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"]) print("current branch:\(currentBranch)") 

Guión completo basado en la respuesta de Legoless

 #!/usr/bin/env xcrun swift import Foundation func printShell(launchPath: String, arguments: [AnyObject] = []) { let output = shell(launchPath, arguments:arguments) if (output != nil) { println(output!) } } func shell(launchPath: String, arguments: [AnyObject] = []) -> String? { let task = NSTask() task.launchPath = launchPath task.arguments = arguments let pipe = NSPipe() task.standardOutput = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output: String? = NSString(data: data, encoding: NSUTF8StringEncoding) return output } // > ls // > ls -a -g printShell("/bin/ls") printShell("/bin/ls", arguments:["-a", "-g"]) 

Si desea utilizar argumentos de línea de comandos “exactamente” como lo haría en la línea de comandos (sin separar todos los argumentos), intente lo siguiente.

(Esta respuesta mejora con la respuesta de LegoLess y puede usarse en Swift 4 Xcode 9.3)

 func shell(_ command: String) -> String { let task = Process() task.launchPath = "/bin/bash" task.arguments = ["-c", command] let pipe = Pipe() task.standardOutput = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String return output } // Example usage: shell("ls -la") 

Actualizando para Swift 4.0 (que trata de los cambios a la String )

 func shell(launchPath: String, arguments: [String]) -> String { let task = Process() task.launchPath = launchPath task.arguments = arguments let pipe = Pipe() task.standardOutput = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: String.Encoding.utf8)! if output.count > 0 { //remove newline character. let lastIndex = output.index(before: output.endIndex) return String(output[output.startIndex ..< lastIndex]) } return output } func bash(command: String, arguments: [String]) -> String { let whichPathForCommand = shell(launchPath: "/bin/bash", arguments: [ "-l", "-c", "which \(command)" ]) return shell(launchPath: whichPathForCommand, arguments: arguments) } 

Mezclando las respuestas de rintaro y Legoless para Swift 3

 @discardableResult func shell(_ args: String...) -> String { let task = Process() task.launchPath = "/usr/bin/env" task.arguments = args let pipe = Pipe() task.standardOutput = pipe task.launch() task.waitUntilExit() let data = pipe.fileHandleForReading.readDataToEndOfFile() guard let output: String = String(data: data, encoding: .utf8) else { return "" } return output }