确定执行脚本的路径

发布于 2024-08-12 13:47:48 字数 280 浏览 4 评论 0 原文

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

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

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

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

I have a script called foo.R that includes another script other.R, which is in the same directory:

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

But I want R to find that other.R no matter what the current working directory.

In other words, foo.R needs to know its own path. How can I do that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

剑心龙吟 2024-08-19 13:47:48

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

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

返回当前脚本文件的路径。保存脚本后即可运行。

Here there is a simple solution for the problem. This command:

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

returns the path of the current script file. It works after the script was saved.

我们只是彼此的过ke 2024-08-19 13:47:48

您可以使用 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"

这就是我相信德曼正在寻找的东西。

You can use the commandArgs function to get all the options that were passed by Rscript to the actual R interpreter and search them for --file=. If your script was launched from the path or if it was launched with a full path, the script.name below will start with a '/'. Otherwise, it must be relative to the cwd and you can concat the two paths to get the full path.

Edit: it sounds like you'd only need the script.name above and to strip off the final component of the path. I've removed the unneeded cwd() sample and cleaned up the main script and posted my other.R. Just save off this script and the other.R script into the same directory, chmod +x them, and run the main script.

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")

output:

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"

This is what I believe dehmann is looking for.

じ违心 2024-08-19 13:47:48

从 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))
        }
}

I couldn't get Suppressingfire's solution to work when 'source'ing from the R console.
I couldn't get hadley's solution to work when using Rscript.

Best of both worlds?

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))
        }
}
终陌 2024-08-19 13:47:48
frame_files <- lapply(sys.frames(), function(x) x$ofile)
frame_files <- Filter(Negate(is.null), frame_files)
PATH <- dirname(frame_files[[length(frame_files)]])

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

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

Don't ask me how it works though, because I've forgotten :/

梦纸 2024-08-19 13:47:48

这对我有用

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

This works for me

library(rstudioapi)    
rstudioapi::getActiveDocumentContext()$path
凉城 2024-08-19 13:47:48

rakensi 来自 获取 R 脚本的路径 是最正确且非常出色的恕我直言。然而,它仍然是一个包含虚拟函数的 hack。我把它引用在这里,是为了让其他人更容易找到。

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

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

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

或创建绝对路径

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

The answer of rakensi from Getting path of an R script is the most correct and really brilliant IMHO. Yet, it's still a hack incorporating a dummy function. I'm quoting it here, in order to have it easier found by others.

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

This gives the directory of the file where the statement was placed (where the dummy function is defined). It can then be used to set the working direcory and use relative paths e.g.

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

or to create absolute paths

 source(paste(sourceDir, "/other.R", sep=""))
长伴 2024-08-19 13:47:48

我为此制作了一个包,可在 CRAN 和 GitHub 上获取,名为 this.path。当前版本是2.5.0 (2024-06-29),您可以在这里找到它:

https://CRAN.R-project.org/package=this.path

https://github.com/ArcadeAntics/this.path

从 CRAN 安装:

utils::install.packages("this.path")

或从 GitHub 安装开发版本:

utils::install.packages("this.path",
    repos = "https://raw.githubusercontent.com/ArcadeAntics/PACKAGES")

## or:

remotes::install_github("ArcadeAntics/this.path")

然后通过以下方式使用它:

this.path::this.path()

或:

library(this.path)
this.path()

