已弃用: 请不要再使用此软件包。请改用 Swift 的参数解析器。
CLIKit 框架包含各种方便的实用工具,可以更轻松地用 Swift 编写命令行工具。
CLIKit 在 MIT 许可证下发布。有关更多详细信息,请参见 LICENSE
文件。
通过将以下内容添加到 Package.swift
文件中的 dependencies 数组,将 CLIKit 添加到你的 Swift 包
.package(url: "https://github.com/apparata/CLIKit.git", from: "<version>")
如果你使用的是 Xcode 11 或更高版本,可以通过 File
菜单输入存储库的 URL 来添加 CLIKit
File > Swift Packages > Add Package Dependency...
注意: CLIKit 需要 Swift 5.1 或更高版本。
有生成的 参考文档 可用。
以下各节包含一些关于 CLIKit 中最突出功能的初步信息以及示例。
命令定义的示例
class FibonacciCommand: Command {
let description = "Calculate fibonacci numbers"
@CommandFlag(short: "v", description: "Prints verbose output")
var verbose: Bool
@CommandOption(short: "i", default: 5, regex: #"^\d+$"#,
description: "Number of iterations to perform.")
var iterations: Int
func run() {
let result = fibonacci(iterations, printSteps: verbose)
print("Result: \(result)")
}
}
在可执行参数上运行解析器,然后在命令解析后运行命令处理程序的示例
let command = try CommandLineParser().parse(FibonacciCommand())
try command.run()
如果二进制文件名为 fibonacci
,则可以在 shell 中像这样运行该命令
$ fibonacci -i 4
可以将多个命令组合为子命令
class MathCommand: Commands {
let description = "Perform math operations"
let fibonacci = FibonacciCommand()
let factorize = FactorizeCommand()
let sum = SumCommand()
}
在可执行参数上运行解析器,然后在命令解析后运行命令处理程序的示例
let command = try CommandLineParser().parse(MathCommand())
try command.run()
如果二进制文件名为 math
,则可以像这样在 shell 中运行 fibonacci
子命令
$ math fibonacci -i 4
有几种不同类型的参数
@CommandFlag(short: "v", description: "Prints verbose output")
var verbose: Bool
@CommandOption(short: "i", default: 5, regex: #"^\d+$"#,
description: "Number of iterations to perform.")
var iterations: Int
@CommandRequiredInput(description: "First number")
var numberA: Int
@CommandOptionalInput(description: "Number to factorize")
var number: Int?
@CommandVariadicInput(description: "More numbers")
var numbers: [Int]
启动子进程并捕获其输出的示例
import CLIKit
// Search for Swift using PATH environment variable.
guard let path = ExecutableFinder.find("swift") else {
print("Didn't find swift, exiting.")
exit(1)
}
do {
// Launch Swift as a subprocess and capture its output.
let subprocess = Subprocess(executable: path,
arguments: ["-h"],
captureOutput: true)
try subprocess.spawn()
// Wait for the process to finish.
let result = try subprocess.wait()
// Print the captured output from the subprocess.
print(try result.capturedOutputString())
} catch {
dump(error)
}
使用 TerminalString
结构打印带有 ANSI 终端代码的字符串的示例
Console.print("\(.green)This is green.\(.reset)\(.bold)This is bold.\(.reset)")
如果控制台是“哑”终端或 Xcode 控制台,则将过滤掉 ANSI 终端代码。
Console
类具有一些用于控制台输入和输出的便捷方法
if Console.confirmYesOrNo(question: "Clear the screen?", default: false) {
// Clear the screen.
Console.clear()
} else {
// Do not clear the screen.
}
命令行程序通常在主线程上没有更多代码要运行时结束。要执行异步工作,例如网络请求或在调度队列上运行代码,需要启动运行循环。可以使用 Execution
类的 runUntilTerminated()
方法启动一个运行循环,该循环将一直运行,直到程序被终止,无论是通过编程方式使用 exit()
或类似方法,还是被系统显式终止,例如,如果用户按下 Ctrl-C。
示例
Execution.runUntilTerminated()
有一个可选的闭包参数,用于处理程序终止时任何必要的清理。如果进程收到 SIGINT
(通常是用户按下 Ctrl-C 时)、SIGHUP
(终端断开连接)或 SIGTERM
(终止),则调用该闭包。
示例
Execution.runUntilTerminated { signal in
switch signal {
case .terminate:
// Do any necessary cleanup here.
...
// Return true to allow the system to handle the SIGTERM signal.
return true
case .interrupt:
// Do any necessary cleanup here.
...
// Return false to suppress the SIGINT signal.
// This will not allow Ctrl-C to terminate the program.
return false
}
case .terminalDisconnected:
// Do any necessary cleanup here.
...
// Return false to suppress the SIGHUP signal.
// This will allow the process to run without the terminal.
return false
}
}
ReadEvaluatePrintLoop
类具有内置的命令行编辑器,支持各种常见的键盘快捷键、可自定义的制表符补全、命令行历史记录和多行支持。如果终端是“哑”终端或附加了调试器(例如,如果你想在 Xcode 控制台中运行),它会回退到仅从 stdin 读取缓冲的行。
示例
let readEvaluatePrintLoop = try ReadEvaluatePrintLoop()
readEvaluatePrintLoop.textCompletion = SimpleWordCompletion(completions: [
"banana",
"discombobulated",
"water",
"whatever"
])
try readEvaluatePrintLoop.run { input in
guard !["quit", "exit"].contains(input) else {
return .break
}
Console.write(terminalString: "You entered: \(input)\n")
return .continue
}
CLIKit 包含一个 Path
结构,可以更轻松地处理文件系统路径。
示例
let absolutePath = Path("/usr/bin/zip")
absolutePath.isAbsolute
absolutePath.isRelative
let relativePath = Path("bin/whatever")
relativePath.isAbsolute
relativePath.isRelative
let concatenatedPath = Path("/usr") + Path("/bin")
let messyPath = Path("//usr/../usr/local/bin/./whatever")
messyPath.normalized
let pathFromLiteralString: Path = "/this/is/a/path"
let pathFromEmptyString: Path = ""
let pathFromConcatenatedStrings: Path = "/usr" + "/bin"
let pathFromComponents = Path(components: ["/", "usr/", "bin", "/", "swift"])
let pathFromEmptyComponents = Path(components: [])
let appendedPath = Path("/usr/local").appendingComponent("bin")
let appendedPath3 = Path("/usr/local").appending(Path("bin"))
let appendedPath2 = Path("/usr/local") + Path("bin")
let imagePath = Path("photos/photo").appendingExtension("jpg")
imagePath.extension
let imagePathWithoutExtension = imagePath.deletingExtension
let imagePathWithoutLastComponent = imagePath.deletingLastComponent
absolutePath.exists
absolutePath.isFile
absolutePath.isDirectory
absolutePath.isDeletable
absolutePath.isExecutable
absolutePath.isReadable
absolutePath.isWritable
// Return an array of Path objects representing files in the current directory.
let filesInDirectory = try Path.currentDirectory.contentsOfDirectory
// Change directory to the user's home directory
Path.homeDirectory?.becomeCurrentDirectory()
if let desktop = Path.desktopDirectory {
Path("/path/myfile.txt").copy(to: desktop)
}
desktop.appendingComponent("myfile.txt").remove()
try (desktop + "My Folder").createDirectory()
Path("/path/myscript.sh").setPosixPermissions(0o700)