Determine path of the executing script

这一生的挚爱 提交于 2019-11-26 00:10:35

问题


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?


回答1:


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.




回答2:


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.




回答3:


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



回答4:


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 :/




回答5:


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



回答6:


This works for me

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



回答7:


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



回答8:


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



回答9:


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



回答10:


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




回答11:


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



回答12:


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.




回答13:


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



回答14:


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



回答15:


I like this approach:

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



回答16:


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.




回答17:


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




回答18:


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)



回答19:


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



回答20:


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.




回答21:


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



回答22:


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.




回答23:


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



回答24:


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:

  • https://github.com/r-lib/rprojroot or https://rprojroot.r-lib.org/
  • https://here.r-lib.org/

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.




回答25:


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


来源:https://stackoverflow.com/questions/1815606/determine-path-of-the-executing-script

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!