下面的答案是我原来的答案,仅供参考,尽管它的功能比上面可用的最新版本要少得多。改进包括:

  • 与以下 GUI 的兼容性:

  • 与以下函数和包的兼容性:

  • 处理文件名在类 Unix 下从 shell 运行 R 脚本时带有空格

  • 处理从 shell 运行 R 脚本的两种用途(-f FILE 和 <代码>--file=FILE)

  • 正确规范化路径当使用带有参数的 source()(chdir = TRUE)

  • 使用 处理 file:// URL >source() 例如 source("file:///path/to/file")source("file:///C:/path/to /文件”)

  • 改进了对 source() 中连接而不是字符串的处理< /p>

  • source() 中 URL 路径名的处理,例如:

    source("https://host/path/to/file")
    

    如果在文件中使用this.path(),它将返回“https://host/path/to/file”。这也适用于以 "http://""ftp://""ftps://" 开头的 URL。作为示例,请尝试:

    源(“https://raw.githubusercontent.com/ArcadeAntics/this.path/main/tests/this.path_w_URLs.R”)
    
  • 引入函数here() // this.proj(),类似于here::here(),用于指定相对于执行脚本的目录的绝对文件路径//执行脚本的项目根目录

  • 将规范化路径保存在其中第一次在脚本中调用 this.path() 时会使用适当的环境,从而可以更快地在同一脚本中使用后续时间,并且独立于工作目录。这意味着 setwd() 将不再破坏 this.path()(只要 setwd() 使用 AFTER< /em> 在该脚本中第一次调用 this.path()


原始答案:

我的答案是对 Jerry T 的回答。我发现的问题是,他们通过检查是否在堆栈的第一帧中找到变量 ofile 来猜测是否进行了 source() 调用。这不适用于嵌套源调用,也不适用于从非全局环境进行的源调用。另外,顺序是错误的。我们必须在检查 shell 参数之前查找源调用。这是我的解决方案:

this.path <- function (verbose = getOption("verbose"))
{
    ## loop through functions that lead here from most recent to
    ## earliest looking for an appropriate source call (a call to
    ## function source / / sys.source / / debugSource in RStudio)
    ##
    ## an appropriate source call is one in which the file argument has
    ## been evaluated (forced)
    ##
    ## for example, `source(this.path())` is an inappropriate source
    ## call. argument 'file' is stored as a promise containing the
    ## expression `this.path()`. when 'file' is requested,
    ## the expression is evaluated at which time there should be two
    ## functions on the calling stack being 'source' and 'this.path'.
    ## clearly, you don't want to request the 'file' argument from that
    ## source call because the value of 'file' is under evaluation
    ## right now! the trick is to ask if 'file' has already been
    ## evaluated, the easiest way of which is to ask if a variable
    ## exists, one which is only created after the expression is
    ## necessarily evaluated.
    ##
    ## if that variable does exist, then argument 'file' has been
    ## forced and the source call is deemed appropriate. otherwise,
    ## the source call is deemed inappropriate and the 'for' loop
    ## moves to the next function up the calling stack
    ##
    ## unfortunately, there is no way to check the argument 'fileName'
    ## has been forced for 'debugSource' since all the work is done
    ## internally in C. Instead, we have to use a 'tryCatch' statement.
    ## When we evaluate a promise, R is capable of realizing if a
    ## variable is asking for its own definition (a recursive promise).
    ## The error is "promise already under evaluation" which indicates
    ## that the promise is requesting its own value. So we use the
    ## 'tryCatch' to get 'fileName' from the evaluation environment of
    ## 'debugSource', and if it does not raise an error, then we are
    ## safe to return that value. If not, the condition returns false
    ## and the 'for' loop moves to the next function up the calling
    ## stack


    debugSource <- if (.Platform$GUI == "RStudio")
        get("debugSource", "tools:rstudio", inherits = FALSE)
    for (n in seq.int(to = 1L, by = -1L, length.out = sys.nframe() - 1L)) {
        fun <- sys.function(n)
        if (identical(fun, source)) {
            if (!exists("ofile", envir = sys.frame(n), inherits = FALSE))
                next
            path <- get("ofile", envir = sys.frame(n), inherits = FALSE)
            if (!is.character(path))
                path <- summary.connection(path)$description
            if (verbose)
                cat("Source: call to function source\n")
            return(normalizePath(path, "/", TRUE))
        }
        else if (identical(fun, sys.source)) {
            if (!exists("exprs", envir = sys.frame(n), inherits = FALSE))
                next
            path <- get("file", envir = sys.frame(n), inherits = FALSE)
            if (verbose)
                cat("Source: call to function sys.source\n")
            return(normalizePath(path, "/", TRUE))
        }
        else if (identical(fun, debugSource)) {
            threw_error <- tryCatch({
                path <- get("fileName", envir = sys.frame(n), inherits = FALSE)
                FALSE
            }, error = function(c) TRUE)
            if (threw_error)
                next
            if (verbose)
                cat("Source: call to function debugSource in RStudio\n")
            return(normalizePath(path, "/", TRUE))
        }
    }


    ## no appropriate source call was found up the calling stack


    ## running from RStudio
    if (.Platform$GUI == "RStudio") {


        ## ".rs.api.getSourceEditorContext" from "tools:rstudio"
        ## returns a list of information about the document open in the
        ## current tab
        ##
        ## element 'path' is a character string, the document's path


        context <- get(".rs.api.getSourceEditorContext",
            "tools:rstudio", inherits = FALSE)()
        if (is.null(context))
            stop("R is running from RStudio with no documents open\n",
                 " (or document has no path)")


        path <- context[["path"]]
        if (nzchar(path)) {
            Encoding(path) <- "UTF-8"
            if (verbose)
                cat("Source: document in RStudio\n")
            return(normalizePath(path, "/", TRUE))
        }
        else stop("document in RStudio does not exist")
    }


    ## running from a shell
    else if (.Platform$OS.type == "windows" && .Platform$GUI == "RTerm" ||  ## on Windows
             .Platform$OS.type == "unix"    && .Platform$GUI == "X11")      ## under Unix-alikes
    {


        argv <- commandArgs()
        ## remove all trailing arguments
        m <- match("--args", argv, 0L)
        if (m)
            argv <- argv[seq_len(m)]
        argv <- argv[-1L]


        ## get all arguments starting with "--file="
        FILE <- argv[startsWith(argv, "--file=")]
        ## remove "--file=" from the start of each string
        FILE <- substring(FILE, 8L)
        ## remove strings "-"
        FILE <- FILE[FILE != "-"]
        n <- length(FILE)
        if (n) {
            FILE <- FILE[[n]]
            if (verbose)
                cat("Source: shell argument 'FILE'\n")
            return(normalizePath(FILE, "/", TRUE))
        } else {
            stop("R is running from a shell and argument 'FILE' is missing")
        }
    }


    ## running from RGui on Windows
    else if (.Platform$OS.type == "windows" && .Platform$GUI == "Rgui") {
        stop("R is running from Rgui which is currently unimplemented\n",
             " consider using RStudio until such a time when this is implemented")
    }


    ## running from RGui on macOS
    else if (.Platform$OS.type == "unix" && .Platform$GUI == "AQUA") {
        stop("R is running from AQUA which is currently unimplemented\n",
             " consider using RStudio until such a time when this is implemented")
    }


    ## otherwise
    else stop("R is running in an unrecognized manner")
}

I've made a package for this, available on CRAN and GitHub, called this.path. The current version is 2.5.0 (2024-06-29), you can find it here:

https://CRAN.R-project.org/package=this.path

https://github.com/ArcadeAntics/this.path

Install it from CRAN:

utils::install.packages("this.path")

or install the development version from GitHub:

utils::install.packages("this.path",
    repos = "https://raw.githubusercontent.com/ArcadeAntics/PACKAGES")

## or:

remotes::install_github("ArcadeAntics/this.path")

and then use it by:

this.path::this.path()

or:

library(this.path)
this.path()

The answer below is my original answer, kept just for reference, though it is quite a bit less functional than the most recent versions available above. Improvements include:

  • compatibility with the following GUIs:

  • compatibility with the following functions and packages:

  • handling filenames with spaces when running an R script from a shell under Unix-alikes

  • handling both uses of running an R script from a shell (-f FILE and --file=FILE)

  • correctly normalizes the path when using source() with argument (chdir = TRUE)

  • handling of file:// URLs with source() such as source("file:///path/to/file") and source("file:///C:/path/to/file")

  • improved handling of a connection instead of a character string within source()

  • handling of URL pathnames in source(), such as:

    source("https://host/path/to/file")
    

    if this.path() was used within the file, it would return "https://host/path/to/file". This also works for URLs starting with "http://", "ftp://", and "ftps://". As an example, try:

    source("https://raw.githubusercontent.com/ArcadeAntics/this.path/main/tests/this.path_w_URLs.R")
    
  • introduces functions here() / / this.proj(), similar to here::here(), for specifying absolute file paths relative to the executing script's directory / / executing script's project root

  • saving the normalized path within its appropriate environment the first time this.path() is called within a script, making it faster to use subsequent times within the same script and being independent of working directory. This means that setwd() will no longer break this.path() (as long as setwd() is used AFTER the first call to this.path() within that script)

Original Answer:

My answer is an improvement upon Jerry T's answer. The issue I found is that they are guessing whether a source() call was made by checking if variable ofile is found in the first frame on the stack. This will not work with nested source calls, nor source calls made from a non-global environment. Additionally, the order is wrong. We must look for source call BEFORE checking the shell arguments. Here is my solution:

this.path <- function (verbose = getOption("verbose"))
{
    ## loop through functions that lead here from most recent to
    ## earliest looking for an appropriate source call (a call to
    ## function source / / sys.source / / debugSource in RStudio)
    ##
    ## an appropriate source call is one in which the file argument has
    ## been evaluated (forced)
    ##
    ## for example, `source(this.path())` is an inappropriate source
    ## call. argument 'file' is stored as a promise containing the
    ## expression `this.path()`. when 'file' is requested,
    ## the expression is evaluated at which time there should be two
    ## functions on the calling stack being 'source' and 'this.path'.
    ## clearly, you don't want to request the 'file' argument from that
    ## source call because the value of 'file' is under evaluation
    ## right now! the trick is to ask if 'file' has already been
    ## evaluated, the easiest way of which is to ask if a variable
    ## exists, one which is only created after the expression is
    ## necessarily evaluated.
    ##
    ## if that variable does exist, then argument 'file' has been
    ## forced and the source call is deemed appropriate. otherwise,
    ## the source call is deemed inappropriate and the 'for' loop
    ## moves to the next function up the calling stack
    ##
    ## unfortunately, there is no way to check the argument 'fileName'
    ## has been forced for 'debugSource' since all the work is done
    ## internally in C. Instead, we have to use a 'tryCatch' statement.
    ## When we evaluate a promise, R is capable of realizing if a
    ## variable is asking for its own definition (a recursive promise).
    ## The error is "promise already under evaluation" which indicates
    ## that the promise is requesting its own value. So we use the
    ## 'tryCatch' to get 'fileName' from the evaluation environment of
    ## 'debugSource', and if it does not raise an error, then we are
    ## safe to return that value. If not, the condition returns false
    ## and the 'for' loop moves to the next function up the calling
    ## stack


    debugSource <- if (.Platform$GUI == "RStudio")
        get("debugSource", "tools:rstudio", inherits = FALSE)
    for (n in seq.int(to = 1L, by = -1L, length.out = sys.nframe() - 1L)) {
        fun <- sys.function(n)
        if (identical(fun, source)) {
            if (!exists("ofile", envir = sys.frame(n), inherits = FALSE))
                next
            path <- get("ofile", envir = sys.frame(n), inherits = FALSE)
            if (!is.character(path))
                path <- summary.connection(path)$description
            if (verbose)
                cat("Source: call to function source\n")
            return(normalizePath(path, "/", TRUE))
        }
        else if (identical(fun, sys.source)) {
            if (!exists("exprs", envir = sys.frame(n), inherits = FALSE))
                next
            path <- get("file", envir = sys.frame(n), inherits = FALSE)
            if (verbose)
                cat("Source: call to function sys.source\n")
            return(normalizePath(path, "/", TRUE))
        }
        else if (identical(fun, debugSource)) {
            threw_error <- tryCatch({
                path <- get("fileName", envir = sys.frame(n), inherits = FALSE)
                FALSE
            }, error = function(c) TRUE)
            if (threw_error)
                next
            if (verbose)
                cat("Source: call to function debugSource in RStudio\n")
            return(normalizePath(path, "/", TRUE))
        }
    }


    ## no appropriate source call was found up the calling stack


    ## running from RStudio
    if (.Platform$GUI == "RStudio") {


        ## ".rs.api.getSourceEditorContext" from "tools:rstudio"
        ## returns a list of information about the document open in the
        ## current tab
        ##
        ## element 'path' is a character string, the document's path


        context <- get(".rs.api.getSourceEditorContext",
            "tools:rstudio", inherits = FALSE)()
        if (is.null(context))
            stop("R is running from RStudio with no documents open\n",
                 " (or document has no path)")


        path <- context[["path"]]
        if (nzchar(path)) {
            Encoding(path) <- "UTF-8"
            if (verbose)
                cat("Source: document in RStudio\n")
            return(normalizePath(path, "/", TRUE))
        }
        else stop("document in RStudio does not exist")
    }


    ## running from a shell
    else if (.Platform$OS.type == "windows" && .Platform$GUI == "RTerm" ||  ## on Windows
             .Platform$OS.type == "unix"    && .Platform$GUI == "X11")      ## under Unix-alikes
    {


        argv <- commandArgs()
        ## remove all trailing arguments
        m <- match("--args", argv, 0L)
        if (m)
            argv <- argv[seq_len(m)]
        argv <- argv[-1L]


        ## get all arguments starting with "--file="
        FILE <- argv[startsWith(argv, "--file=")]
        ## remove "--file=" from the start of each string
        FILE <- substring(FILE, 8L)
        ## remove strings "-"
        FILE <- FILE[FILE != "-"]
        n <- length(FILE)
        if (n) {
            FILE <- FILE[[n]]
            if (verbose)
                cat("Source: shell argument 'FILE'\n")
            return(normalizePath(FILE, "/", TRUE))
        } else {
            stop("R is running from a shell and argument 'FILE' is missing")
        }
    }


    ## running from RGui on Windows
    else if (.Platform$OS.type == "windows" && .Platform$GUI == "Rgui") {
        stop("R is running from Rgui which is currently unimplemented\n",
             " consider using RStudio until such a time when this is implemented")
    }


    ## running from RGui on macOS
    else if (.Platform$OS.type == "unix" && .Platform$GUI == "AQUA") {
        stop("R is running from AQUA which is currently unimplemented\n",
             " consider using RStudio until such a time when this is implemented")
    }


    ## otherwise
    else stop("R is running in an unrecognized manner")
}
自控 2024-08-19 13:47:48

我的全合一! (--01/09/2019 更新以处理 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)
                }
            }
        }
    }
}

