Ich möchte meine CI-Bash-Skripte durch Swift ersetzen. Ich kann nicht herausfinden, wie man einen normalen Terminalbefehl wie ls
oder aufruftxcodebuild
#!/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
quelle
quelle
#!/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")
Wenn Sie Befehlszeilenargumente "genau" wie in der Befehlszeile verwenden möchten (ohne alle Argumente zu trennen), versuchen Sie Folgendes.
(Diese Antwort verbessert die Antwort von LegoLess und kann in Swift 5 verwendet werden.)
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")
quelle
/bin/bash
beziehtbash-3.2
. Wenn Sie die erweiterten Funktionen von bash verwenden möchten, ändern Sie den Pfad (/usr/bin/env bash
normalerweise eine gute Alternative)Das Problem hierbei ist, dass Sie Bash und Swift nicht kombinieren können. Sie wissen bereits, wie das Swift-Skript über die Befehlszeile ausgeführt wird. Jetzt müssen Sie die Methoden zum Ausführen von Shell-Befehlen in Swift hinzufügen. Zusammenfassend aus dem 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 }
Der folgende Swift-Code wird
xcodebuild
mit Argumenten ausgeführt und gibt dann das Ergebnis aus.shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);
Was das Durchsuchen des Verzeichnisinhalts
ls
betrifft (was in Bash der Fall ist), empfehle ich,NSFileManager
das Verzeichnis direkt in Swift zu verwenden und zu scannen, anstatt die Bash-Ausgabe, was schwierig zu analysieren sein kann.quelle
shell("ls", [])
-'NSInvalidArgumentException', reason: 'launch path not accessible'
Irgendwelche Ideen?Dienstprogrammfunktion In Swift 3.0
Dies gibt auch den Status der Aufgabenbeendigung zurück und wartet auf den Abschluss.
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) }
quelle
import Foundation
fehltWenn Sie die Bash-Umgebung zum Aufrufen von Befehlen verwenden möchten, verwenden Sie die folgende Bash-Funktion, die eine feste Version von Legoless verwendet. Ich musste einen nachgestellten Zeilenumbruch aus dem Ergebnis der Shell-Funktion entfernen.
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) }
Zum Beispiel, um den aktuellen Arbeits-Git-Zweig des aktuellen Arbeitsverzeichnisses abzurufen:
let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"]) print("current branch:\(currentBranch)")
quelle
Vollständiges Skript basierend auf Legoless 'Antwort
#!/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"])
quelle
Um dies zu aktualisieren, da Apple sowohl .launchPath als auch launch () nicht mehr unterstützt, finden Sie hier eine aktualisierte Dienstprogrammfunktion für Swift 4, die etwas zukunftssicherer sein sollte.
Hinweis: Die Dokumentation von Apple zu den Ersetzungen ( run () , ausführbare URL usw.) ist zu diesem Zeitpunkt grundsätzlich leer.
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")
Sollte in der Lage sein, dies direkt in einen Spielplatz einzufügen, um es in Aktion zu sehen.
quelle
Aktualisierung für Swift 4.0 (Änderungen an
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) }
quelle
Nachdem ich einige der hier veröffentlichten Lösungen ausprobiert hatte, stellte ich fest, dass der beste Weg zum Ausführen von Befehlen darin bestand, das
-c
Flag für die Argumente zu verwenden.@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")
quelle
Mischen von Rintaro und Legoless 'Antworten für 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 }
quelle
Kleine Verbesserung mit der Unterstützung von env-Variablen:
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) }
quelle
Beispiel für die Verwendung der Process-Klasse zum Ausführen eines Python-Skripts.
Ebenfalls:
- 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)") } }
quelle
Ich habe SwiftExec erstellt , eine kleine Bibliothek zum Ausführen solcher Befehle:
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!)
Es handelt sich um eine Einzeldateibibliothek, die einfach in Projekte kopiert oder mit SPM installiert werden kann. Es wurde getestet und vereinfacht die Fehlerbehandlung.
Es gibt auch ShellOut , das zusätzlich eine Vielzahl vordefinierter Befehle unterstützt.
quelle