我试图通过命令行将多个文件路径参数传递给 Rscript,然后可以使用参数解析器对其进行处理。最终我想要这样的东西
Rscript test.R --inputfiles fileA.txt fileB.txt fileC.txt --printvar yes --size 10 --anotheroption helloworld -- etc...
通过命令行传递,并在解析时将结果作为 R 中的数组
args$inputfiles = "fileA.txt", "fileB.txt", "fileC.txt"
我尝试了几种解析器,包括 optparse 和 getopt,但它们似乎都不支持此功能。我知道 argparse 可以,但它目前不适用于 R 版本 2.15.2
有什么想法吗?
谢谢
您描述命令行选项的方式与大多数人期望的使用方式不同。通常,命令行选项将采用单个参数,而没有前面选项的参数将作为参数传递。如果一个参数需要多个项目(如文件列表),我建议使用 strsplit() 解析字符串。
这是一个使用 optparse 的示例:
library (optparse)
option_list <- list ( make_option (c("-f","--filelist"),default="blah.txt",
help="comma separated list of files (default %default)")
)
parser <-OptionParser(option_list=option_list)
arguments <- parse_args (parser, positional_arguments=TRUE)
opt <- arguments$options
args <- arguments$args
myfilelist <- strsplit(opt$filelist, ",")
print (myfilelist)
print (args)
以下是几个运行示例:
$ Rscript blah.r -h
Usage: blah.r [options]
Options:
-f FILELIST, --filelist=FILELIST
comma separated list of files (default blah.txt)
-h, --help
Show this help message and exit
$ Rscript blah.r -f hello.txt
[[1]]
[1] "hello.txt"
character(0)
$ Rscript blah.r -f hello.txt world.txt
[[1]]
[1] "hello.txt"
[1] "world.txt"
$ Rscript blah.r -f hello.txt,world.txt another_argument and_another
[[1]]
[1] "hello.txt" "world.txt"
[1] "another_argument" "and_another"
$ Rscript blah.r an_argument -f hello.txt,world.txt,blah another_argument and_another
[[1]]
[1] "hello.txt" "world.txt" "blah"
[1] "an_argument" "another_argument" "and_another"
注意,对于strsplit,您可以使用正则表达式来确定分隔符。我建议您使用逗号或冒号来分隔列表:
myfilelist <- strsplit (opt$filelist,"[,:]")
虽然当这个问题被问到时,它还没有在 CRAN 上发布,但现在已经有了 argparse 模块的 beta 版本,它可以做到这一点。它基本上是流行的同名 python 模块的包装器,因此您需要安装最新版本的 python 才能使用它。有关详细信息,请参阅安装说明。基本示例包括对任意长的数字列表进行求和,这些数字应该不难修改,因此您可以获取任意长的输入文件列表。
> install.packages("argparse")
> library("argparse")
> example("ArgumentParser")
在你的脚本 test.R 的前面,你把这个:
args <- commandArgs(trailingOnly = TRUE)
hh <- paste(unlist(args),collapse=' ')
listoptions <- unlist(strsplit(hh,'--'))[-1]
options.args <- sapply(listoptions,function(x){
unlist(strsplit(x, ' '))[-1]
})
options.names <- sapply(listoptions,function(x){
option <- unlist(strsplit(x, ' '))[1]
})
names(options.args) <- unlist(options.names)
print(options.args)
获得:
$inputfiles
[1] "fileA.txt" "fileB.txt" "fileC.txt"
$printvar
[1] "yes"
$size
[1] "10"
$anotheroption
[1] "helloworld"
在四处搜索并避免从下往上编写新包之后,我发现使用包 optparse 输入多个参数的最佳方法是用一个字符分隔输入文件,该字符很可能是非法包含在文件名中的(例如,冒号)
Rscript test.R --inputfiles fileA.txt:fileB.txt:fileC.txt etc...
文件名中也可以包含空格,只要空格被转义即可(optparse 将处理这个问题)
Rscript test.R --inputfiles file\ A.txt:file\ B.txt:fileC.txt etc...
最终,如果有一个包(可能是 optparse 的修改版本)能够支持问题和下面提到的多个参数,那就太好了
Rscript test.R --inputfiles fileA.txt fileB.txt fileC.txt
有人会认为这些琐碎的功能可以实现到广泛使用的包中,例如 optparse
干杯
args <- commandArgs(trailingOnly = TRUE)
hh <- paste(unlist(args),collapse=' ')
listoptions <- unlist(strsplit(hh,'--'))[-1]
options.args <- sapply(listoptions,function(x){
unlist(strsplit(x, ' '))[-1]
}, simplify=FALSE)
options.names <- sapply(listoptions,function(x){
option <- unlist(strsplit(x, ' '))[1]
})
names(options.args) <- unlist(options.names)
print(options.args)
optparse
解析器之前调整输入命令行参数,方法是使用替代分隔符(例如“管道”)将空格分隔的输入文件名连接在一起" 字符,不太可能用作文件名的一部分。最后再次反转调整,使用
str_split()
删除分隔符。这是一些示例代码:
#!/usr/bin/env Rscript
library(optparse)
library(stringr)
# ---- Part 1: Helper Functions ----
# Function to collapse multiple input arguments into a single string
# delimited by the "pipe" character
insert_delimiter <- function(rawarg) {
# Identify index locations of arguments with "-" as the very first
# character. These are presumed to be flags. Prepend with a "dummy"
# index of 0, which we'll use in the index step calculation below.
flagloc <- c(0, which(str_detect(rawarg, '^-')))
# Additionally, append a second dummy index at the end of the real ones.
n <- length(flagloc)
flagloc[n+1] <- length(rawarg) + 1
concatarg <- c()
# Counter over the output command line arguments, with multiple input
# command line arguments concatenated together into a single string as
# necessary
ii <- 1
# Counter over the flag index locations
for(ij in seq(1,length(flagloc)-1)) {
# Calculate the index step size between consecutive pairs of flags
step <- flagloc[ij+1]-flagloc[ij]
# Case 1: empty flag with no arguments
if (step == 1) {
# Ignore dummy index at beginning
if (ij != 1) {
concatarg[ii] <- rawarg[flagloc[ij]]
ii <- ii + 1
}
}
# Case 2: standard flag with one argument
else if (step == 2) {
concatarg[ii] <- rawarg[flagloc[ij]]
concatarg[ii+1] <- rawarg[flagloc[ij]+1]
ii <- ii + 2
}
# Case 3: flag with multiple whitespace delimited arguments (not
# currently handled correctly by optparse)
else if (step > 2) {
concatarg[ii] <- rawarg[flagloc[ij]]
# Concatenate multiple arguments using the "pipe" character as a delimiter
concatarg[ii+1] <- paste0(rawarg[(flagloc[ij]+1):(flagloc[ij+1]-1)],
collapse='|')
ii <- ii + 2
}
}
return(concatarg)
}
# Function to remove "pipe" character and re-expand parsed options into an
# output list again
remove_delimiter <- function(rawopt) {
outopt <- list()
for(nm in names(rawopt)) {
if (typeof(rawopt[[nm]]) == "character") {
outopt[[nm]] <- unlist(str_split(rawopt[[nm]], '\\|'))
} else {
outopt[[nm]] <- rawopt[[nm]]
}
}
return(outopt)
}
# ---- Part 2: Example Usage ----
# Prepare list of allowed options for parser, in standard fashion
option_list <- list(
make_option(c('-i', '--inputfiles'), type='character', dest='fnames',
help='Space separated list of file names', metavar='INPUTFILES'),
make_option(c('-p', '--printvar'), type='character', dest='pvar',
help='Valid options are "yes" or "no"',
metavar='PRINTVAR'),
make_option(c('-s', '--size'), type='integer', dest='sz',
help='Integer size value',
metavar='SIZE')
)
# This is the customary pattern that optparse would use to parse command line
# arguments, however it chokes when there are multiple whitespace-delimited
# options included after the "-i" or "--inputfiles" flag.
#opt <- parse_args(OptionParser(option_list=option_list),
# args=commandArgs(trailingOnly = TRUE))
# This works correctly
opt <- remove_delimiter(parse_args(OptionParser(option_list=option_list),
args=insert_delimiter(commandArgs(trailingOnly = TRUE))))
print(opt)
假设上面的文件名为fix_optparse.R
,输出结果如下:
> chmod +x fix_optparse.R
> ./fix_optparse.R --help
Usage: ./fix_optparse.R [options]
Options:
-i INPUTFILES, --inputfiles=INPUTFILES
Space separated list of file names
-p PRINTVAR, --printvar=PRINTVAR
Valid options are "yes" or "no"
-s SIZE, --size=SIZE
Integer size value
-h, --help
Show this help message and exit
> ./fix_optparse.R --inputfiles fileA.txt fileB.txt fileC.txt --printvar yes --size 10
$fnames
[1] "fileA.txt" "fileB.txt" "fileC.txt"
$pvar
[1] "yes"
$sz
[1] 10
$help
[1] FALSE
>
此方法的一个小限制是,如果任何其他参数有可能接受“管道”字符作为有效输入,那么这些参数将不会被正确处理。不过,我认为您可能可以开发该解决方案的稍微复杂的版本来正确处理这种情况。这个简单的版本在大多数情况下都有效,并说明了总体思路。
{argparser}包支持多值参数。 nargs
函数中有
add_argument()
参数,将其指定为
Inf
即可。举个例子:
library(argparser)
cli_args <- c("-s", 2, 3, 5)
arg_parser("Test with multiple values") |>
add_argument("--subject", "sub", type = "numeric", nargs = Inf) |>
parse_args(cli_args)
#> [[1]]
#> [1] FALSE
#>
#> $help
#> [1] FALSE
#>
#> $opts
#> [1] NA
#>
#> $subject
#> [1] 2 3 5