I have a script called foo.R
that includes another script other.R
, which is in the same directory:
#!/usr/bin/env Rscript
message(\
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(".")
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)])))
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've made a package for this as of 2020-11-11 available on CRAN called "this.path".
Install it using:
install.packages("this.path")
and then use it by:
this.path::this.path()
or
library(this.path)
this.path()
I still have my original answer below, though it is less functional than the version in the package. The version in the package accounts for 'sys.source' (a variant of 'source' not commonly used), for the command-line 'Rscript' argument 'file' character conversion on Unix-alike OS, and does not rely on package "rstudioapi". On a Unix-alike OS, strange characters (such as " ") in the command-line 'Rscript' argument 'file' are replaced (such as "~+~"). This makes it so that it is not necessary to use quotation marks to surround that filename for other system commands. Given that we want a path usable in R, any such strange sequences must be replaced.
Original Answer:
My answer is an improvement upon Jerry T's answer. The issue I found is that he is 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, a source call must be looked for before checking the command-line arguments, that has also been fixed. Here is my solution:
this.path <- function (verbose = getOption("verbose"))
{
where <- function(x) if (verbose) cat(x, "\n", sep = "")
# loop through functions that lead here from most recent to
# earliest (excluding this.path) looking for source
for (n in seq.int(sys.nframe(), 1L)[-1L]) {
if (identical(sys.function(n), base::source) &&
exists("ofile", envir = sys.frame(n), inherits = FALSE)) {
where("Source: call to function source")
path <- get("ofile", envir = sys.frame(n), inherits = FALSE)
if (!is.character(path))
path <- summary.connection(path)[["description"]]
return(normalizePath(path, mustWork = TRUE))
}
}
# if the for loop is passed, no appropriate source call was found
# next, check if the user is running a file from the command-line
# get command-line arguments that start with --file=, remove --file= from the start
if (length(path <- sub("^--file=", "", grep("^--file=", commandArgs(), value = TRUE))))
where("Source: Command-line argument")
else if (!requireNamespace("rstudioapi", quietly = TRUE)) {
where("Unavailable: Package 'rstudioapi' is not installed")
return("")
}
else if (!rstudioapi::isAvailable()) {
where("Unavailable: RStudio not running")
return("")
}
else if (nzchar(path <- rstudioapi::getActiveDocumentContext()$path))
where("Source: RStudio Run Selection")
else if (!is.null(path <- rstudioapi::getSourceEditorContext()$path))
where("Source: RStudio Console")
else {
where("Unavailable: No open R Script")
return("")
}
normalizePath(path, mustWork = TRUE)
}
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.
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)
}
}
}
}
}