My all in one! (--01/09/2019 updated to deal with RStudio Console)

#' 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)
                }
            }
        }
    }
}
梦魇绽荼蘼 2024-08-19 13:47:48

Supressingfire 答案的简化版本:

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

A slimmed down variant of Supressingfire's answer:

source_local <- function(fname){
    argv <- commandArgs(trailingOnly = FALSE)
    base_dir <- dirname(substring(argv[grep("--file=", argv)], 8))
    source(paste(base_dir, fname, sep="/"))
}
少年亿悲伤 2024-08-19 13:47:48

这对我有用。只需将其从命令行参数中删除,去掉不需要的文本,执行目录名,最后从中获取完整路径:

args <- commandArgs(trailingOnly = F)  
scriptPath <- normalizePath(dirname(sub("^--file=", "", args[grep("^--file=", args)])))

This works for me. Just greps it out of the command line arguments, strips off the unwanted text, does a dirname and finally gets the full path from that:

args <- commandArgs(trailingOnly = F)  
scriptPath <- normalizePath(dirname(sub("^--file=", "", args[grep("^--file=", args)])))
一江春梦 2024-08-19 13:47:48

我已将这个问题的答案总结并扩展为 rprojroot。也适用于使用 knitr 进行编织。

I have wrapped up and extended the answers to this question into a new function thisfile() in rprojroot. Also works for knitting with knitr.

