如何在swift脚本中运行终端命令?(如xcodebuild)

时间:2022-05-01 01:09:45

I want to replace my CI bash scripts with swift. I can't figure out how to invoke normal terminal command such as ls or xcodebuild

我想用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 ....

7 个解决方案

#1


80  

If you don't use command outputs in Swift code, following would be sufficient:

如果在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")

Updated: for Swift3/Xcode8

更新:Swift3 / Xcode8

#2


22  

The problem here is that you cannot mix and match Bash and Swift. You already know how to run Swift script from command line, now you need to add the methods to execute Shell commands in Swift. In summary from PracticalSwift blog:

这里的问题是您不能混合和匹配Bash和Swift。您已经知道如何从命令行运行Swift脚本,现在您需要添加方法来快速执行Shell命令。从实用快速博客总结:

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
}

The following Swift code will execute xcodebuild with arguments and then output the result.

下面的Swift代码将使用参数执行xcodebuild,然后输出结果。

shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);

As for searching the directory contents (which is what ls does in Bash), I suggest using NSFileManager and scanning the directory directly in Swift, instead of Bash output, which can be a pain to parse.

至于搜索目录内容(这就是ls在Bash中做的事情),我建议使用NSFileManager并快速扫描目录,而不是Bash输出,这可能是解析的痛苦。

#3


17  

Utility function In Swift 3.0

效用函数在Swift 3.0。

This also returns the tasks termination status and waits for completion.

这也返回任务终止状态并等待完成。

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)
}

#4


14  

If you'd like to use the bash environment for calling commands use the following bash function which uses a fixed up version of Legoless. I had to remove a trailing newline from the shell function's result.

如果您想要使用bash环境来调用命令,请使用以下bash函数,该函数使用一个固定的Legoless版本。我必须从shell函数的结果中删除一条尾随的新行。

Swift 3.0:(Xcode8)

斯威夫特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)
}

For example to get the current working git branch of the current working directory:

例如,获取当前工作目录的当前工作git分支:

let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"])
print("current branch:\(currentBranch)")

#5


8  

Full script based on Legoless's answer

完整的脚本基于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"])

#6


1  

Updating for Swift 4.0 (dealing with changes to String)

更新Swift 4.0(处理字符串的更改)

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)
}

#7


0  

Mixing rintaro and Legoless's answers for Swift 3

混合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
}

#1


80  

If you don't use command outputs in Swift code, following would be sufficient:

如果在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")

Updated: for Swift3/Xcode8

更新:Swift3 / Xcode8

#2


22  

The problem here is that you cannot mix and match Bash and Swift. You already know how to run Swift script from command line, now you need to add the methods to execute Shell commands in Swift. In summary from PracticalSwift blog:

这里的问题是您不能混合和匹配Bash和Swift。您已经知道如何从命令行运行Swift脚本,现在您需要添加方法来快速执行Shell命令。从实用快速博客总结:

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
}

The following Swift code will execute xcodebuild with arguments and then output the result.

下面的Swift代码将使用参数执行xcodebuild,然后输出结果。

shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);

As for searching the directory contents (which is what ls does in Bash), I suggest using NSFileManager and scanning the directory directly in Swift, instead of Bash output, which can be a pain to parse.

至于搜索目录内容(这就是ls在Bash中做的事情),我建议使用NSFileManager并快速扫描目录,而不是Bash输出,这可能是解析的痛苦。

#3


17  

Utility function In Swift 3.0

效用函数在Swift 3.0。

This also returns the tasks termination status and waits for completion.

这也返回任务终止状态并等待完成。

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)
}

#4


14  

If you'd like to use the bash environment for calling commands use the following bash function which uses a fixed up version of Legoless. I had to remove a trailing newline from the shell function's result.

如果您想要使用bash环境来调用命令,请使用以下bash函数,该函数使用一个固定的Legoless版本。我必须从shell函数的结果中删除一条尾随的新行。

Swift 3.0:(Xcode8)

斯威夫特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)
}

For example to get the current working git branch of the current working directory:

例如,获取当前工作目录的当前工作git分支:

let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"])
print("current branch:\(currentBranch)")

#5


8  

Full script based on Legoless's answer

完整的脚本基于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"])

#6


1  

Updating for Swift 4.0 (dealing with changes to String)

更新Swift 4.0(处理字符串的更改)

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)
}

#7


0  

Mixing rintaro and Legoless's answers for Swift 3

混合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
}