CI bashスクリプトをSwiftに置き換えたい。 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")
更新:Swift3/Xcode8用
コマンドライン引数を「正確に」コマンドラインで使用する場合(すべての引数を分離せずに)使用する場合は、次を試してください。
(この回答は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でシェルコマンドを実行するメソッドを追加する必要があります。 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
を使用して、Bashの出力の代わりにSwiftで直接ディレクトリをスキャンすることをお勧めします。
ユーティリティ関数In Swift 3.
これは、タスクの終了ステータスも返し、完了を待ちます。
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環境を使用する場合は、修正版のLegolessを使用する次のbash関数を使用します。 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)")
レゴレスの答えに基づいた完全なスクリプト
#!/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")
これをプレイグラウンドに直接貼り付けて、動作を確認できる必要があります。
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)
}
Swift 3に対するrintaroとLegolessの回答の混合
@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
}
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)")
}
}