确定执行脚本的路径

Determine path of the executing script

提问人:Frank 提问时间:11/29/2009 最后编辑:Konrad RudolphFrank 更新时间:10/29/2023 访问量:153890

问:

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

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

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

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

R 文件 路径 rscript r-faq

评论

2赞 Frank 12/5/2009
不。:(我还没有看到任何真正有效的解决方案。除了仅传递目录或使用环境变量的解决方法。
4赞 Etienne Low-Décarie 4/4/2012
这将使脚本完全可移植并可被 R neofites 执行,这将是惊人的!
5赞 Etienne Low-Décarie 4/4/2012
似乎所有的答案都要求您在某个时候输入路径(至少要获取文件)!如果可以向某人发送一个压缩文件夹,并且运行该文件夹中的任何 R 脚本文件都将读取并保存到该文件夹,那就太好了。
17赞 Giacomo 10/29/2017
这个问题实际上可能成为我可以完全转向 Python 的原因
9赞 Michael Barton 11/1/2017
@giac_man,我觉得 R 充满了数百个这样的小问题,这些问题加起来使工作变得非常困难。

答:

2赞 ennuikiller 11/29/2009 #1

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

评论

4赞 Etienne Low-Décarie 4/4/2012
这要求您具有脚本路径。它不允许你制作一个真正可移植的 R 脚本,该脚本可以从任何地方运行。
0赞 John Haberstroh 8/29/2017
@EtienneLow-Décarie 它不需要脚本路径,它是从 bash 获取的。主要问题是它不是获取路径的可靠方法。像这样的东西是首选,就像在 stackoverflow.com/questions/59895/ 中一样...... path_to_script=“$( cd ”$( dirname “${BASH_SOURCE[0]}” )“ && pwd )”
83赞 Suppressingfire 11/29/2009 #2

可以使用该函数获取 Rscript 传递给实际 R 解释器的所有选项,并在其中搜索 .如果脚本是从该路径启动的,或者是使用完整路径启动的,则以下内容将以 .否则,它必须是相对于 和 连接这两个路径以获取完整路径。commandArgs--file=script.name'/'cwd

编辑:听起来您只需要上述内容并剥离路径的最终组件。我已经删除了不需要的示例并清理了主脚本并发布了我的 .只需将此脚本和脚本保存到同一目录中,然后运行主脚本即可。script.namecwd()other.Rother.Rchmod +x

主要。其中,中:

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

其他。其中,中:

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"

这就是我相信 dehmann 正在寻找的。

评论

3赞 hadley 12/1/2009
我修改了,因为你的技术不像我认为 OP 想要的那样工作 - 但也许我误读了他/她的要求。但是我无法取消 downmod :(不好意思!source
1赞 Suppressingfire 12/1/2009
但实际上,它确实适用于源代码!只需 source(other.name) 即可正常工作。
0赞 Suppressingfire 12/1/2009
我想也许我们在谈论交叉目的。我认为我们对 dehmann 感兴趣的工作有不同的理解。
3赞 Jason 9/9/2014
对于路径串联,最好使用other.name <- file.path(script.basename, "other.R")
2赞 Paul 1/19/2017
当我尝试在服务器内运行时。R 在一个闪亮的应用程序中,我得到了.没有关于从中调用它的目录的信息。commandArgs(trailingOnly = FALSE)[1] "RStudio" "--interactive"
38赞 hadley 11/30/2009 #3
frame_files <- lapply(sys.frames(), function(x) x$ofile)
frame_files <- Filter(Negate(is.null), frame_files)
PATH <- dirname(frame_files[[length(frame_files)]])

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

评论

3赞 Suppressingfire 11/30/2009
这在什么情况下起作用?print(sys.frames()) 在我运行时显示 NULL。
1赞 Richie Cotton 11/30/2009
@Suppressingfire:返回调用堆栈的环境,因此只有在从函数调用时才有意义。尝试,例如,.我无法弄清楚@hadley的代码,因为环境没有成员。sys.framesfoo <- function() {bar <- function() print(sys.frames()); bar()}; foo()ofile
1赞 hadley 12/1/2009
您必须在 - 即如果我保存该代码,则运行 ,将设置为 。如果只在顶层计算它,它将返回 NULL。source("~/code/test.r")PATH~/desktop
4赞 Frank 12/5/2009
这不能回答我的问题。我需要自动找到“其他。R“文件。 未定义,因此为空。x$ofileframe_files
0赞 Sim 6/18/2012
@hadley,非常有用的代码。当几乎所有脚本处于积极开发状态时,我能够将“重新加载当前脚本”实用程序功能推广到几乎所有脚本中。RScript 重载程序
13赞 momeara 6/24/2011 #4

Supressingfire答案的精简版:

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

评论

1赞 The Unfun Cat 5/5/2015
这在递归上不起作用;我源的文件查找数据文件(但在错误的目录中)。
11赞 user382132 9/28/2011 #5

这对我有用。只需将其从命令行参数中取出,剥离不需要的文本,执行 dirname,最后从中获取完整路径:

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

评论

1赞 user5359531 9/1/2021
这是正确答案。真是莫名其妙,有多少人在浪费时间在其他提出的答案上。
0赞 Ryan Howard 12/9/2022
这只是在交互式 R 会话中为我返回了空白值。
61赞 steamer25 3/13/2013 #6

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

评论

7赞 wch 11/13/2014
我喜欢这个,因为它适用于 R 和 R 中。我建议在两个版本上都这样做,以便在这两种情况下都提供完整的路径。Rscriptsource()normalizePath()
1赞 O.rka 10/5/2016
这是唯一有效的方法。请注意,为此我花了一段时间才弄清楚,哈哈library(base)
2赞 Vince W. 3/19/2018
先生,您得到了我的投票,因为这是对我有用的解决方案
2赞 Kim 5/13/2019
如果这对任何人有帮助,对于原始帖子,那将意味着 .这对我有用。source(file.path(dirname(thisFile()), "other.R"))foo.R
0赞 Wassadamo 5/30/2019
一个问题。假设在 RStudio 中,我源哪个源调用 .它将获取 的路径,而不是 。这里有任何提示吗?main.Rhelper.RthisFile()main.Rhelper.R
117赞 this.is.not.a.nick 4/17/2013 #7

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

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

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

评论

9赞 Ehsan88 9/25/2014
它对我不起作用。我在 Windows 中运行 R。有什么想法吗?
5赞 RalfB 5/20/2015
遇到同样的错误,保存了脚本,并在 Windows 上全新安装并运行 R 3.2.0......
35赞 Murta 7/15/2015
当您尝试直接从 Rstudio 执行时,会发生此错误。当使用 source(“other.R“),并在里面。dirname(sys.frame(1)$ofile)dirname(sys.frame(1)$ofile)"other.R"
13赞 Mark Adamson 2/26/2016
使用 rscript.exe 作为脚本调用时,我收到了“堆栈上没有那么多帧”错误,即不使用 source()。所以我不得不改用下面 Suppressingfire 的解决方案
8赞 Paul 1/19/2017
当它被放置在服务器中时,我凝胶。R 使用闪亮时NULL
10赞 krlmlr 11/29/2013 #8

我已经将这个问题的答案总结并扩展到 rprojroot 中的一个新函数中。也适用于针织。thisfile()knitr

0赞 HenrikB 5/19/2014 #9

请参阅 R.utils 包,其中findSourceTraceback()

查找所有调用帧中由 source() 生成的所有 'srcfile' 对象。 这样就可以找出当前由 source() 编写脚本的文件。

0赞 kinjelom 11/23/2014 #10
#!/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='/'))

评论

1赞 Michael Barton 11/1/2017
我仍然收到错误“Error in sys.frame(1) : not that many frames on the stack ”
0赞 Iris 12/12/2020
这仅在使用 或 时有效,并且它始终抓取堆栈上的第一个,而不是最新的。sourcesys.sourcesource
2赞 kuna.matata 12/17/2014 #11

我喜欢这种方法:

this.file <- sys.frame(tail(grep('source',sys.calls()),n=1))$ofile
this.dir <- dirname(this.file)
1赞 Luke Singham 5/27/2015 #12

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

Bash.sh

#!/bin/bash

# set path variable
path=`pwd -P`

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

呸呸。R

args <- commandArgs(trailingOnly=TRUE)
setwd(args[1])
source(other.R)
7赞 aprstar 8/15/2015 #13

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

评论

1赞 Mark Adamson 7/7/2016
Rstudio 中的 source 为我提供了 ofile,但 debugSource 提供了 fileName,因此您的解决方案运行良好,但代码注释在我的情况下不太正确
1赞 Ailton Andrade de Oliveira 9/2/2015 #14

我会使用@steamer25方法的变体。关键是,即使我的会话是通过 Rscript 启动的,我也更喜欢获取最后一个源脚本。以下代码片段包含在文件中时,将提供一个包含脚本规范化路径的变量。 我承认(滥用)使用源,所以有时我调用 Rscript,参数中提供的脚本 source 另一个脚本,该脚本源另一个脚本......总有一天,我会投资把我凌乱的代码变成一个包。thisScript--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))
  }
})()
29赞 cuffel 3/29/2016 #15

