Chcę zamienić moje skrypty bash CI na szybkie. Nie mogę dowiedzieć się, jak wywołać normalne polecenie terminala, takie jak ls
lubxcodebuild
#!/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
źródło
źródło
#!/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")
Jeśli chcesz używać argumentów wiersza poleceń „dokładnie” tak, jak w wierszu poleceń (bez oddzielania wszystkich argumentów), wypróbuj następujące rozwiązania.
(Ta odpowiedź jest poprawą odpowiedzi LegoLess i może być używana w 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/zsh" task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8)! return output } // Example usage: shell("ls -la")
źródło
/bin/bash
odnoszą się dobash-3.2
. Jeśli chcesz korzystać z bardziej zaawansowanych funkcji basha, zmień ścieżkę (/usr/bin/env bash
zwykle jest to dobra alternatywa)Problem polega na tym, że nie możesz mieszać i dopasowywać Bash i Swift. Wiesz już, jak uruchomić skrypt Swift z wiersza poleceń, teraz musisz dodać metody do wykonywania poleceń powłoki w języku Swift. Podsumowując z bloga 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 }
Poniższy kod Swift zostanie wykonany
xcodebuild
z argumentami, a następnie wyprowadzi wynik.shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);
Jeśli chodzi o przeszukiwanie zawartości katalogu (co
ls
robi w Bash), sugeruję używanieNSFileManager
i skanowanie katalogu bezpośrednio w Swift, zamiast wyjścia Bash, co może być trudne do przeanalizowania.źródło
shell("ls", [])
-'NSInvalidArgumentException', reason: 'launch path not accessible'
jakieś pomysły?Funkcja użyteczności w Swift 3.0
Powoduje to również zwrócenie stanu zakończenia zadań i oczekuje na zakończenie.
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) }
źródło
import Foundation
zaginionyJeśli chcesz używać środowiska bash do wywoływania poleceń, użyj następującej funkcji bash, która używa poprawionej wersji Legoless. Musiałem usunąć kończący znak nowej linii z wyniku funkcji powłoki.
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) }
Na przykład, aby uzyskać bieżącą działającą gałąź git bieżącego katalogu roboczego:
let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"]) print("current branch:\(currentBranch)")
źródło
Pełny scenariusz oparty na odpowiedzi 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"])
źródło
Aby to zaktualizować, ponieważ Apple wycofał zarówno .launchPath, jak i launch (), oto zaktualizowana funkcja narzędzia dla Swift 4, która powinna być nieco bardziej odporna na przyszłość.
Uwaga: dokumentacja Apple dotycząca zamienników ( run () , executableURL itp.) Jest w zasadzie pusta w tym momencie.
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")
Powinien móc wkleić to bezpośrednio do placu zabaw, aby zobaczyć, jak działa.
źródło
Aktualizacja do Swift 4.0 (radzenie sobie ze zmianami w
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) }
źródło
Po wypróbowaniu niektórych z zamieszczonych tutaj rozwiązań stwierdziłem, że najlepszym sposobem wykonywania poleceń jest użycie
-c
flagi dla argumentów.@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")
źródło
Mieszanie odpowiedzi rintaro i Legoless dla 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 }
źródło
Mała poprawa z obsługą zmiennych 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) }
źródło
Przykład użycia klasy Process do uruchomienia skryptu w Pythonie.
Również:
- 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)") } }
źródło
Zbudowałem SwiftExec , małą bibliotekę do uruchamiania takich poleceń:
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!)
Jest to biblioteka jednoplikowa, którą można łatwo skopiować i wkleić do projektów lub zainstalować za pomocą SPM. Jest przetestowany i upraszcza obsługę błędów.
Dostępny jest również ShellOut , który dodatkowo obsługuje różne predefiniowane polecenia.
źródło