使用 Swift 创建命令行工具
创建快速且内存安全的强大 CLI 工具
- 简单易用 使用 Swift 清晰的语法和轻量级的声明式风格,快速开发功能丰富的命令行工具。
- 功能强大 ArgumentParser 提供类型验证、丰富的帮助界面、shell 自动补全等功能,无需繁琐的配置。
- 安全可靠 利用 Swift 的类型安全、内存安全和并发安全特性,无忧开发强大的 CLI 工具。
使用 Argument Parser 构建简单的命令行工具
您可以使用 Swift Argument Parser 库快速构建功能完整的命令行界面。通过创建带有常规 Swift 属性的类型来定义命令。链接多个命令以创建丰富的命令层次结构。
ArgumentParser 提供详细的帮助界面、清晰的错误消息(包含近似匹配检查)、广泛的定制选项等功能。
Swift Argument Parserimport ArgumentParser
@main
struct Repeat: ParsableCommand {
@Argument(help: "The phrase to repeat.")
var phrase: String
@Option(help: "The number of times to repeat 'phrase'.")
var count: Int? = nil
mutating func run() throws {
let repeatCount = count ?? .max
for i in 1...repeatCount {
print(phrase)
}
}
}
ArgumentParser 使用示例
除了详细的帮助界面和开箱即用的错误消息外,您的 ArgumentParser CLI 工具还可以提供自动补全脚本和手册页面,以及通过接口的 JSON 渲染实现可扩展性。
$ repeat yes --count 3
yes
yes
yes
$ repeat --count
Error: Missing value for '--count <count>'
Help: --count <count> The number of times to repeat 'phrase'.
Usage: repeat <phrase> [--count <count>]
See 'repeat --help' for more information.
$ repeat -h
USAGE: repeat <phrase> [--count <count>]
ARGUMENTS:
<phrase> The phrase to repeat.
OPTIONS:
--count <count> The number of times to repeat 'phrase'.
-h, --help Show help information.'
.\" "Generated by swift-argument-parser"
.Dd May 21, 2025
.Dt REPEAT 1
.Os
.Sh NAME
.Nm repeat
.Sh SYNOPSIS
.Nm
.Ar subcommand
.Ar phrase
.Op Fl -count Ar count
.Op Fl -help
.Sh DESCRIPTION
.Bl -tag -width 6n
.It Ar phrase
The phrase to repeat.
.It Fl -count Ar count
The number of times to repeat 'phrase'.
.It Fl h , -help
Show help information.
.It Em help
Show subcommand help information.
.Bl -tag -width 6n
.It Ar subcommands...
.El
.El
.Sh "EXIT STATUS"
.Ex -std
#compdef repeat
__repeat_complete() {
local -ar non_empty_completions=("${@:#(|:*)}")
local -ar empty_completions=("${(M)@:#(|:*)}")
_describe -V '' non_empty_completions -- empty_completions -P $'\'\''
}
__repeat_custom_complete() {
local -a completions
completions=("${(@f)"$("${command_name}" "${@}" "${command_line[@]}")"}")
if [[ "${#completions[@]}" -gt 1 ]]; then
__repeat_complete "${completions[@]:0:-1}"
fi
}
__repeat_cursor_index_in_current_word() {
if [[ -z "${QIPREFIX}${IPREFIX}${PREFIX}" ]]; then
printf 0
else
printf %s "${#${(z)LBUFFER}[-1]}"
fi
}
_repeat() {
emulate -RL zsh -G
setopt extendedglob nullglob numericglobsort
unsetopt aliases banghist
local -xr SAP_SHELL=zsh
local -x SAP_SHELL_VERSION
SAP_SHELL_VERSION="$(builtin emulate zsh -c 'printf %s "${ZSH_VERSION}"')"
local -r SAP_SHELL_VERSION
local context state state_descr line
local -A opt_args
local -r command_name="${words[1]}"
local -ar command_line=("${words[@]}")
local -ir current_word_index="$((CURRENT - 1))"
local -i ret=1
local -ar arg_specs=(
':phrase:'
'--count[The number of times to repeat '\''phrase'\''.]:count:'
'(-h --help)'{-h,--help}'[Show help information.]'
)
_arguments -w -s -S : "${arg_specs[@]}" && ret=0
return "${ret}"
}
_repeat
{
"command" : {
"arguments" : [
{
"abstract" : "The phrase to repeat.",
"isOptional" : false,
"isRepeating" : false,
"kind" : "positional",
"shouldDisplay" : true,
"valueName" : "phrase"
},
{
"abstract" : "The number of times to repeat 'phrase'.",
"isOptional" : true,
"isRepeating" : false,
"kind" : "option",
"names" : [
{
"kind" : "long",
"name" : "count"
}
],
"preferredName" : {
"kind" : "long",
"name" : "count"
},
"shouldDisplay" : true,
"valueName" : "count"
},
{
"abstract" : "Show help information.",
"isOptional" : true,
"isRepeating" : false,
"kind" : "flag",
"names" : [
{
"kind" : "short",
"name" : "h"
},
{
"kind" : "long",
"name" : "help"
}
],
"preferredName" : {
"kind" : "long",
"name" : "help"
},
"shouldDisplay" : true,
"valueName" : "help"
}
],
"commandName" : "repeat",
"shouldDisplay" : true,
"subcommands" : [
{
"abstract" : "Show subcommand help information.",
"arguments" : [
{
"isOptional" : true,
"isRepeating" : true,
"kind" : "positional",
"shouldDisplay" : true,
"valueName" : "subcommands"
},
{
"isOptional" : true,
"isRepeating" : false,
"kind" : "flag",
"names" : [
{
"kind" : "short",
"name" : "h"
},
{
"kind" : "long",
"name" : "help"
},
{
"kind" : "longWithSingleDash",
"name" : "help"
}
],
"preferredName" : {
"kind" : "long",
"name" : "help"
},
"shouldDisplay" : false,
"valueName" : "help"
}
],
"commandName" : "help",
"shouldDisplay" : true,
"superCommands" : [
"repeat"
]
}
]
},
"serializationVersion" : 0
}
让您的命令行工具更出色
Noora 是一个 Swift 包,它"将常见的命令行模式提炼成一个主题化的组件设计系统,实现更丰富和更具交互性的体验。"设计组件包括提示框、进度条和警告框等。
在 GitHub 上查看 Noora使用 Subprocess 处理进程执行
Subprocess 是一个 Swift 库,它提供了精确、符合语言习惯的方式来启动和管理子进程。您可以异步地完整收集子进程的输出,或者使用 AsyncSequence 实时流式处理,这使得按行处理实时到达的输出变得简单。
Subprocess 让您能够精细控制环境变量、参数和许多平台特定的参数,同时充分利用 Swift 的并发特性和类型安全。无论您是在构建 CLI 工具还是服务器端 Swift 应用程序,swift-subprocess 都能完美集成。
Subprocessimport Subprocess
// Launch Nginx and monitor the log file in parallel
async let monitorResult = run(
.path("/usr/bin/tail"),
arguments: ["-f", "/path/to/nginx.log"]
) { execution, standardOutput in
for try await line in standardOutput.lines(encoding: UTF8.self) {
// Parse the log text
if line.contains("500") {
// Oh no, 500 error
}
}
}
let launchResult = try await run(
.name("nginx"), // Lookup executable by name
arguments: ["-c", "/path/to/nginx.conf"]
)
if !launchResult.terminationStatus.isSuccess {
print("Nginx failed to launch: \(launchResult.terminationStatus)")
} else {
print("Nginx launched with PID \(launchResult.processIdentifier)")
}