我想用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 ....
如果你不在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")
更新:适用于Swift 3 / Xcode 8
支持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)
}
使用Process类运行Python脚本的示例。
也:
- 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)")
}
}
如果您想在命令行中“完全”使用命令行参数(不分离所有参数),请尝试以下操作。
(这个答案改进了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输出,这可能很难解析。
实用功能在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)
}
如果您想使用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的答案
#!/usr/bin/env xcrun swift
import Foundation
func printShell(launchPath: String, arguments: [AnyObject] = []) {
let output = shell(launchPath, arguments:arguments)
if (output != nil) {
println(output!)
}
}
func shell(launchPath: String, arguments: [AnyObject] = []) -> String? {
let task = NSTask()
task.launchPath = launchPath
task.arguments = arguments
let pipe = NSPipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String? = NSString(data: data, encoding: NSUTF8StringEncoding)
return output
}
// > ls
// > ls -a -g
printShell("/bin/ls")
printShell("/bin/ls", arguments:["-a", "-g"])
更新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)
}
只是为了更新这个,因为Apple已经弃用了.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")
应该可以将它直接粘贴到操场上以查看它的运行情况。
混合rintaro和Legoless对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
}