我有一个名为foo的脚本。R包含另一个脚本other。R,在同一个目录下:

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

但我想让R找到另一个。R,不管当前工作目录是什么。

换句话说,就是foo。R需要知道自己的路径。我该怎么做呢?


当前回答

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

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

其他回答

我为此做了一个包,在CRAN和GitHub上可用,名为“this.path”。当前版本是1.2.0,发布于2023-01-16,你可以在这里找到它:

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

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

从CRAN安装:

跑龙套::install.packages(“this.path”)

或从GitHub安装开发版本:

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

然后使用它:

this.path: this.path ()

or

库(this.path)

this.path ()

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

this.path() now works within VSCode 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 (that is, "file://absolute or relative path" and "file:///absolute path") better handling of a connection instead of a character string within source this.path is compatible with URLs in source, that is:

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

如果这一点。如果在文件中使用了Path,它将返回“https://host/path/to/file”。这也适用于以“http://”,“ftp://”和“ftps://”开头的URL。举个例子,试试:

source("https://raw.githubusercontent.com/ArcadeAntics/this.path/main/tests/this.path_w_URLs.R")

compatibility with package testthat and knitr, particularly testthat::source_file and knitr::knit introduces function here, similar to here::here, for specifying an absolute file path, relative to the executing script's directory on Windows, in Rgui, added support for all languages listed by list.dirs(system.file(package = "translations"), full.names = FALSE, recursive = FALSE) 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 when using relative paths within source or when running R from a shell (as long as setwd is used AFTER the first call to this.path within that script)

最初的回答:

