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?
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.
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.
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))
}
}
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 :/
This works for me
library(rstudioapi)
rstudioapi::getActiveDocumentContext()$path
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=""))
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)
}
}
}
}
}
I've made a package for this, available on CRAN and GitHub, called "this.path". The current version is 1.2.0 published on 2023-01-16, 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")
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:
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")
if this.path was used within the file, it would return "https://host/path/to/file". This also works for a URL beginning with "http://", "ftp://", and "ftps://". As an example, try:
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)
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, 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")
}
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="/"))
}
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)])))
I have wrapped up and extended the answers to this question into a new function thisfile() in rprojroot. Also works for knitting with knitr.
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.
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))
}
}
}
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(".")
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.
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
I like this approach:
this.file <- sys.frame(tail(grep('source',sys.calls()),n=1))$ofile
this.dir <- dirname(this.file)
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.
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.
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)
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))
}
})()
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.
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.
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) ))()
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.
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().
#!/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='/'))
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")
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
Just to build on the above answers, as a safety check, you could add a wrapper that asks the user to find the file if (for whatever reason) sys.frame(1) fails (as it might if interactive() == TRUE), or the sourced script is not where the main script expects it to be.
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')