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

Rob*_*ert 64 bash shell xcodebuild swift

我想用swift替换我的CI bash脚本.我无法弄清楚如何调用普通的终端命令,如lsxcodebuild

#!/usr/bin/env xcrun swift

import Foundation // Works
println("Test") // Works
ls // Fails
xcodebuild -workspace myApp.xcworkspace // Fails
Run Code Online (Sandbox Code Playgroud)
$ ./script.swift
./script.swift:5:1: error: use of unresolved identifier 'ls'
ls // Fails
^
... etc ....
Run Code Online (Sandbox Code Playgroud)

rin*_*aro 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")
Run Code Online (Sandbox Code Playgroud)

更新:适用于Swift3/Xcode8

  • 进程仅在 macOS 上可用 (6认同)
  • “ NSTask”已重命名为“ Process” (3认同)
  • @dylan`hell("open"," - a","safari")`应该有效 (3认同)
  • Process()仍在Swift 4中吗?我收到一个未定义的符号。:/ (3认同)
  • 我试过了:我试过了:https://i.imgur.com/Ge1OOCG.png (3认同)

use*_*009 47

如果您想在命令行中"完全"使用命令行参数(不分离所有参数),请尝试以下操作.

(这个答案改进了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")
Run Code Online (Sandbox Code Playgroud)

  • 这个答案确实应该更高,因为它解决了以前的许多问题。 (3认同)
  • 您还应该在调用“task.run()”之前添加“task.standardInput = nil”。这可能很难诊断,因为如果子进程尝试从“stdin”读取,内核将“默默地停止”子进程。这可能很微妙,因为孩子可以在监视“stdin”输入的同时做有用的工作。当您在键盘上输入内容时,孩子会看到有输入等待并调用“read()”。内核通过发送“SIGSTOP”进行响应,并且子进程冻结。我是。有点惊讶这不是“Process()”的默认值。 (2认同)

Leg*_*ess 30

这里的问题是你不能混淆和匹配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
}
Run Code Online (Sandbox Code Playgroud)

以下Swift代码将xcodebuild使用参数执行,然后输出结果.

shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);
Run Code Online (Sandbox Code Playgroud)

至于搜索目录内容(这是lsBash中的内容),我建议NSFileManager直接在Swift中使用和扫描目录,而不是Bash输出,这可能很难解析.

  • NSTask不会像shell一样搜索*可执行文件(使用环境中的PATH).启动路径必须是绝对路径(例如"/ bin/ls")或相对于当前工作目录的路径. (4认同)
  • 使用shell(“ cd”,“〜/ Desktop /”),我得到:/ usr / bin / cd:第4行:cd​​:〜/ Desktop /:没有这样的文件或目录 (2认同)

小智 20

实用功能在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)
}
Run Code Online (Sandbox Code Playgroud)

  • `import Foundation`失踪 (3认同)
  • 可悲的是,不适用于iOS. (3认同)

Pel*_*let 16

如果您想使用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)
}
Run Code Online (Sandbox Code Playgroud)

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

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


Rob*_*ert 9

完整的脚本基于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"])
Run Code Online (Sandbox Code Playgroud)


rou*_*ter 8

更新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)
}
Run Code Online (Sandbox Code Playgroud)


小智 7

在尝试了此处发布的一些解决方案后,我发现执行命令的最佳方法是使用-c参数的标志。

@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")
Run Code Online (Sandbox Code Playgroud)


ang*_*usc 6

自苹果不推荐使用.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")
Run Code Online (Sandbox Code Playgroud)

应该可以将其直接粘贴到操场上以查看实际效果。

  • 如果 Playground 设置平台是“macOS”,则可以在 Swift 5 Playground 中正常工作。“iOS”游乐场平台收到_“无法在范围内找到‘进程’”_错误消息。 (2认同)