获取 R 脚本路径rakensi 的答案是最正确和非常出色的恕我直言。然而,它仍然是一个包含虚拟功能的黑客。我在这里引用它,为了让其他人更容易找到它。

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

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

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

或创建绝对路径

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

评论

2赞 jcarlos 4/18/2016
对我来说,你的解决方案是最好的。特别是因为它可以应用于 Shiny 应用程序,而链接上的应用程序则不然。
1赞 RubenLaguna 10/25/2016
这里的 getSrcDirectory 是 utils::getSrcDirectory
10赞 Contango 8/8/2017
这可能在 Linux/Mac 下工作得很好,但在 Windows 下的交互式 RStudio 会话中对我不起作用。 是空白的。sourceDir
2赞 pommedeterresautee 1/4/2018
@Contango在交互式终端上,没有路径!!您需要文件的路径。
3赞 abalter 8/2/2019
我得到了.建议?character(0)
19赞 Jerry T 4/22/2016 #16

我的多合一!(--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)
                }
            }
        }
    }
}

评论

0赞 ManicMailman 7/7/2017
不适用于交互式 R 会话;我得到: ''' > source(“csf.R“) > csf() 错误:RStudio 未运行 '''
0赞 Joe Flack 6/13/2020
这太棒了。有人可以做一个包裹吗?
0赞 Patrick 6/23/2020
这在 Rstudio 中以交互方式运行时有效,只要您不更改焦点文档即可。如果提交要运行的行,然后在运行时切换到另一个文档,则将返回另一个文档的路径。
3赞 Andrew Moffat Jr. 8/5/2016 #17

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

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

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

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

