我想用swift替换我的CI bash脚本.我无法弄清楚如何调用普通的终端命令,如ls
或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 ....
rintaro.. 114
如果你不在Swift代码中使用命令输出,那么下面就足够了:
#!/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")
更新:适用于Swift3/Xcode8
如果您想使用bash环境来调用命令,请使用以下bash函数,它使用固定版本的Legoless.我不得不从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) }
例如,获取当前工作目录的当前工作git分支:
let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"]) print("current branch:\(currentBranch)")
如果您想在命令行中"完全"使用命令行参数(不分离所有参数),请尝试以下操作.
(这个答案改进了LegoLess的答案,可以在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")
这里的问题是你不能混淆和匹配Bash和Swift.您已经知道如何从命令行运行Swift脚本,现在需要添加方法以在Swift中执行Shell命令.来自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 }
以下Swift代码将xcodebuild
使用参数执行,然后输出结果.
shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);
至于搜索目录内容(这是ls
Bash中的内容),我建议NSFileManager
直接在Swift中使用和扫描目录,而不是Bash输出,这可能很难解析.
自苹果不推荐使用.launchPath和launch()以来,只是为了对此进行更新,这是Swift 4的更新实用程序函数,应该可以在以后进行验证。
注意:Apple的替换文档(run(),executableURL等)目前基本上为空。
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")
应该可以将其直接粘贴到操场上以查看实际效果。
完整的脚本基于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"])
实用功能在Swift 3.0中
这也返回任务终止状态并等待完成.
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) }
如果你不在Swift代码中使用命令输出,那么下面就足够了:
#!/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")
更新:适用于Swift3/Xcode8
更新Swift 4.0(处理更改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) }