仅此而已 2024-08-19 13:47:48

我尝试了这个问题的几乎所有内容,获取 R 脚本的路径, 获取当前脚本的路径< /a>, 查找当前 .R 文件的位置R 命令用于将工作目录设置为 Rstudio 中的源文件位置,但最后发现自己手动浏览 CRAN 表并发现

scriptName

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

I tried almost everything from this question, Getting path of an R script, Get the path of current script, Find location of current .R file and R command for setting working directory to source file location in Rstudio, but at the end found myself manually browsing the CRAN table and found

scriptName library

which provides current_filename() function, which returns proper full path of the script when sourcing in RStudio and also when invoking via R or RScript executable.

眼睛会笑 2024-08-19 13:47:48

我喜欢 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))
        }
    }
}

I liked steamer25's solution as it seems the most robust for my purposes. However, when debugging in RStudio (in windows), the path would not get set properly. The reason being that if a breakpoint is set in RStudio, sourcing the file uses an alternate "debug source" command which sets the script path a little differently. Here is the final version which I am currently using which accounts for this alternate behavior within RStudio when debugging:

# @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))
        }
    }
}
路还长,别太狂 2024-08-19 13:47:48

我也遇到了这个问题,以上解决方案都不适合我。也许有源代码或类似的东西,但还不够清楚。

