Sh

谁想用 Bash 或 Ruby 脚本来维护你的 Swift 项目?反正我不想。让我们用 Swift 吧。

Sh 让你能够用 Swift 来思考你的脚本,轻松调用 shell 命令,并在你的 Swift 程序中使用它们的输出。或者在编排构建脚本时,简单地将所有输出重定向到终端、日志文件或 /dev/null

要查看在 iOS 项目中利用 Sh 的完整示例,请访问 https://github.com/FullQueueDeveloper/SwishExampleiOSProject

动机

Bash 脚本已经让我们走了很远,但是很难理解控制流。而且没有类型安全。许多命令行工具已经有了不错的接口,只是控制流方面需要改进。Sh 通过依赖 Swift 控制流来解决这个问题。

安装

将 Sh 作为依赖项添加到你的 Package.swift

  dependencies: [
    .package(url: "https://github.com/FullQueueDeveloper/Sh.git", from: "1.0.0"),
  ]

编写脚本

从 Shell 获取数据

这是一个简单的例子,我们向 shell 请求日期,格式为自 1970 年以来的秒数。然后我们解析一个 Foundation.TimeInterval,因为它符合 Codable。最后,我们构造一个 Data,并打印它。

import Sh
import Foundation

let timeInterval = try sh(TimeInterval.self, "date +%s")
let date = Date(timeIntervalSince1970: timeInterval)
print("The date is \(date).")

假设这在一个名为 Date 的脚本中,一个 shell 会话可能看起来像这样

 % spx Date
[Sh] Running `swift package --package-path SPX dump-package`, decoding `SwiftPackageDump`
[SPX] Running target named `Date`
[Sh] Running `swift run --package-path SPX Date `
Fetching https://github.com/FullQueueDeveloper/Sh.git from cache
Fetched https://github.com/FullQueueDeveloper/Sh.git (0.24s)
Computing version for https://github.com/FullQueueDeveloper/Sh.git
Computed https://github.com/FullQueueDeveloper/Sh.git at 1.0.1 (0.37s)
Fetching https://github.com/onevcat/Rainbow from cache
Fetched https://github.com/onevcat/Rainbow (0.21s)
Computed https://github.com/FullQueueDeveloper/Sh.git at 1.0.1 (0.00s)
Computing version for https://github.com/onevcat/Rainbow
Computed https://github.com/onevcat/Rainbow at 4.0.1 (0.30s)
Creating working copy for https://github.com/onevcat/Rainbow
Working copy of https://github.com/onevcat/Rainbow resolved at 4.0.1
Creating working copy for https://github.com/FullQueueDeveloper/Sh.git
Working copy of https://github.com/FullQueueDeveloper/Sh.git resolved at 1.0.1
Building for debugging...
[29/29] Linking Date
Build complete! (4.96s)
[Sh] Running `date +%s`, decoding `Double`
The date is 2022-08-10 18:08:24 +0000.

如果所有 package 都已解析,则 shell 会话可能如下所示

 % spx Date
[Sh] Running `swift package --package-path SPX dump-package`, decoding `SwiftPackageDump`
[SPX] Running target named `Date`
[Sh] Running `swift run --package-path SPX Date `
Building for debugging...
Build complete! (0.08s)
[Sh] Running `date +%s`, decoding `Double`
The date is 2022-08-10 18:09:53 +0000.

一个更实质性的例子可能是查询 oplpass 以获取密钥,或者查询 terraform output 以获取有关我们基础设施的信息,或者查询 Apple 的 agvtool 以获取 Xcode 项目的 Apple 版本信息。

长时间运行的脚本

此文件可能位于 scripts/Sources/pre-commit/main.swift 中。也许我们想运行我们的测试,并确认 release 构建也成功。也许我们想在我们的终端中看到 swift test 的输出,以便我们可以对其做出反应,但我们并不真正关心立即看到任何 release 构建输出,乐于将其发送到日志文件。

import Sh
import Foundation

try sh(.terminal, "swift test")
try sh(.file("logs/build.log"), "swift build -c release")

架构

Sh 向 Foundation.Process 添加了方便的扩展。

构造

Sh 使构造 Foundation.Process 更容易。

init(cmd: String, environment: [String: String] = [:], workingDirectory: String? = nil)

运行

Sh 使运行 Process 更容易。 基本方法运行该进程,并将标准输出中的内容作为 Data? 返回。

func runReturningData() throws -> Data?

Sh 添加了一些建立在此基础上的辅助方法。 runReturningTrimmedStringData 解析为 String 并修剪空格。

try Process("echo hello").runReturningTrimmedString() // returns "hello"

Sh 还可以解析 JSON 输出。 给定一个简单的 struct

struct Simple: Decodable {
  let greeting: String
}

我们可以这样解析输出

let simple = try sh(Simple.self, #"echo '{"greeting": "hello"}'"#)
print(simple.greeting) // prints "hello"

Async/await

是的,Sh 支持 Swift 的 async/await。所有方法都有相应的 async 版本。

目标

相关项目

此软件包本身并不试图为各种工具提供特定领域的语言。但是,越来越多的 Sh 支持的包装器为某些命令行工具提供了更好的 API。

关于作者

Twitch 上关注 Full Queue Developer 以进行实时编码,在 Discord 上进行交流,在 Mastodon 上获取更新。如果此库对您有所帮助,您可以在 GitHub 上赞助