r-cli-app
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBuilding CLI Apps with Rapp
使用Rapp构建CLI应用
Rapp (v0.3.0) is an R package that provides a drop-in replacement for
that automatically parses command-line arguments into R values. It turns simple
R scripts into polished CLI apps with argument parsing, help text, and subcommand
support — with zero boilerplate.
RscriptR ≥ 4.1.0 | CRAN: | GitHub:
install.packages("Rapp")r-lib/RappAfter installing, put the launcher on PATH:
Rappr
Rapp::install_pkg_cli_apps("Rapp")This places the executable in (macOS/Linux) or
(Windows).
Rapp~/.local/bin%LOCALAPPDATA%\Programs\R\Rapp\binRapp(v0.3.0)是一款R包,可作为的直接替代工具,能自动将命令行参数解析为R语言数据类型。它无需任何样板代码,就能将简单的R脚本转换为具备参数解析、帮助文本和子命令支持的成熟CLI应用。
RscriptR ≥ 4.1.0 | CRAN安装: | GitHub地址:
install.packages("Rapp")r-lib/Rapp安装完成后,将Rapp启动器添加到PATH中:
r
Rapp::install_pkg_cli_apps("Rapp")此操作会将可执行文件放置在macOS/Linux系统的目录,或Windows系统的目录。
Rapp~/.local/bin%LOCALAPPDATA%\Programs\R\Rapp\binCore Concept: Scripts Are the Spec
核心概念:脚本即规范
Rapp scans top-level expressions of an R script and converts specific
patterns into CLI constructs. This means:
- The same script works identically via and as a CLI tool.
source() - You write normal R code — Rapp infers the CLI from what you write.
- Default values in your R code become the CLI defaults.
Only top-level assignments are recognized. Assignments inside functions,
loops, or conditionals are not parsed as CLI arguments.
Rapp会扫描R脚本的顶层表达式,并将特定模式转换为CLI结构。这意味着:
- 同一脚本通过调用和作为CLI工具运行时表现完全一致。
source() - 你只需编写常规R代码,Rapp会从你的代码中自动推断CLI的结构。
- R代码中的默认值会直接成为CLI的默认参数。
仅顶层赋值语句会被识别,函数、循环或条件语句内部的赋值不会被解析为CLI参数。
Pattern Recognition: R → CLI Mapping
模式识别:R到CLI的映射
This table is the heart of Rapp — each R pattern automatically maps to a
CLI surface:
| R Top-Level Expression | CLI Surface | Notes |
|---|---|---|
| | String option |
| | Integer option |
| | Float option |
| | Boolean toggle |
| | Optional integer (NA = not set) |
| | Optional string (NA = not set) |
| positional arg | Required by default |
| variadic positional | Zero or more values |
| repeatable | Multiple values as strings |
| repeatable | Multiple values parsed as YAML/JSON |
| subcommands | |
| subcommands | Same; captures command name in |
下表是Rapp的核心——每种R模式都会自动映射到对应的CLI元素:
| R顶层表达式 | CLI元素 | 说明 |
|---|---|---|
| | 字符串选项 |
| | 整数选项 |
| | 浮点数选项 |
| | 布尔开关 |
| | 可选整数(NA表示未设置) |
| | 可选字符串(NA表示未设置) |
| 位置参数 | 默认必填 |
| 可变长度位置参数 | 零个或多个值 |
| 可重复 | 多个字符串值 |
| 可重复 | 多个解析为YAML/JSON的值 |
| 子命令 | |
| 子命令 | 同上;将命令名称捕获到 |
Type behavior
类型行为
- Non-string scalars are parsed as YAML/JSON at the CLI and coerced to the
R type of the default. means
n <- 5Lgives integer--n 10.10L - NA defaults signal optional arguments. Test with .
!is.na(myvar) - Snake case variable names map to kebab-case: →
n_flips.--n-flips - Positional args always arrive as character strings — convert manually.
- 非字符串标量在CLI中会被解析为YAML/JSON格式,并强制转换为默认值对应的R类型。例如意味着
n <- 5L会得到整数--n 10。10L - NA默认值表示参数是可选的,可通过判断是否被设置。
!is.na(myvar) - 蛇形命名的变量名会映射为短横线命名:→
n_flips。--n-flips - 位置参数始终以字符串形式传入,需手动转换类型。
Script Structure
脚本结构
Shebang line
Shebang行
r
#!/usr/bin/env RappMakes the script directly executable on macOS/Linux after .
On Windows, call explicitly.
chmod +xRapp myscript.Rr
#!/usr/bin/env Rapp在macOS/Linux系统中,执行后可直接运行该脚本。在Windows系统中,需显式调用。
chmod +xRapp myscript.RFront matter metadata
前置元数据
Hash-pipe comments () before any code set script-level metadata:
#|r
#!/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 `|`.The field sets the app name in help output (defaults to filename).
name:在任何代码之前,使用哈希竖线注释()设置脚本级元数据:
#|r
#!/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:Per-argument annotations
单参数注解
Place comments immediately before the assignment they annotate:
#|r
#| description: Number of coin flips
#| short: 'n'
flips <- 1LAvailable annotation fields:
| Field | Purpose |
|---|---|
| Help text shown in |
| Display title (for subcommands and front matter) |
| Single-letter alias, e.g. |
| |
| Override type: |
| Override CLI type: |
| For repeatable options: |
Add for frequently-used options — users expect single-letter
shortcuts for common flags like verbose (), output (), or count ().
#| short:-v-o-n在赋值语句前紧邻添加注释,为该参数添加注解:
#|r
#| description: Number of coin flips
#| short: 'n'
flips <- 1L可用的注解字段:
| 字段 | 用途 |
|---|---|
| |
| 显示标题(用于子命令和前置元数据) |
| 单字母别名,例如 |
| |
| 覆盖类型: |
| 覆盖CLI类型: |
| 针对可重复选项: |
为常用选项添加注解——用户期望常见标志(如详细输出、输出、计数)有单字母快捷方式。
#| short:-v-o-nNamed Options
命名选项
Scalar literal assignments become named options:
r
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)For optional arguments, test whether the user supplied them:
r
seed <- NA_integer_
if (!is.na(seed)) set.seed(seed)标量字面量赋值会成为命名选项:
r
name <- "world" # --name <value> (字符串,默认值"world")
count <- 1L # --count <int> (整数,默认值1)
threshold <- 0.5 # --threshold <flt> (浮点数,默认值0.5)
seed <- NA_integer_ # --seed <int> (可选,未设置时为NA)
output <- NA_character_ # --output <str> (可选,未设置时为NA)对于可选参数,可通过以下方式判断用户是否传入:
r
seed <- NA_integer_
if (!is.na(seed)) set.seed(seed)Boolean Switches
布尔开关
TRUEFALSEr
verbose <- FALSE # --verbose or --no-verbose
wrap <- TRUE # --wrap (default) or --no-wrapValues // set TRUE; // set FALSE.
yestrue1nofalse0TRUEFALSEr
verbose <- FALSE # --verbose 或 --no-verbose
wrap <- TRUE # 默认开启--wrap,可通过--no-wrap关闭输入//会设置为TRUE;输入//会设置为FALSE。
yestrue1nofalse0Repeatable Options
可重复选项
r
pattern <- c() # --pattern '*.csv' --pattern 'sales-*' → character vector
threshold <- list() # --threshold 5 --threshold '[10,20]' → list of parsed valuesr
pattern <- c() # --pattern '*.csv' --pattern 'sales-*' → 字符向量
threshold <- list() # --threshold 5 --threshold '[10,20]' → 解析后的值列表Positional Arguments
位置参数
Assign for positional args (required by default):
NULLr
#| description: The input file to process.
input_file <- NULLMake optional with . Test with .
#| required: falseis.null(myvar)赋值会创建位置参数(默认必填):
NULLr
#| description: The input file to process.
input_file <- NULL添加可将其设为可选,通过判断是否传入。
#| required: falseis.null(myvar)Variadic positional args
可变长度位置参数
Use suffix to collect multiple positional values:
...r
pkgs... <- c()使用后缀来收集多个位置参数值:
...r
pkgs... <- c()install-pkgs dplyr ggplot2 tidyr → pkgs... = c("dplyr", "ggplot2", "tidyr")
install-pkgs dplyr ggplot2 tidyr → pkgs... = c("dplyr", "ggplot2", "tidyr")
---
---Subcommands
子命令
Use with a string first argument to declare subcommands.
Options before the are global; options inside branches are
local to that subcommand.
switch()switch()r
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
}
)Help is scoped: lists commands; shows
list-specific options plus globals. Subcommands can nest by placing another
inside a branch.
myapp --helpmyapp list --helpswitch()使用函数并传入字符串作为第一个参数来声明子命令。之前的选项为全局选项;分支内部的选项仅对该子命令生效。
switch()switch()r
switch(
command <- "",
#| title: Display the todos
list = {
#| description: Max entries to display (-1 for all).
limit <- 30L
# ... list实现代码
},
#| title: Add a new todo
add = {
#| description: Task description to add.
task <- NULL
# ... add实现代码
},
#| title: Mark a task as completed
done = {
#| description: Index of the task to complete.
index <- 1L
# ... done实现代码
}
)帮助信息支持作用域:会列出所有子命令;会显示list子命令的专属选项及全局选项。子命令可嵌套,只需在分支内部再添加一个即可。
myapp --helpmyapp list --helpswitch()Built-in Help
内置帮助功能
Every Rapp automatically gets (human-readable) and
(machine-readable). These work with subcommands too.
--help--help-yaml所有Rapp应用都会自动支持(人类可读格式)和(机器可读格式),子命令也同样支持这两个参数。
--help--help-yamlDevelopment and Testing
开发与测试
Use to test scripts from an R session:
Rapp::run()r
Rapp::run("path/to/myapp.R", c("--help"))
Rapp::run("path/to/myapp.R", c("--name", "Alice", "--count", "5"))It returns the evaluation environment (invisibly) for inspection, and
supports for interactive debugging.
browser()在R会话中使用来测试脚本:
Rapp::run()r
Rapp::run("path/to/myapp.R", c("--help"))
Rapp::run("path/to/myapp.R", c("--name", "Alice", "--count", "5"))该函数会返回(隐式)脚本的执行环境,便于调试,同时支持进行交互式调试。
browser()Complete Example: Coin Flipper
完整示例:掷硬币工具
r
#!/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)sh
flip-coin # heads
flip-coin -n 3 # heads tails heads
flip-coin --seed 42 -n 5
flip-coin --helpGenerated 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]r
#!/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)sh
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]Complete Example: Todo Manager (Subcommands)
完整示例:待办事项管理器(子命令)
r
#!/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")
}
)sh
todo add "Write quarterly report"
todo list
todo list --limit 5
todo done 1
todo --store /tmp/work.yml listr
#!/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")
}
)sh
todo add "Write quarterly report"
todo list
todo list --limit 5
todo done 1
todo --store /tmp/work.yml listShipping CLIs in an R Package
在R包中发布CLI
Place CLI scripts in and add to in DESCRIPTION:
exec/RappImportsmypkg/
├── DESCRIPTION
├── R/
├── exec/
│ ├── myapp # script with #!/usr/bin/env Rapp shebang
│ └── myapp2
└── man/Users install the CLI launchers after installing the package:
r
Rapp::install_pkg_cli_apps("mypkg")Expose a convenience installer so users don't need to know about Rapp:
r
#' Install mypkg CLI apps
#' @export
install_mypkg_cli <- function(destdir = NULL) {
Rapp::install_pkg_cli_apps(package = "mypkg", destdir = destdir)
}By default, launchers set , so only
and the package are auto-loaded. Use for other dependencies.
--default-packages=base,<pkg>baselibrary()将CLI脚本放置在目录下,并在DESCRIPTION文件中添加到字段:
exec/RappImportsmypkg/
├── DESCRIPTION
├── R/
├── exec/
│ ├── myapp # 包含#!/usr/bin/env Rapp shebang的脚本
│ └── myapp2
└── man/用户安装包后,可通过以下命令安装CLI启动器:
r
Rapp::install_pkg_cli_apps("mypkg")可提供一个便捷的安装函数,让用户无需了解Rapp的存在:
r
#' Install mypkg CLI apps
#' @export
install_mypkg_cli <- function(destdir = NULL) {
Rapp::install_pkg_cli_apps(package = "mypkg", destdir = destdir)
}默认情况下,启动器会设置,因此仅会自动加载包和当前包。如需其他依赖,需使用显式加载。
--default-packages=base,<pkg>baselibrary()Quick Reference: Common Patterns
快速参考:常见模式
NA vs NULL for optional arguments
可选参数的NA与NULL区别
- NA (,
NA_integer_) → optional named option. Test:NA_character_.!is.na(x) - NULL + → optional positional arg. Test:
#| required: false.!is.null(x)
- NA(,
NA_integer_)→ 可选命名选项。判断方式:NA_character_。!is.na(x) - NULL + → 可选位置参数。判断方式:
#| required: false。!is.null(x)
stdin/stdout
标准输入/输出
r
input_file <- NA_character_
con <- if (is.na(input_file)) file("stdin") else file(input_file, "r")
lines <- readLines(con)
writeLines(lines, stdout())r
input_file <- NA_character_
con <- if (is.na(input_file)) file("stdin") else file(input_file, "r")
lines <- readLines(con)
writeLines(lines, stdout())Exit codes and stderr
退出码与标准错误输出
r
message("Error: something went wrong") # writes to stderr
cat("Error:", msg, "\n", file = stderr()) # also stderr
quit(status = 1) # non-zero exitr
message("Error: something went wrong") # 写入标准错误输出
cat("Error:", msg, "\n", file = stderr()) # 同样写入标准错误输出
quit(status = 1) # 非零退出码Error handling
错误处理
r
tryCatch({
result <- do_work()
}, error = function(e) {
cat("Error:", conditionMessage(e), "\n", file = stderr())
quit(status = 1)
})r
tryCatch({
result <- do_work()
}, error = function(e) {
cat("Error:", conditionMessage(e), "\n", file = stderr())
quit(status = 1)
})Additional Reference
更多参考资料
For less common topics — launcher customization ( front matter),
detailed API options, and more complete examples
(deduplication filter, variadic install-pkg, interactive fallback) — read
.
#| launcher:Rapp::install_pkg_cli_apps()references/advanced.md对于较不常见的主题——启动器自定义(前置元数据)、的详细API选项,以及更完整的示例(去重过滤器、可变长度安装包工具、交互式回退)——请阅读。
#| launcher:Rapp::install_pkg_cli_apps()references/advanced.md