wd <- getwd()
setwd(wd)

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

setwd(".")

评论

13赞 user1071847 12/10/2016
不。这将查找进程的目录,而不是文件本身。
0赞 Contango 8/8/2017
这在 Windows 中使用 RStudio 在交互模式下对我有用。
0赞 Ryan Howard 12/9/2022
谢谢!。最后,在 Linux 中的交互式 RStudio 会话中工作的东西。
0赞 Konrad Rudolph 7/23/2023
setwd(".")和(在你的情况下)都是荒谬的操作。它们没有任何影响。setwd(wd)
1赞 antonio 11/25/2016 #18

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

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

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

评论

2赞 nJGL 12/24/2017
但是,不是来自 RStudio,除非在采购时
2赞 Ryan C. Thompson 3/16/2017 #19

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

评论

0赞 bokov 2/21/2018
它总是返回 NA,即使我创建一个打印其输出的脚本,然后调用该脚本,例如R -e "library(getopt); testscript.R"
1赞 Ryan C. Thompson 2/21/2018
顾名思义,您需要使用 .Rscript
0赞 bokov 2/23/2018
啊,哎呀。谢谢。
2赞 iball 7/29/2017 #20

Steamer25 的方法有效,但前提是路径中没有空格。至少在 macOS 上,返回类似 for 的内容。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 块。

35赞 ColinTea 8/23/2017 #21

这对我有用

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

评论