我发现这个对我来说很优雅的解决方案:

paste0(gsub("\\", "/", fileSnapshot()$path, fixed=TRUE),"/")

其中重要的是 fileSnapshot() ,它为您提供了有关文件的大量信息。它返回一个包含 8 个元素的列表。当您选择 path 作为列表元素时,路径将返回,并以 \\ 作为分隔符,因此其余代码只是更改它。

我希望这有帮助。

I also had this problem, and none of the above solutions worked for me. Maybe with the source or things like that, but it was not clear enough.

I found this, for me elegant, solution:

paste0(gsub("\\", "/", fileSnapshot()$path, fixed=TRUE),"/")

The important thing in that is the fileSnapshot() that gives you a lot of information about a file. It returns a list of 8 elements. When you pick path as the list element, the path is returned with \\ as separator, so the rest of the code is just to change that.

I hope this helps.

我们的影子 2024-08-19 13:47:48

我刚刚自己解决了这个问题。为了确保脚本的可移植性,请始终以以下内容开头:

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

它之所以有效,是因为“.”翻译起来就像 Unix 命令 $PWD。将此字符串分配给字符对象允许您将该字符对象插入到 setwd() 中,并且您的代码将始终以其当前目录作为工作目录运行,无论它在谁的计算机上或它在文件结构中的位置。 (额外的好处:wd 对象可以与 file.path() (即 file.path(wd, "output_directory")一起使用,以允许创建标准输出目录,而不管通向指定目录的文件路径如何。这确实要求您在以这种方式引用它之前创建新目录,但这也可以通过 wd 对象来辅助。

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

wd <- getwd()
setwd(wd)

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

setwd(".")

I just worked this out myself. To ensure portability of your script always begin it with:

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

It works because "." translates like the Unix command $PWD. Assigning this string to a character object allows you to then insert that character object into setwd() and Presto your code will always run with its current directory as the working directory, no matter whose machine it is on or where in the file structure it is located. (Extra bonus: The wd object can be used with file.path() (ie. file.path(wd, "output_directory") to allow for the creation of a standard output directory regardless of the file path leading to your named directory. This does require you to make the new directory before referencing it this way but that, too, can be aided with the wd object.

Alternately, the following code performs the exact same thing:

wd <- getwd()
setwd(wd)

or, if you don't need the file path in an object you can simply:

setwd(".")
忘年祭陌 2024-08-19 13:47:48

您可以将 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

You can wrap the r script in a bash script and retrieve the script's path as a bash variable like so:

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

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

     EOF
绅刃 2024-08-19 13:47:48

我喜欢这种方法:

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

I like this approach:

this.file <- sys.frame(tail(grep('source',sys.calls()),n=1))$ofile
this.dir <- dirname(this.file)
晚风撩人 2024-08-19 13:47:48

请注意,getopt 包提供了 get_Rscript_filename 函数,该函数仅使用此处提供的相同解决方案,但已在标准 R 模块中为您编写,因此您无需复制并粘贴“将脚本路径”函数添加到您编写的每个脚本中。

Note that the getopt package provides the get_Rscript_filename function, which just uses the same solution presented here, but is already written for you in a standard R module, so you don't have to copy and paste the "get script path" function into every script you write.

2024-08-19 13:47:48

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

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

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 块。

Steamer25's approach works, but only if there is no whitespace in the path. On macOS at least the cmdArgs[match] returns something like /base/some~+~dir~+~with~+~whitespace/ for /base/some\ dir\ with\ whitespace/.

I worked around this by replacing the "~+~" with a simple whitespace before returning it.

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))
  }
}

