确定正在执行的脚本的路径

300

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

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

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

换句话说,foo.R需要知道它自己的路径。我该如何做到这一点?


2
没有 :( 我还没有看到任何真正有效的解决方案。除了通过传递目录或使用环境变量的变通方法。 - Frank
4
让脚本完全可移植且甚至 R 初学者也能执行,这将是非常惊人的! - Etienne Low-Décarie
5
似乎所有答案都需要你在某个时刻输入路径(至少要定位文件)!如果你能发送一个压缩文件夹,让任何运行该文件夹内的R脚本文件都从中读取并保存到其中,那将是很好的。 - Etienne Low-Décarie
17
这个单一问题实际上可能成为我完全转向Python的原因。 - Giacomo
9
@giac_man,我觉得R充满了成百上千个这样微小的问题,它们相互累加,使得工作变得非常困难。 - Michael Barton
显示剩余5条评论
31个回答

1

由于我的脚本是从符号链接目录操作的,因此我对以上实现存在问题,或者至少我认为以上解决方案对我没有起作用。参考 @ennuikiller 的回答,我将我的 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)

1
99%的情况下,您可能只需使用以下内容:
sys.calls()[[1]] [[2]]

在不是脚本作为第一个参数的疯狂调用中,例如source(some args, file="myscript"),它将无法工作。在这些花哨的情况下,请使用@hadley的方法。

2
不过,除了在源代码时,不要从RStudio内部进行操作。 - nJGL

1

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

  • 可以交互地工作,适用于 .Rmd 和 .R 脚本
  • 在编织 .Rmd 文件时工作
  • 在源代码 .R 文件中工作

请尝试以下操作:

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

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

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

希望这有所帮助!


这个“解决方案”已经发布过了,之前的回答有几条评论解释了为什么它不是一个好的解决方案。 - Konrad Rudolph
请您能否提供之前发布的解决方案的链接?我想看一下评论,了解为什么这种方法不可行(以便自己学习)。 - Lewkrr
请参考以下链接:https://dev59.com/k3I-5IYBdhLWcg3whIqk#36777602 和 https://dev59.com/k3I-5IYBdhLWcg3whIqk#45844073。 - Konrad Rudolph
@Giacomo 因为它不起作用。请查看我之前评论中的链接。 - Konrad Rudolph
好的,我明白你的意思,这并不是这个问题的正确答案。然而,它确实适用于另一种用例(从R Studio运行R脚本)。 - Giacomo
显示剩余2条评论

1

我会使用@steamer25的方法的变体。重要的是,即使我的会话是通过Rscript启动的,我仍然希望获取最后一个已源代码化的脚本。当包含以下代码片段的文件被调用时,将提供一个名为thisScript的变量,其中包含了脚本的标准化路径。我承认滥用source函数,所以有时我会调用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))
  }
})()

1

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

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

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='/'))

1
我仍然收到错误信息:“Error in sys.frame(1) : not that many frames on the stack”。 - Michael Barton
只有在使用 sourcesys.source 时才有效,它总是获取堆栈上的第一个 source,而不是最近的。 - Iris

0

这个解决方案是在2016年提出的,非常感谢作者Sahil Seth!

软件包funrCRANgithub上提供了函数sys.script(),用于获取当前脚本的完整路径。它甚至引用了一个类似的SO post

因此,解决方案如下:

myscript.R:

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

然后执行命令:

user@somewhere:/home$ Rscript myscript.R

在命令行上运行,将输出,例如:

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

到控制台。


这仅处理两种情况(通过source()运行脚本或从命令行运行),即使如此,它也不总是有效(R可以使用-f而不是--file =来调用)。您不应该依赖它。 - Konrad Rudolph

0

在上面的答案基础上,为了进行安全检查,您可以添加一个包装器,要求用户查找文件,如果(由于某种原因)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')

这种方法对于已经有更好的解决方案(特别是box::use())来说是不切实际的。 - Konrad Rudolph

0
请参考R.utils包中的findSourceTraceback()函数,该函数可以在所有调用帧中查找由source()生成的所有'srcfile'对象。这使得我们能够找出当前由source()脚本化的文件。

0

真是令人惊讶,在R中没有'$0'类型的结构!您可以通过调用一个在R中编写的bash脚本来实现:

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

我收到一个错误信息 readLink: illegal option -- e usage: readLink [-FlLnqrsx] [-f format] [-t timefmt] [file ...] - altabq

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