Je souhaite remplacer mes scripts CI bash par Swift. Je n'arrive pas à comprendre comment appeler une commande de terminal normale telle que ls
ou 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 vous n'utilisez pas les sorties de commande dans Swift, ce qui suit serait suffisant:
#!/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")
Mise à jour: pour Swift3/Xcode8
Si vous souhaitez utiliser les arguments de la ligne de commande "exactement" comme vous le feriez en ligne de commande (sans séparer tous les arguments), essayez ce qui suit.
(Cette réponse améliore la réponse de LegoLess et peut être utilisée dans 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")
Le problème ici est que vous ne pouvez pas mélanger Bash et Swift. Vous savez déjà comment exécuter Swift en ligne de commande, vous devez maintenant ajouter les méthodes permettant d'exécuter les commandes Shell dans Swift. En résumé, à partir de PracticalSwift blog:
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
}
Le code Swift) suivant exécutera xcodebuild
avec des arguments, puis affichera le résultat.
Shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);
En ce qui concerne la recherche du contenu du répertoire (ce que ls
fait dans Bash), je suggère d’utiliser NSFileManager
et d’analyser le répertoire directement dans Swift, au lieu de la sortie Bash, ce qui peut être difficile à analyser .
Fonction utilitaire In Swift 3.
Cela renvoie également l'état de fin des tâches et attend son achèvement.
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 vous souhaitez utiliser l'environnement bash pour appeler des commandes, utilisez la fonction bash suivante, qui utilise une version corrigée de Legoless. J'ai dû supprimer une nouvelle ligne du résultat de la fonction 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)
}
Par exemple, pour obtenir la branche git active du répertoire de travail actuel:
let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"])
print("current branch:\(currentBranch)")
Script complet basé sur la réponse 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"])
Mise à jour pour Swift 4.0 (traitant des modifications apportées à 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)
}
Juste pour mettre à jour ceci depuis Apple a déconseillé à la fois les .launchPath et launch (), voici une fonction utilitaire mise à jour pour Swift 4 qui devrait être un peu plus fiable .
Remarque: la documentation d'Apple sur les remplacements ( run () , executableURL , etc.) est pratiquement vide à ce stade.
import Foundation
// wrapper function for Shell commands
// must provide full path to executable
func Shell(_ launchPath: String, _ arguments: [String] = []) -> (String?, Int32) {
let task = Process()
task.executableURL = URL(fileURLWithPath: launchPath)
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
do {
try task.run()
} catch {
// handle errors
print("Error: \(error.localizedDescription)")
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
return (output, task.terminationStatus)
}
// valid directory listing test
let (goodOutput, goodStatus) = Shell("/bin/ls", ["-la"])
if let out = goodOutput { print("\(out)") }
print("Returned \(goodStatus)\n")
// invalid test
let (badOutput, badStatus) = Shell("ls")
Devrait pouvoir coller ceci directement dans un terrain de jeu pour le voir en action.
Petite amélioration avec le support des variables env:
func Shell(launchPath: String,
arguments: [String] = [],
environment: [String : String]? = nil) -> (String , Int32) {
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
if let environment = environment {
task.environment = environment
}
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)
}
Mélanger les réponses de rintaro et de Legoless pour 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
}
Exemple d'utilisation de la classe Process pour exécuter un script Python.
Également:
- added basic exception handling
- setting environment variables (in my case I had to do it to get Google SDK to authenticate correctly)
- arguments
import Cocoa
func shellTask(_ url: URL, arguments:[String], environment:[String : String]) throws ->(String?, String?){
let task = Process()
task.executableURL = url
task.arguments = arguments
task.environment = environment
let outputPipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = outputPipe
task.standardError = errorPipe
try task.run()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: outputData, as: UTF8.self)
let error = String(decoding: errorData, as: UTF8.self)
return (output,error)
}
func pythonUploadTask()
{
let url = URL(fileURLWithPath: "/usr/bin/python")
let pythonScript = "upload.py"
let fileToUpload = "/CuteCat.mp4"
let arguments = [pythonScript,fileToUpload]
var environment = ProcessInfo.processInfo.environment
environment["PATH"]="usr/local/bin"
environment["GOOGLE_APPLICATION_CREDENTIALS"] = "/Users/j.chudzynski/GoogleCredentials/credentials.json"
do {
let result = try shellTask(url, arguments: arguments, environment: environment)
if let output = result.0
{
print(output)
}
if let output = result.1
{
print(output)
}
} catch {
print("Unexpected error:\(error)")
}
}