Obviously you can still extend the else block like aprstar did.

看海 2024-08-19 13:47:48

如果不是脚本,foo.R,知道它的路径位置,如果您可以更改代码以始终引用来自公共根的所有路径 那么这些可能会有很大的帮助:

给定

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

这将起作用

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

请参阅https: //rprojroot.r-lib.org/ 了解如何定义项目根。

If rather than the script, foo.R, knowing its path location, if you can change your code to always reference all source'd paths from a common root then these may be a great help:

Given

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

This will work

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

See https://rprojroot.r-lib.org/ for how to define project roots.

多彩岁月 2024-08-19 13:47:48

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

Bash.sh

#!/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)

I had issues with the implementations above as my script is operated from a symlinked directory, or at least that's why I think the above solutions didn't work for me. Along the lines of @ennuikiller's answer, I wrapped my Rscript in bash. I set the path variable using pwd -P, which resolves symlinked directory structures. Then pass the path into the Rscript.

Bash.sh

#!/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)
像你 2024-08-19 13:47:48

我会使用 @steamer25 方法的变体。重点是,即使我的会话是通过 Rscript 启动的,我也更喜欢获取最后的源脚本。以下代码片段包含在文件中时,将提供一个变量 thisScript ,其中包含脚本的规范化路径。
我承认(滥用)使用了 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))
  }
})()