我的回答比Jerry T的回答好多了。我发现的问题是,他们通过检查变量ofile是否在堆栈上的第一帧中找到来猜测是否进行了源调用。这将不适用于嵌套的源调用,也不适用于来自非全局环境的源调用。另外,顺序是错误的。在检查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, this means `source(this.path())` is an inappropriate
    # source call. the argument 'file' is stored as a promise
    # containing the expression "this.path()". when the value of '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. For 'source', the
    # filename we want is the variable 'ofile' from that function's
    # evaluation environment. For 'sys.source', the filename we want is
    # the variable 'file' from that function's evaluation environment.
    #
    # if that variable does NOT exist, then argument 'file' hasn't been
    # forced and the source call is deemed inappropriate. 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 ask for an object by name using 'get', R is capable of
    # realizing if a variable is asking for its own definition (a
    # recursive promise). The exact error is "promise already under
    # evaluation" which indicates that the promise evaluation is
    # requesting its own value. So we use the 'tryCatch' to get the
    # argument '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)) {
        if (identical(sys.function(n), source) &&
            exists("ofile", envir = sys.frame(n), inherits = FALSE))
        {
            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, mustWork = TRUE))
        }
        else if (identical(sys.function(n), sys.source) &&
                 exists("exprs", envir = sys.frame(n), inherits = FALSE))
        {
            path <- get("file", envir = sys.frame(n), inherits = FALSE)
            if (verbose)
                cat("Source: call to function sys.source\n")
            return(normalizePath(path, mustWork = TRUE))
        }
        else if (identical(sys.function(n), debugSource) &&
                 tryCatch({
                     path <- get("fileName", envir = sys.frame(n), inherits = FALSE)
                     TRUE
                 }, error = function(c) FALSE))
        {
            if (verbose)
                cat("Source: call to function debugSource in RStudio\n")
            return(normalizePath(path, mustWork = TRUE))
        }
    }


    # no appropriate source call was found up the calling stack


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


        # ".rs.api.getActiveDocumentContext" from "tools:rstudio"
        # returns a list of information about the document where your
        # cursor is located
        #
        # ".rs.api.getSourceEditorContext" from "tools:rstudio" returns
        # a list of information about the document open in the current
        # tab
        #
        # element 'id' is a character string, an identification for the document
        # element 'path' is a character string, the path of the document


        context <- get(".rs.api.getActiveDocumentContext",
            "tools:rstudio", inherits = FALSE)()
        active <- context[["id"]] != "#console"
        if (!active) {
            context <- get(".rs.api.getSourceEditorContext",
                "tools:rstudio", inherits = FALSE)()
            if (is.null(context))
                stop("'this.path' used in an inappropriate fashion\n",
                     "* no appropriate source call was found up the calling stack\n",
                     "* R is being run from RStudio with no documents open\n",
                     "  (or source document has no path)")
        }


        path <- context[["path"]]
        Encoding(path) <- "UTF-8"
        if (nzchar(path)) {
            if (verbose)
                cat(if (active)
                    "Source: active document in RStudio\n"
                else "Source: source document in RStudio\n")
            return(normalizePath(path, mustWork = TRUE))
        }
        else stop("'this.path' used in an inappropriate fashion\n",
                  "* no appropriate source call was found up the calling stack\n",
                  if (active)
                      "* active document in RStudio does not exist"
                  else "* source document in RStudio does not exist")
    }


    # if (running R from RStudio before .Platform$GUI is changed)
    # this includes code evaluated in the site-wide startup profile file,
    # user profile, and function .First (see ?Startup) 
    else if (isTRUE(Sys.getpid() == as.integer(Sys.getenv("RSTUDIO_SESSION_PID"))) {
        stop("RStudio has not finished loading")
    }


    # if (running R 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, mustWork = TRUE))
        } else {
            stop("'this.path' used in an inappropriate fashion\n",
                  "* no appropriate source call was found up the calling stack\n",
                  "* R is being run from a shell where argument 'FILE' is missing")
        }
    }


    # if (running R from RGui on Windows)
    else if (.Platform$OS.type == "windows" && .Platform$GUI == "Rgui") {


        # "getWindowsHandles" from "utils" (Windows exclusive) returns
        # a list of external pointers containing the windows handles.
        # The thing of interest are the names of this list, these are
        # the names of the windows belonging to the current R process.
        # Since Rgui can have files besides R scripts open (such as
        # images), a regular expression is used to subset only windows
        # handles with names that start with "R Console" or end with
        # " - R Editor". From there, similar checks are done as in the
        # above section for 'RStudio'


        x <- names(utils::getWindowsHandles(pattern = "^R Console| - R Editor$",
            minimized = TRUE))


        if (!length(x))
            stop("no windows in Rgui; should never happen, please report!")


        active <- !startsWith(x[[1L]], "R Console")
        if (active)
            x <- x[[1L]]
        else if (length(x) >= 2L)
            x <- x[[2L]]
        else stop("'this.path' used in an inappropriate fashion\n",
                  "* no appropriate source call was found up the calling stack\n",
                  "* R is being run from Rgui with no documents open")
        if (x == "Untitled - R Editor")
            stop("'this.path' used in an inappropriate fashion\n",
                 "* no appropriate source call was found up the calling stack\n",
                 if (active)
                     "* active document in Rgui does not exist"
                 else "* source document in Rgui does not exist")
        path <- sub(" - R Editor$", "", x)
        if (verbose)
            cat(if (active)
                "Source: active document in Rgui\n"
            else "Source: source document in Rgui\n")
        return(normalizePath(path, mustWork = TRUE))
    }


    # if (running R from RGui on macOS)
    else if (.Platform$OS.type == "unix" && .Platform$GUI == "AQUA") {
        stop("'this.path' used in an inappropriate fashion\n",
             "* no appropriate source call was found up the calling stack\n",
             "* R is being run from AQUA which is currently unimplemented\n",
             "  consider using RStudio until such a time when this is implemented")
    }


    # otherwise
    else stop("'this.path' used in an inappropriate fashion\n",
              "* no appropriate source call was found up the calling stack\n",
              "* R is being run in an unrecognized manner")
}

这个问题有一个简单的解决办法。这个命令:

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

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

这对我很有用

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

99%的情况你可以简单地使用:

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

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

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