7赞 Ista 10/8/2018
我猜这只能在 RStudio 内部工作。从终端尝试我得到.Error: RStudio not running
1赞 Kay 5/19/2020
更具体地说,如果从 R Studio 中的 R 脚本运行,它就可以工作。即使在 RStudio 的控制台上,在我的情况下,它也不会给出正确的结果""
0赞 Patrick 6/23/2020
这在 Rstudio 中以交互方式运行时有效,只要您不更改焦点文档即可。如果提交要运行的行,然后在运行时切换到另一个文档,则将返回另一个文档的路径。
2赞 mmell 1/6/2018 #22

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

鉴于

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

这将起作用

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

有关如何定义项目根目录,请参见 https://rprojroot.r-lib.org/

评论

0赞 Ron 12/10/2019
对我来说,这里的包完全可以完成这项工作,似乎是一个简单的解决方案
0赞 bruce.moran 3/13/2018 #23

令人惊讶的是,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 名称拆分为其他名称。R

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

评论

0赞 altabq 5/7/2020
我收到一条错误消息readLink: illegal option -- e usage: readLink [-FlLnqrsx] [-f format] [-t timefmt] [file ...]
8赞 Bojan P. 11/13/2018 #24

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

scriptName

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

评论

2赞 Bojan P. 5/27/2019
Package ‘scriptName’ was removed from the CRAN repository.-现在怎么办?:o
1赞 user425678 8/7/2019 #25

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

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) ))()
4赞 Antoine 12/13/2019 #26

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

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

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

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

我希望这会有所帮助。

评论

2赞 Boops Boops 1/8/2020
这在 Linux 机器上对我不起作用;它没有返回文件的路径,而是返回我当前所在的目录。我创建了一个名为 TEST 的测试脚本。R 与一行代码:print(fileSnapshot()$path) 我将其保存在这个文件夹中:/opt/home/boops/Desktop/Testfolder/TEST。R 然后我导航到我的桌面并尝试运行文件:boops@linuxserver:~/Desktop$ Rscript /opt/home/boops/Desktop/Testfolder/TEST。R [1] “/opt/home/boops/桌面”
1赞 Joe Flack 6/13/2020
对我也没有用。使用“here”库时返回与“here()”相同的内容。它返回了我当前打开的 R 项目的路径,但不是他正在执行的文件本身。
0赞 irritable_phd_syndrome 8/26/2020 #27

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

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

其他。R

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

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

如果不是从交互式 shell 运行,也不是从 运行,请先设置环境变量,然后调用/path/to/R/codeR_SRCRscript

$ export R_SRC=/path/to/R/code/
$ Rscript /path/to/R/code/foo.R
24赞 Iris 9/30/2020 #28

我为此制作了一个包,可在 CRAN 和 GitHub 上找到,称为 this.path。当前版本为 2.3.1 (2023-12-10),您可以在此处找到它:

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

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

this.path::this.path()

艺术

library(this.path)
this.path()

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

  • 兼容更多 GUI:Windows 上的“Rgui”、“VSCode”、“Jupyter”和“Emacs”+“ESS”'
  • 软件包 compilerboxknitrplumbershinytargetstestthat 的兼容性,特别是 、 、 、 和compiler::loadcmp()box::use()knitr::knit()plumber::plumb()shiny::runApp()testthat::source_file()
  • 在 Unix-alikes 下从 shell 运行 R 脚本时处理带空格的文件名
  • 处理从 shell 运行 R 脚本的两种用法(和-fFILE--file=FILE)
  • 与参数一起使用时正确规范化路径source()(chdir = TRUE)
  • 处理带有 such as 和 的文件 URIsource()source("file:///path/to/file")source("file:///C:/path/to/file")
  • 改进了对连接而不是字符串的处理source()
  • 中 URL 路径名的处理,即:source()
source("https://host/path/to/file")

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

source("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()this.path()

原答案:

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

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)) {
        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, "/", 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, "/", 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, "/", 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")
}

评论

