Reputation: 66164
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?
Upvotes: 305
Views: 158474
Reputation: 501
I've made a package for this, available on CRAN and GitHub, called this.path. The current version is 2.6.0 (2024-12-18), 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"
)
## or:
remotes::install_github("ArcadeAntics/this.path")
and then use it by:
this.path::this.path()
or:
library(this.path)
this.path()
This improves upon the other answers in the following ways:
compatibility with the following GUIs:
'Rgui' on Windows
'RStudio' (including background jobs)
'Positron'
'VSCode' + 'REditorSupport'
'Jupyter'
compatibility with the following functions and packages:
source()
sys.source()
debugSource()
in 'RStudio'
compiler::loadcmp()
utils::Sweave()
handling filenames with spaces, tabs, and newlines when running an R script from a shell under Unix-alikes
handling both uses of running an R script from a shell (-f FILE, --file=FILE
)
correctly normalizes the path when using source()
with argument (chdir = TRUE)
handling of file://
URLs with source()
such as source("file:///path/to/file")
and source("file:///C:/path/to/file")
handling of URL pathnames in source()
, such as:
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 http://
, ftp://
, and ftps://
URLs. As an example, try:
source("https://raw.githubusercontent.com/ArcadeAntics/this.path/main/tests/this.path_w_URLs.R")
handling of a connection instead of a character string within source()
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()
(as long as setwd()
is used AFTER the first call to this.path()
within that script)
Upvotes: 31
Reputation: 420
The most flexible solution to this that I have found utilizes rstudioapi::getSourceEditorContext()
and (optionally) sub()
Try the following:
current_file <-
rstudioapi::getSourceEditorContext()$path %>%
sub(".*/", "", .)
The rstudioapi::getSourceEditorContext()$path
returns the full path of the current file
The sub(".*/", "", .)
extracts everything after the last /
, leaving only the name of the file.
I hope that helps!
Upvotes: 1
Reputation: 91
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')
Upvotes: 0
Reputation: 4104
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.
Upvotes: 0
Reputation: 5067
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
Upvotes: 0
Reputation: 269
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.
Upvotes: 4
Reputation: 760
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) ))()
Upvotes: 1
Reputation: 2498
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:
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.
Upvotes: 2
Reputation: 1690
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)
}
}
}
}
}
Upvotes: 19
Reputation: 960
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.
Upvotes: 8
Reputation: 3286
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.
Upvotes: 85
Reputation: 25444
I have wrapped up and extended the answers to this question into a new function thisfile()
in rprojroot. Also works for knitting with knitr
.
Upvotes: 10
Reputation: 347
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")
Upvotes: 0
Reputation: 1058
This works for me
library(rstudioapi)
rstudioapi::getActiveDocumentContext()$path
Upvotes: 35
Reputation: 31
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.
Upvotes: 2
Reputation: 42010
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.
Upvotes: 2
Reputation: 11110
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.
Upvotes: 1
Reputation: 125
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(".")
Upvotes: 3
Reputation: 529
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=""))
Upvotes: 30
Reputation: 111
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))
}
})()
Upvotes: 1
Reputation: 101
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))
}
}
}
Upvotes: 7
Reputation: 81
I like this approach:
this.file <- sys.frame(tail(grep('source',sys.calls()),n=1))$ofile
this.dir <- dirname(this.file)
Upvotes: 2
Reputation: 1726
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)
Upvotes: 1
Reputation: 6450
#!/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='/'))
Upvotes: 0
Reputation: 9553
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))
}
}
Upvotes: 63
Reputation: 6805
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().
Upvotes: 0
Reputation:
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)])))
Upvotes: 11
Reputation: 2701
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.
Upvotes: 118
Reputation: 1381
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="/"))
}
Upvotes: 13
Reputation: 103898
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 :/
Upvotes: 38