在R中通过命令行传递多个参数

4

我正在尝试通过命令行传递多个文件路径参数给一个R脚本,然后可以使用参数解析器进行处理。最终,我希望像这样:

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中不可用。

有什么建议吗?

谢谢


你能详细说明一下为什么@agstudy的解决方案不起作用吗?它非常准确。 - Ricardo Saporta
此外,可能是https://dev59.com/z3I95IYBdhLWcg3wyRI0 的重复。 - Ricardo Saporta
1
@RicardoSaporta 这不是重复的。它有一点不同。 - agstudy
7个回答

7

您描述命令行选项的方式与大多数人预期使用它们的方式不同。通常,命令行选项将采用单个参数,并且没有前缀选项的参数将作为参数传递。如果参数需要多个项目(例如文件列表),建议使用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,"[,:]")

6
尽管在提出这个问题时它还没有发布到CRAN,但现在已经有一个beta版本的argparse模块可以实现这一点。它基本上是对同名Python模块的包装器,因此您需要安装最新版本的Python才能使用它。有关更多信息,请参见安装说明。所包含的基本示例对任意长度的数字列表求和,这不难修改,以便您可以获取任意长度的输入文件列表。
> install.packages("argparse")
> library("argparse")
> example("ArgumentParser")

4
在您的脚本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"

这对我不起作用。我的参数要复杂得多。大约传递了8-10个选项。因此,需要使用参数解析器。 - Omar Wagih
@Omar 复杂吗?怎么复杂?你可以举个例子吗? - agstudy
当然,我在我的问题中添加了一个示例。如果文件是我作为参数传递的唯一内容,那么您的解决方案对我才有效,不是吗? - Omar Wagih
@Omar 如果你熟悉VB脚本,你可以编写一个并从R中调用它。我认为在SO上很容易找到这个。 - agstudy
我发现这个快速方法存在一些缺失的功能。例如:当文件名中有空格时,转义后变成file\ a.txt,但这种情况无法被检测到。 有没有人能够建议另一种更加健壮的解析参数的方式?或者我必须从头开始编写一个解析器? - Omar Wagih
@Omar 好主意从底部开始做,并分享给大家。 - agstudy

1

@agstudy的解决方案在输入参数长度相同时无法正常工作。默认情况下,sapply将相同长度的输入合并成矩阵而不是列表。修复方法很简单,只需在解析参数的sapply中明确将simplify设置为false即可。

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)

1

在搜索了一番后,我避免从头开始编写新的包,发现使用 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 这样广泛使用的软件包中会实现这样的琐碎功能。

谢谢


2
为什么不解释一下如何使用optparse包? - agstudy
这超出了我的问题范围。Optparse示例可以在此处的文档中找到:http://cran.r-project.org/web/packages/optparse/optparse.pdf - Omar Wagih

0

我曾经遇到过同样的问题,我开发的解决方法是在将输入命令行参数传递给 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

>

这种方法的一个小限制是,如果任何其他参数有可能接受“管道”字符作为有效输入,则这些参数将无法正确处理。但是我认为您可能可以开发一个稍微复杂一些的版本来正确处理该情况。这个简单版本大多数时候都能工作,并且说明了一般思路。

0
刚刚遇到这个问题,幸运的是,{argparser}包支持多值参数。在add_argument()函数中有一个nargs参数,将其指定为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

2023-10-10创建,使用reprex v2.0.2生成


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接