Quero substituir meus scripts de bash de CI por swift. Não consigo descobrir como invocar um comando de terminal normal, como 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
fonte
fonte
#!/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")
Se você gostaria de usar argumentos de linha de comando "exatamente" como faria na linha de comando (sem separar todos os argumentos), tente o seguinte.
(Esta resposta melhora a resposta da LegoLess e pode ser usada no 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")
fonte
/bin/bash
se refere abash-3.2
. Se você quiser usar os recursos mais avançados do bash, altere o caminho (/usr/bin/env bash
geralmente é uma boa alternativa)O problema aqui é que você não pode misturar e combinar Bash e Swift. Você já sabe como executar o script Swift na linha de comando, agora você precisa adicionar os métodos para executar comandos do Shell no Swift. Em resumo do 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 }
O código Swift a seguir será executado
xcodebuild
com argumentos e, em seguida, produzirá o resultado.shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);
Quanto à pesquisa do conteúdo do diretório (que é o que
ls
faz no Bash), sugiro usarNSFileManager
e escanear o diretório diretamente no Swift, em vez da saída do Bash, o que pode ser difícil de analisar.fonte
shell("ls", [])
-'NSInvalidArgumentException', reason: 'launch path not accessible'
Alguma ideia?Função de utilidade em Swift 3.0
Isso também retorna o status de encerramento das tarefas e aguarda a conclusão.
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) }
fonte
import Foundation
ausenteSe você gostaria de usar o ambiente bash para chamar comandos, use a seguinte função bash que usa uma versão corrigida do Legoless. Tive que remover uma nova linha final do resultado da função do 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 exemplo, para obter o branch git de trabalho atual do diretório de trabalho atual:
let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"]) print("current branch:\(currentBranch)")
fonte
Roteiro completo baseado na resposta 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"])
fonte
Apenas para atualizar isso, já que a Apple tornou .launchPath e launch () obsoletos, aqui está uma função de utilitário atualizada para Swift 4 que deve ser um pouco mais preparada para o futuro.
Observação: a documentação da Apple sobre as substituições ( run () , executableURL , etc) está basicamente vazia neste ponto.
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")
Deve ser capaz de colar isso diretamente em um playground para vê-lo em ação.
fonte
Atualizando para Swift 4.0 (lidando com mudanças em
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) }
fonte
Depois de tentar algumas das soluções postadas aqui, descobri que a melhor maneira de executar comandos era usando o
-c
sinalizador para os argumentos.@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")
fonte
Misturando as respostas de Rintaro e 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 }
fonte
Pequena melhoria com o suporte para variáveis 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) }
fonte
Exemplo de uso da classe Process para executar um script Python.
Além disso:
- 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)") } }
fonte
Eu construí SwiftExec , uma pequena biblioteca para a execução de tais comandos:
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!)
É uma biblioteca de arquivo único que pode ser facilmente copiada e colada em projetos ou instalada usando SPM. É testado e simplifica o tratamento de erros.
Há também ShellOut , que também oferece suporte a uma variedade de comandos predefinidos.
fonte