确定执行脚本的路径

问题描述 投票:220回答:24

我有一个名为foo.R的脚本,其中包含另一个脚本other.R,它位于同一目录中:

#!/usr/bin/env Rscript
message("Hello")
source("other.R")

但我希望R找到other.R,无论当前的工作目录是什么。

换句话说,foo.R需要了解自己的道路。我怎样才能做到这一点?

r file path rscript r-faq
24个回答
88
投票

Here有一个简单的解决方案。这个命令:

script.dir <- dirname(sys.frame(1)$ofile)

返回当前脚本文件的路径。保存脚本后它可以工作。


6
投票

我喜欢steamer25的解决方案,因为它似乎对我来说最强大。但是,在RStudio(在Windows中)进行调试时,路径将无法正确设置。原因是如果在RStudio中设置断点,则获取文件使用备用“调试源”命令,该命令将脚本路径设置为略有不同。以下是我目前使用的最终版本,它在调试时考虑了RStudio中的这种替代行为:

# @return full path to this script
get_script_path <- function() {
    cmdArgs = commandArgs(trailingOnly = FALSE)
    needle = "--file="
    match = grep(needle, cmdArgs)
    if (length(match) > 0) {
        # Rscript
        return(normalizePath(sub(needle, "", cmdArgs[match])))
    } else {
        ls_vars = ls(sys.frames()[[1]])
        if ("fileName" %in% ls_vars) {
            # Source'd via RStudio
            return(normalizePath(sys.frames()[[1]]$fileName)) 
        } else {
            # Source'd via R console
            return(normalizePath(sys.frames()[[1]]$ofile))
        }
    }
}

6
投票

我把这个问题的答案结束并扩展到thisfile()中的新函数rprojroot。也适用于与knitr编织。


6
投票

我尝试了几乎所有问题,Getting path of an R scriptGet the path of current scriptFind location of current .R fileR command for setting working directory to source file location in Rstudio,但最后发现自己手动浏览CRAN表并找到了

scriptName图书馆

它提供current_filename()函数,当在RStudio中获取时以及通过R或RScript可执行文件调用时返回脚本的正确完整路径。


5
投票

我自己就是这样做的。要确保脚本的可移植性,请始终使用以下命令:

wd <- setwd(".")
setwd(wd)

