关于 r-cli-app
Rapp 是用于将 R 脚本转为命令行应用的 R 包,可从顶层代码推断参数和子命令。它面向希望获得帮助文本和类型化选项、又不想单独编写解析定义的 R 开发者。
主要功能
- 01从 R 赋值推断命令行参数
- 02支持选项与位置参数
- 03从 switch() 生成子命令
- 04生成可读及机器可读帮助
- 05在 R 中运行和测试脚本
适用场景
- 01将 R 脚本转换为命令行应用
- 02为包脚本添加选项和子命令
- 03使用 testthat 快照测试命令行行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ posit-dev/ skills --skill r-cli-app安装后在 Claude Code 中输入/r-cli-app确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.evals/convert-script/eval.mdinput-script.Rprompt.md.evals/simple-cli/eval.mdprompt.md.evals/subcommand-tool/eval.mdprompt.mdreferences/advanced.md详细介绍
使用 Rapp 构建 CLI 应用
Rapp(v0.3.0)是一个 R 软件包,可直接替代 Rscript,并自动将命令行参数解析为 R 值。它能将简单的 R 脚本变成完善的 CLI 应用,支持参数解析、帮助文本和子命令,无需编写任何样板代码。
R ≥ 4.1.0 | CRAN: install.packages("Rapp") | GitHub: r-lib/Rapp
安装后,将 Rapp 启动器添加到 PATH:
Rapp::install_pkg_cli_apps("Rapp")
这会将 Rapp 可执行文件放在 ~/.local/bin(macOS/Linux)或 %LOCALAPPDATA%\Programs\R\Rapp\bin(Windows)中。
核心概念:脚本就是规范
Rapp 会扫描 R 脚本中的顶层表达式,并将特定模式转换为 CLI 构造。这意味着:
- 同一个脚本通过
source()调用和作为 CLI 工具运行时,行为完全一致。 - 你只需编写普通的 R 代码,Rapp 会根据你编写的内容推断 CLI。
- R 代码中的默认值会成为 CLI 的默认值。
只有顶层赋值会被识别。函数、循环或条件语句中的赋值不会被解析为 CLI 参数。
模式识别:R → CLI 映射
下表是 Rapp 的核心:每种 R 模式都会自动映射到相应的 CLI 界面:
| R 顶层表达式 | CLI 界面 | 说明 |
|---|---|---|
foo <- "text" | --foo <value> | 字符串选项 |
foo <- 1L | --foo <int> | 整数选项 |
foo <- 3.14 | --foo <float> | 浮点数选项 |
foo <- TRUE / FALSE | --foo / --no-foo | 布尔开关 |
foo <- NA_integer_ | --foo <int> | 可选整数(NA = 未设置) |
foo <- NA_character_ | --foo <str> | 可选字符串(NA = 未设置) |
foo <- NULL | 位置参数 | 默认必填 |
foo... <- NULL | 可变位置参数 | 零个或多个值 |
foo <- c() | 可重复的 --foo | 多个字符串值 |
foo <- list() | 可重复的 --foo | 多个按 YAML/JSON 解析的值 |
switch("", cmd1={}, cmd2={}) | 子命令 | app cmd1、app cmd2 |
switch(cmd <- "", ...) | 子命令 | 相同;会将命令名称保存在 cmd 中 |
类型行为
- 非字符串标量会在 CLI 中按 YAML/JSON 解析,并转换为默认值的 R 类型。
n <- 5L表示--n 10会得到整数10L。 - NA 默认值表示参数可选。使用
!is.na(myvar)进行判断。 - 蛇形命名变量名会映射为 kebab-case:
n_flips→--n-flips。 - 位置参数始终以字符字符串形式传入,需手动转换。
脚本结构
Shebang 行
#!/usr/bin/env Rapp
运行 chmod +x 后,可在 macOS/Linux 上直接执行脚本。在 Windows 上,请显式调用 Rapp myscript.R。
Front matter 元数据
在任何代码之前添加 Hash-pipe 注释(#|),即可设置脚本级元数据:
#!/usr/bin/env Rapp
#| name: my-app
#| title: My App
#| description: |
#| A short description of what this app does.
#| Can span multiple lines using YAML block scalar `|`.
name: 字段用于设置帮助输出中的应用名称(默认为文件名)。
各参数注释
将 #| 注释放在所注释赋值语句的正前方:
#| description: Number of coin flips
#| short: 'n'
flips <- 1L
可用的注释字段:
| 字段 | 用途 |
|---|---|
description: | 在 --help 中显示的帮助文本 |
title: | 显示标题(用于子命令和 front matter) |
short: | 单字母别名,例如 'n' → -n |
required: | true/false — 仅用于位置参数 |
val_type: | 覆盖类型:string、integer、float、bool、any |
arg_type: | 覆盖 CLI 类型:option、switch、positional |
action: | 用于可重复选项:replace 或 append |
为常用选项添加 #| short:——用户通常期望常用标志(如 verbose(-v)、output(-o)或 count(-n))有单字母快捷方式。
命名选项
标量字面量赋值会变成命名选项:
name <- "world" # --name <value> (string, default "world")
count <- 1L # --count <int> (integer, default 1)
threshold <- 0.5 # --threshold <flt> (float, default 0.5)
seed <- NA_integer_ # --seed <int> (optional, NA if omitted)
output <- NA_character_ # --output <str> (optional, NA if omitted)
对于可选参数,请检查用户是否提供了该参数:
seed <- NA_integer_
if (!is.na(seed)) set.seed(seed)
布尔开关
TRUE/FALSE 赋值会变成切换开关:
verbose <- FALSE # --verbose or --no-verbose
wrap <- TRUE # --wrap (default) or --no-wrap
值 yes/true/1 会将其设为 TRUE;no/false/0 会将其设为 FALSE。
可重复选项
pattern <- c() # --pattern '*.csv' --pattern 'sales-*' → character vector
threshold <- list() # --threshold 5 --threshold '[10,20]' → list of parsed values
位置参数
位置参数默认必填,使用 NULL 赋值:
#| description: The input file to process.
input_file <- NULL
添加 #| required: false 可将其设为可选。使用 is.null(myvar) 进行测试。
可变位置参数
使用 ... 后缀收集多个位置值:
pkgs... <- c()
# install-pkgs dplyr ggplot2 tidyr → pkgs... = c("dplyr", "ggplot2", "tidyr")
子命令
使用以字符串为第一个参数的 switch() 声明子命令。
switch() 之前的选项是全局选项;分支内的选项则是该子命令的
局部选项。
switch(
command <- "",
#| title: Display the todos
list = {
#| description: Max entries to display (-1 for all).
limit <- 30L
# ... list implementation
},
#| title: Add a new todo
add = {
#| description: Task description to add.
task <- NULL
# ... add implementation
},
#| title: Mark a task as completed
done = {
#| description: Index of the task to complete.
index <- 1L
# ... done implementation
}
)
帮助信息按作用域显示:myapp --help 会列出命令;myapp list --help 会显示
list 专属选项以及全局选项。要嵌套子命令,可在某个分支内再放置一个 switch()。
内置帮助
每个 Rapp 都会自动获得 --help(人类可读)和 --help-yaml(机器可读)。
它们也适用于子命令。
开发与测试
交互式开发
使用 Rapp::run() 在 R 会话中测试脚本:
Rapp::run("path/to/myapp.R", c("--help"))
Rapp::run("path/to/myapp.R", c("--name", "Alice", "--count", "5"))
它会返回求值环境(不可见),供检查使用,并且支持使用
browser() 进行交互式调试。
在软件包中测试 CLI 应用
将 Rapp::run() 与 testthat 快照测试结合使用。通过访问返回的环境来测试计算结果,并使用 expect_snapshot() 测试输出。
请参阅 references/advanced.md,了解详细的测试模式,包括:
- 通过求值环境访问计算结果
- 对帮助输出和格式化文本进行快照测试
- 测试文件副作用和状态变化
完整示例:抛硬币
#!/usr/bin/env Rapp
#| name: flip-coin
#| description: |
#| Flip a coin.
#| description: Number of coin flips
#| short: 'n'
flips <- 1L
sep <- " "
wrap <- TRUE
seed <- NA_integer_
if (!is.na(seed)) {
set.seed(seed)
}
cat(sample(c("heads", "tails"), flips, TRUE), sep = sep, fill = wrap)
flip-coin # heads
flip-coin -n 3 # heads tails heads
flip-coin --seed 42 -n 5
flip-coin --help
生成的帮助信息:
Usage: flip-coin [OPTIONS]
Flip a coin.
Options:
-n, --flips <FLIPS> Number of coin flips [default: 1] [type: integer]
--sep <SEP> [default: " "] [type: string]
--wrap / --no-wrap [default: true]
--seed <SEED> [default: NA] [type: integer]
完整示例:待办事项管理器(子命令)
#!/usr/bin/env Rapp
#| name: todo
#| description: Manage a simple todo list.
#| description: Path to the todo list file.
#| short: s
store <- ".todo.yml"
switch(
command <- "",
list = {
#| description: Max entries to display (-1 for all).
limit <- 30L
tasks <- if (file.exists(store)) yaml::read_yaml(store) else list()
if (!length(tasks)) {
cat("No tasks yet.\n")
} else {
if (limit >= 0L) tasks <- head(tasks, limit)
writeLines(sprintf("%2d. %s\n", seq_along(tasks), tasks))
}
},
add = {
#| description: Task description to add.
task <- NULL
tasks <- if (file.exists(store)) yaml::read_yaml(store) else list()
tasks[[length(tasks) + 1L]] <- task
yaml::write_yaml(tasks, store)
cat("Added:", task, "\n")
},
done = {
#| description: Index of the task to complete.
#| short: i
index <- 1L
tasks <- if (file.exists(store)) yaml::read_yaml(store) else list()
task <- tasks[[as.integer(index)]]
tasks[[as.integer(index)]] <- NULL
yaml::write_yaml(tasks, store)
cat("Completed:", task, "\n")
}
)
todo add "Write quarterly report"
todo list
todo list --limit 5
todo done 1
todo --store /tmp/work.yml list
在 R 软件包中发布 CLI
将 CLI 脚本放在 exec/ 中,并在 DESCRIPTION 的 Imports 中添加 Rapp:
mypkg/
├── DESCRIPTION
├── R/
├── exec/
│ ├── myapp # script with #!/usr/bin/env Rapp shebang
│ └── myapp2
└── man/
用户安装软件包后,即可安装 CLI 启动器:
Rapp::install_pkg_cli_apps("mypkg")
提供一个便捷安装器,让用户无需了解 Rapp:
#' Install mypkg CLI apps
#' @export
install_mypkg_cli <- function(destdir = NULL) {
Rapp::install_pkg_cli_apps(package = "mypkg", destdir = destdir)
}
默认情况下,启动器会设置 --default-packages=base,<pkg>,因此只有 base
和该软件包会自动加载。其他依赖项请使用 library()。
快速参考:常见模式
可选参数使用 NA 还是 NULL
- NA(
NA_integer_、NA_character_)→ 可选的命名选项。 测试:!is.na(x)。 - NULL +
#| required: false→ 可选的位置参数。 测试:!is.null(x)。
stdin/stdout
input_file <- NA_character_
con <- if (is.na(input_file)) file("stdin") else file(input_file, "r")
lines <- readLines(con)
writeLines(lines, stdout())
退出代码和 stderr
message("Error: something went wrong") # writes to stderr
cat("Error:", msg, "\n", file = stderr()) # also stderr
quit(status = 1) # non-zero exit
错误处理
tryCatch({
result <- do_work()
}, error = function(e) {
cat("Error:", conditionMessage(e), "\n", file = stderr())
quit(status = 1)
})
更多参考资料
对于较少见的主题——启动器自定义(#| launcher: 前置元数据)、详细的 Rapp::install_pkg_cli_apps() API 选项,以及更完整的示例
(去重筛选器、可变参数 install-pkg、交互式回退)——请阅读
references/advanced.md。