1赞 johnny 12/2/2020
在 RGui 上运行此命令时,我收到以下消息。关于如何绕过它的任何想法?this.path::this.path() 中的错误:以不适当的方式使用“this.path” * 在调用堆栈中未找到适当的“source”或“sys.source”调用 * R 正在从 RGui 运行,这需要对调用堆栈进行“source”和“sys.source”调用
1赞 Iris 12/2/2020
直到您发表评论说您可以从“RGui”的脚本中运行代码,我才意识到,以前我认为在“RGui”的脚本中运行代码的唯一方法是使用“source”。我正在研究这个问题的解决方法,希望我能尽快找到一些东西。现在,您可以使用“RStudio”来编辑和运行脚本,因为我知道它从那里开始工作。很抱歉没有答案,但感谢您指出这个错误!
1赞 Iris 12/3/2020
@johnny我相信我找到了一个解决方案,但它仅适用于 Windows 操作系统。我正在尝试为名为“AQUA”的 macOS 版本的“RGui”找到解决方案,然后我将更新到包上传到 CRAN。在 CRAN 维护者之一批准发布更新之前,大约需要 ~10 个工作日,希望“RStudio”在此期间为您工作!
1赞 Iris 12/3/2020
@johnny更新是在几个小时前发布的,比我预期的要快得多。我现在已经在两台不同的计算机上进行了测试,它似乎在“RGui”中按预期工作!
2赞 johnny 12/3/2020
刚刚在 RGui 会话中保存的脚本文件上测试了 v.0.2.0,它对我有用。谢谢!
0赞 cmo 6/9/2021 #29

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

CRANgithub 上的包提供了获取当前脚本完整路径的函数。它甚至引用了类似的 SO 帖子funrsys.script()

因此,解决方案是:

myscript。R:

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

然后执行命令:

user@somewhere:/home$ Rscript myscript.R

在命令行将输出,例如:

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

到控制台。

评论

0赞 Konrad Rudolph 5/31/2023
这只能处理两种情况(通过命令行运行或从命令行运行脚本),即使这样也并不总是有效(可以用 R 而不是 调用 )。你不应该依赖它。source()-f--file=
0赞 Foztarz 12/1/2021 #30

只是为了在上述答案的基础上,作为安全检查,您可以添加一个包装器,要求用户在(无论出于何种原因)失败(如果)失败时找到文件,或者源脚本不在主脚本预期的位置。sys.frame(1)interactive() == TRUE

fun_path = tryCatch(expr = 
                      {file.path(dirname(sys.frame(1)$ofile), "foo.R")},
                    error = function(e){'foo.R'}
                    )
if(!file.exists(fun_path))
{
  msg = 'Please select "foo.R"'
  # ask user to find data
  if(Sys.info()[['sysname']] == 'Windows'){#choose.files is only available on Windows
    message('\n\n',msg,'\n\n')
    Sys.sleep(0.5)#goes too fast for the user to see the message on some computers
    fun_path  = choose.files(
      default = file.path(gsub('\\\\', '/', Sys.getenv('USERPROFILE')),#user
                          'Documents'),
      caption = msg
    )
  }else{
    message('\n\n',msg,'\n\n')
    Sys.sleep(0.5)#goes too fast for the user to see the message on some computers
    fun_path = file.choose(new=F)
  }
}
#source the function
source(file = fun_path, 
       encoding = 'UTF-8')

评论

0赞 Konrad Rudolph 7/23/2023
对于具有更好的现有解决方案(特别是)的东西来说,这是一种不切实际的方式。box::use()
1赞 Lewkrr 3/16/2023 #31

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

  • 以交互方式为两者工作。Rmd 和 .R 脚本
  • 在编织时有效。Rmd 文件
  • 在采购 .R 文件

请尝试以下操作:

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

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

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

我希望这会有所帮助!

评论

0赞 Konrad Rudolph 7/23/2023
这个“解决方案”已经发布,之前的答案有几条评论解释了为什么它不是一个好的解决方案。
0赞 Lewkrr 8/22/2023
您能否附上指向先前发布的解决方案的链接?我想看看解释为什么这种方法不好的评论(为了我自己的启发)。
0赞 Konrad Rudolph 8/22/2023
请参阅 stackoverflow.com/a/36777602/1968stackoverflow.com/a/45844073/1968
0赞 Giacomo 8/30/2023
到目前为止,这是唯一适合我的解决方案。@KonradRudolph为什么它不是一个好的解决方案?
0赞 Konrad Rudolph 8/30/2023
@Giacomo因为它不起作用。请参阅我之前评论中的链接。