I would use a variant of @steamer25 's approach. The point is that I prefer to obtain the last sourced script even when my session was started through Rscript. The following snippet, when included on a file, will provided a variable thisScript containing the normalized path of the script.
I confess the (ab)use of source'ing, so sometimes I invoke Rscript and the script provided in the --file argument sources another script that sources another one... Someday I will invest in making my messy code turns into a package.

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))
  }
})()
节枝 2024-08-19 13:47:48

99% 的情况您可能会简单地使用:

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

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

99% of the cases you might simply use:

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

It will not work for crazy calls where the script is not the first argument, i.e., source(some args, file="myscript"). Use @hadley's in these fancy cases.

执笏见 2024-08-19 13:47:48

通过查看调用堆栈,我们可以获得正在执行的每个脚本的文件路径,最有用的两个脚本可能是当前正在执行的脚本,或者是要获取的第一个脚本(条目)。

script.dir.executing = (function() return( if(length(sys.parents())==1) getwd() else dirname( Filter(is.character,lapply(rev(sys.frames()),function(x) x$ofile))[[1]] ) ))()

script.dir.entry = (function() return( if(length(sys.parents())==1) getwd() else dirname(sys.frame(1)$ofile) ))()

By looking at the call stack we can get the filepath of each script being executed, the two most useful will probably either be the currently executing script, or the first script to be sourced (entry).

script.dir.executing = (function() return( if(length(sys.parents())==1) getwd() else dirname( Filter(is.character,lapply(rev(sys.frames()),function(x) x$ofile))[[1]] ) ))()

script.dir.entry = (function() return( if(length(sys.parents())==1) getwd() else dirname(sys.frame(1)$ofile) ))()
一桥轻雨一伞开 2024-08-19 13:47:48

我发现的最灵活的解决方案是利用 rstudioapi::getSourceEditorContext() 和(可选)sub()

  • 对 .Rmd 和 .R 脚本进行交互
  • 工作编织 .Rmd 文件
  • 在获取 .R 文件时有效

请尝试以下操作:

current_file <-
  rstudioapi::getSourceEditorContext()$path %>%
  sub(".*/", "", .)

rstudioapi::getSourceEditorContext()$path 返回当前文件的完整路径

sub(".*/ ", "", .) 提取最后一个 / 之后的所有内容,仅保留文件名。

我希望这有帮助!

The most flexible solution to this that I have found utilizes rstudioapi::getSourceEditorContext() and (optionally) sub()

  • Work interactively for both .Rmd and .R scripts
  • Works when knitting a .Rmd file
  • Works when sourcing a .R file

Try the following:

current_file <-
  rstudioapi::getSourceEditorContext()$path %>%
  sub(".*/", "", .)

The rstudioapi::getSourceEditorContext()$path returns the full path of the current file

The sub(".*/", "", .) extracts everything after the last /, leaving only the name of the file.

I hope that helps!

我不是你的备胎 2024-08-19 13:47:48

请参阅 R.utilsfindSourceTraceback() > 包,其中

查找所有调用帧中由 source() 生成的所有“srcfile”对象。
这使得可以找出哪些文件当前由 source() 编写脚本。