它起作用是因为“。”像Unix命令$ PWD一样翻译。将此字符串分配给字符对象允许您将该字符对象插入到setwd()和Presto中,您的代码将始终以其当前目录作为工作目录运行,无论它在哪个机器上或在文件结构中它在哪里位于。 (额外的好处:wd对象可以与file.path()一起使用(即file.path(wd,“output_directory”),以允许创建标准输出目录,而不管通往命名目录的文件路径如何。这确实需要您在以这种方式引用之前创建新目录,但这也可以使用wd对象进行帮助。

或者,以下代码执行完全相同的操作:

wd <- getwd()
setwd(wd)

或者,如果您不需要对象中的文件路径,您可以简单地:

setwd(".")

2
投票

您可以将r脚本包装在bash脚本中,并将脚本的路径检索为bash变量,如下所示:

#!/bin/bash
     # [environment variables can be set here]
     path_to_script=$(dirname $0)

     R --slave<<EOF
        source("$path_to_script/other.R")

     EOF

2
投票

我喜欢这种方法:

this.file <- sys.frame(tail(grep('source',sys.calls()),n=1))$ofile
this.dir <- dirname(this.file)

2
投票

请注意,getopt包提供了get_Rscript_filename函数,它只使用此处提供的相同解决方案,但已经在标准R模块中为您编写,因此您不必将“get script path”函数复制并粘贴到每个你写的脚本。


1
投票

参见findSourceTraceback()包装的R.utils

在所有调用帧中查找source()生成的所有'srcfile'对象。这使得可以找出source()当前编写的文件。


1
投票

我的上述实现存在问题,因为我的脚本是从符号链接的目录中运行的,或者至少这就是我认为上述解决方案对我不起作用的原因。按照@ ennuikiller的回答,我把我的Rscript包裹在bash中。我使用pwd -P设置路径变量,它解析了符号链接的目录结构。然后将路径传递给Rscript。

bash.是

#!/bin/bash

# set path variable
path=`pwd -P`

#Run Rscript with path argument
Rscript foo.R $path

foo.R

args <- commandArgs(trailingOnly=TRUE)
setwd(args[1])
source(other.R)

1
投票

我会使用@ steamer25方法的变体。关键是我更喜欢获取最后一个源脚本,即使我的会话是通过Rscript启动的。以下代码段包含在文件中时,将提供包含脚本规范化路径的变量thisScript。我承认(ab)使用source'ing,所以有时我调用Rscript并在--file参数中提供的脚本来源另一个脚本来源另一个...有一天我会投入使我的凌乱代码变成一个包。

thisScript <- (function() {
  lastScriptSourced <- tail(unlist(lapply(sys.frames(), function(env) env$ofile)), 1)

  if (is.null(lastScriptSourced)) {
    # No script sourced, checking invocation through Rscript
    cmdArgs <- commandArgs(trailingOnly = FALSE)
    needle <- "--file="
    match <- grep(needle, cmdArgs)
    if (length(match) > 0) {
      return(normalizePath(sub(needle, "", cmdArgs[match]), winslash=.Platform$file.sep, mustWork=TRUE))
    }
  } else {
    # 'source'd via R console
    return(normalizePath(lastScriptSourced, winslash=.Platform$file.sep, mustWork=TRUE))
  }
})()

63
投票

您可以使用commandArgs函数获取Rscript传递给实际R解释器的所有选项,并搜索--file=。如果您的脚本是从路径启动的,或者它是以完整路径启动的,那么下面的script.name将以'/'开头。否则,它必须相对于cwd,您可以连接两条路径以获得完整路径。

编辑:听起来你只需要上面的script.name并剥离路径的最后一个组成部分。我删除了不需要的cwd()样本并清理了主脚本并发布了我的other.R。只需将此脚本和other.R脚本保存到同一目录chmod +x中,然后运行主脚本即可。

main.R:

#!/usr/bin/env Rscript
initial.options <- commandArgs(trailingOnly = FALSE)
file.arg.name <- "--file="
script.name <- sub(file.arg.name, "", initial.options[grep(file.arg.name, initial.options)])
script.basename <- dirname(script.name)
other.name <- file.path(script.basename, "other.R")
print(paste("Sourcing",other.name,"from",script.name))
source(other.name)

other.R:

print("hello")

输出:

burner@firefighter:~$ main.R
[1] "Sourcing /home/burner/bin/other.R from /home/burner/bin/main.R"
[1] "hello"
burner@firefighter:~$ bin/main.R
[1] "Sourcing bin/other.R from bin/main.R"
[1] "hello"
burner@firefighter:~$ cd bin
burner@firefighter:~/bin$ main.R
[1] "Sourcing ./other.R from ./main.R"
[1] "hello"

这就是我认为德曼正在寻找的东西。


1
投票

您可能只使用99%的案例:

sys.calls()[[1]] [[2]]

它不适用于脚本不是第一个参数的疯狂调用,即source(some args, file="myscript")。在这些奇特的案例中使用@ hadley's。


0
投票
#!/usr/bin/env Rscript
print("Hello")

# sad workaround but works :(
programDir <- dirname(sys.frame(1)$ofile)
source(paste(programDir,"other.R",sep='/'))
source(paste(programDir,"other-than-other.R",sep='/'))

0
投票

Steamer25的方法有效,但前提是路径中没有空格。在macOS上,至少cmdArgs[match]/base/some~+~dir~+~with~+~whitespace/返回类似/base/some\ dir\ with\ whitespace/的东西。

我通过在返回之前用简单的空格替换“〜+〜”来解决这个问题。

thisFile <- function() {
  cmdArgs <- commandArgs(trailingOnly = FALSE)
  needle <- "--file="
  match <- grep(needle, cmdArgs)
  if (length(match) > 0) {
    # Rscript
    path <- cmdArgs[match]
    path <- gsub("\\~\\+\\~", " ", path)
    return(normalizePath(sub(needle, "", path)))
  } else {
    # 'source'd via R console
    return(normalizePath(sys.frames()[[1]]$ofile))
  }
}

显然你仍然可以像aprstar那样扩展else块。


0
投票

令人惊讶的是,R中没有'$ 0'类型的结构!您可以使用system()调用以R编写的bash脚本来执行此操作:

write.table(c("readlink -e $0"), file="scriptpath.sh",col=F, row=F, quote=F)
thisscript <- system("sh scriptpath.sh", intern = TRUE)

然后只需为other.R拆分scriptpath.sh名称

splitstr <- rev(strsplit(thisscript, "\\/")[[1]])
otherscript <- paste0(paste(rev(splitstr[2:length(splitstr)]),collapse="/"),"/other.R")

0
投票

如果不是脚本,foo.R,知道它的路径位置,如果你可以改变你的代码总是从一个常见的source引用所有root'd路径,那么这些可能是一个很大的帮助:

特定

  • /app/deeply/nested/foo.R
  • /app/other.R

这会奏效

#!/usr/bin/env Rscript
library(here)
source(here("other.R"))

有关如何定义项目根的信息,请参阅https://rprojroot.r-lib.org/


50
投票

当从R控制台'源'时,我无法让Suppressingfire的解决方案工作。 使用Rscript时,我无法让hadley的解决方案工作。

两全其美?

thisFile <- function() {
        cmdArgs <- commandArgs(trailingOnly = FALSE)
        needle <- "--file="
        match <- grep(needle, cmdArgs)
        if (length(match) > 0) {
                # Rscript
                return(normalizePath(sub(needle, "", cmdArgs[match])))
        } else {
                # 'source'd via R console
                return(normalizePath(sys.frames()[[1]]$ofile))
        }
}

35
投票
frame_files <- lapply(sys.frames(), function(x) x$ofile)
frame_files <- Filter(Negate(is.null), frame_files)
PATH <- dirname(frame_files[[length(frame_files)]])

不要问我它是如何工作的,因为我忘记了:/


22
投票

来自rakensiGetting path of an R script的答案是最正确和最精彩的恕我直言。然而,它仍然是一个包含虚拟功能的黑客。我在这里引用它,以便让别人更容易找到它。

sourceDir < - getSrcDirectory(function(dummy){dummy})

这给出了放置语句的文件的目录(定义了伪函数的位置)。然后它可以用于设置工作目标并使用相对路径,例如

setwd(sourceDir)
source("other.R")

或创建绝对路径

 source(paste(sourceDir, "/other.R", sep=""))

21
投票

这适合我

library(rstudioapi)    
rstudioapi::getActiveDocumentContext()$path

12
投票

Supressingfire的答案的缩小版本:

source_local <- function(fname){
    argv <- commandArgs(trailingOnly = FALSE)
    base_dir <- dirname(substring(argv[grep("--file=", argv)], 8))
    source(paste(base_dir, fname, sep="/"))
}

11
投票

我的一体! ( - 01/09/2011更新以处理RStudio控制台)

#' current script file (in full path)
#' @description current script file (in full path)
#' @examples
#' works with Rscript, source() or in RStudio Run selection, RStudio Console
#' @export
ez.csf <- function() {
    # http://stackoverflow.com/a/32016824/2292993
    cmdArgs = commandArgs(trailingOnly = FALSE)
    needle = "--file="
    match = grep(needle, cmdArgs)
    if (length(match) > 0) {
        # Rscript via command line
        return(normalizePath(sub(needle, "", cmdArgs[match])))
    } else {
        ls_vars = ls(sys.frames()[[1]])
        if ("fileName" %in% ls_vars) {
            # Source'd via RStudio
            return(normalizePath(sys.frames()[[1]]$fileName))
        } else {
            if (!is.null(sys.frames()[[1]]$ofile)) {
            # Source'd via R console
            return(normalizePath(sys.frames()[[1]]$ofile))
            } else {
                # RStudio Run Selection
                # http://stackoverflow.com/a/35842176/2292993
                pth = rstudioapi::getActiveDocumentContext()$path
                if (pth!='') {
                    return(normalizePath(pth))
                } else {
                    # RStudio Console
                    tryCatch({
                            pth = rstudioapi::getSourceEditorContext()$path
                            pth = normalizePath(pth)
                        }, error = function(e) {
                            # normalizePath('') issues warning/error
                            pth = ''
                        }
                    )
                    return(pth)
                }
            }
        }
    }
}

10
投票

这适合我。只需从命令行参数中获取它,剥离不需要的文本,执行一个dirname,最后从中获取完整路径:

args <- commandArgs(trailingOnly = F)  
scriptPath <- normalizePath(dirname(sub("^--file=", "", args[grep("^--file=", args)])))
© www.soinside.com 2019 - 2024. All rights reserved.