Je souhaite remplacer mes scripts CI bash par swift. Je ne peux pas comprendre comment appeler une commande de terminal normale telle que ls
ouxcodebuild
#!/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 ....
swift
bash
shell
xcodebuild
Robert
la source
la source
#!/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")
Si vous souhaitez utiliser des arguments de 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 5)
import Foundation func shell(_ command: String) -> String { let task = Process() let pipe = Pipe() task.standardOutput = pipe task.arguments = ["-c", command] task.launchPath = "/bin/bash" task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8)! return output } // Example usage: shell("ls -la")
la source
/bin/bash
référence àbash-3.2
. Si vous souhaitez utiliser les fonctionnalités plus avancées de bash, changez le chemin (c'est/usr/bin/env bash
généralement une bonne alternative)Le problème ici est que vous ne pouvez pas mélanger et assortir Bash et Swift. Vous savez déjà comment exécuter le script Swift à partir de la ligne de commande, vous devez maintenant ajouter les méthodes pour exécuter les commandes Shell dans Swift. En résumé du 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 }
Le code Swift suivant s'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 qui est
ls
fait dans Bash), je suggère d'utiliserNSFileManager
et de scanner le répertoire directement dans Swift, au lieu de la sortie Bash, ce qui peut être difficile à analyser.la source
shell("ls", [])
-'NSInvalidArgumentException', reason: 'launch path not accessible'
Des idées?Fonction utilitaire dans Swift 3.0
Cela renvoie également l'état de fin des tâches et attend la fin.
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) }
la source
import Foundation
missingSi 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 de travail actuelle du répertoire de travail actuel:
let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"]) print("current branch:\(currentBranch)")
la source
Script complet basé sur la réponse de Legoless
#!/usr/bin/env swift import Foundation func printShell(launchPath: String, arguments: [String] = []) { let output = shell(launchPath: launchPath, arguments: arguments) if (output != nil) { print(output!) } } 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 } // > ls // > ls -a -g printShell(launchPath: "/bin/ls") printShell(launchPath: "/bin/ls", arguments:["-a", "-g"])
la source
Juste pour mettre à jour cela puisque Apple a désapprouvé à la fois .launchPath et launch (), voici une fonction utilitaire mise à jour pour Swift 4 qui devrait être un peu plus à l'épreuve du temps.
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 le coller directement dans une aire de jeux pour le voir en action.
la source
Mise à jour pour Swift 4.0 (gestion 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) }
la source
Après avoir essayé certaines des solutions publiées ici, j'ai trouvé que la meilleure façon d'exécuter des commandes était d'utiliser l'
-c
indicateur pour les arguments.@discardableResult func shell(_ command: String) -> (String?, Int32) { let task = Process() task.launchPath = "/bin/bash" task.arguments = ["-c", command] 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) } let _ = shell("mkdir ~/Desktop/test")
la source
Mélanger les réponses de Rintaro et 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 }
la source
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) }
la source
Exemple d'utilisation de la classe Process pour exécuter un script Python.
Aussi:
- 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)") } }
la source
J'ai construit SwiftExec , une petite bibliothèque pour exécuter de telles commandes:
import SwiftExec var result: ExecResult do { result = try exec(program: "/usr/bin/git", arguments: ["status"]) } catch { let error = error as! ExecError result = error.execResult } print(result.exitCode!) print(result.stdout!) print(result.stderr!)
C'est une bibliothèque à un seul fichier qui peut facilement être copiée-collée dans des projets ou installée à l'aide de SPM. Il est testé et simplifie la gestion des erreurs.
Il existe également ShellOut , qui prend également en charge une variété de commandes prédéfinies.
la source