See findSourceTraceback() of the R.utils package, which

Finds all 'srcfile' objects generated by source() in all call frames.
This makes it possible to find out which files are currently scripted by source().

心安伴我暖 2024-08-19 13:47:48
#!/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='/'))
#!/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='/'))
骄傲 2024-08-19 13:47:48

令人惊奇的是,R 中没有“$0”类型结构!您可以通过对用 R 编写的 bash 脚本进行 system() 调用来完成此操作:

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

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

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

Amazing there is no '$0' type structure in R! You can do it with a system() call to a bash script written in R:

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

Then just split out the scriptpath.sh name for other.R

splitstr <- rev(strsplit(thisscript, "\\/")[[1]])
otherscript <- paste0(paste(rev(splitstr[2:length(splitstr)]),collapse="/"),"/other.R")
來不及說愛妳 2024-08-19 13:47:48

我在 HPC 集群环境中工作。我在与生产运行不同的位置开发代码。在开发过程中,我通常从命令行以交互方式调用 R(使用 RStudio)。有很多 source("foo.R") 正在进行。

在生产运行期间,我通常编写一个 bash 脚本来尝试不同的参数并在单独的目录中运行每组参数。 bash 脚本利用工作负载管理器(即 SLURM)。在这种环境下,设置环境变量是微不足道的。考虑到这一点,以下解决方案最适合我。

other.R

my_message <- function(){
return("R is awkward")
}

foo.R

srcpath = Sys.getenv("R_SRC")
# Check if runnning w/o setting R_SRC - presumably done in directory of development, i.e. /path/to/R/code
if(srcpath == ""){
    srcpath="./"
}
source(sprintf("%s/other.R", srcpath))
string = my_message()
print(string)

如果从 R 交互式 shell 并在 /path/to/R/code 中运行它,只需执行

> source("foo.R")

If running不是从交互式 shell 运行,也不是从 /path/to/R/code 运行,请先设置环境变量 R_SRC,然后调用 Rscript

$ export R_SRC=/path/to/R/code/
$ Rscript /path/to/R/code/foo.R

I work in an HPC cluster environment. I develop my code in a different location from where I do my production runs. During development, I'm usually calling R interactively from the command line (not using RStudio). There is lots of source("foo.R") going on.

During production runs, I usually write a bash script that tries different parameters and runs each set of parameters in a separate directory. The bash script utilizes the workload manager (i.e. SLURM). In this environment, it is trivial to set an environmental variable. With this in mind, the below solution works best for me.

other.R

my_message <- function(){
return("R is awkward")
}

foo.R

srcpath = Sys.getenv("R_SRC")
# Check if runnning w/o setting R_SRC - presumably done in directory of development, i.e. /path/to/R/code
if(srcpath == ""){
    srcpath="./"
}
source(sprintf("%s/other.R", srcpath))
string = my_message()
print(string)

If running this from the R interactive shell and within /path/to/R/code, simply do

> source("foo.R")

If running not from the interactive shell and not running from /path/to/R/code, set the environmental variable R_SRC first, then call Rscript

$ export R_SRC=/path/to/R/code/
$ Rscript /path/to/R/code/foo.R
埋葬我深情 2024-08-19 13:47:48

解决方案于 2016 年问世。非常感谢作者 Sahil Seth!

CRAN 上的包 funr并且 github 提供了获取完整路径的函数 sys.script()到当前脚本。它甚至引用了类似的SO post

因此,解决方案是:

myscript.R:

#!/usr/bin/env Rscript
f  <-  funr::sys.script()
show(f)

然后

user@somewhere:/home$ Rscript myscript.R

在命令行执行命令: 将输出,eg:

"/home/path/to/myscript.R"

到控制台。

The solution arrived in 2016. Many thanks to the author, Sahil Seth!

The package funr on CRAN and github provides the function sys.script() which gets the full path to the current script. It even references a similar SO post.

Thus, the solution is:

myscript.R:

#!/usr/bin/env Rscript
f  <-  funr::sys.script()
show(f)

and then executing the command:

user@somewhere:/home$ Rscript myscript.R

at the command line will output, e.g.:

"/home/path/to/myscript.R"

to the console.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文