package:this.path
provides a mechanism for an
R script to retrieve its own path without the explicit
need to write it elsewhere.
Install it from CRAN:
install.packages("this.path")
Install the development version from GitHub:
install.packages(
"this.path",
repos = "https://raw.githubusercontent.com/ArcadeAntics/PACKAGES"
)
## or:
::install_github("ArcadeAntics/this.path") remotes
The most important functions from package:this.path
are
this.path()
, this.dir()
, here()
,
and this.proj()
:
this.path()
returns the normalized path of the
script in which it is written.
this.dir()
returns the directory of
this.path()
.
here()
constructs file paths against
this.dir()
.
this.proj()
constructs file paths against the
project root of this.dir()
.
New additions include:
LINENO()
returns the line number of the executing
expression.
shFILE()
looks through the command line arguments,
extracting FILE from either of the following:
-f FILE
or --file=FILE
set.sys.path()
implements this.path()
for any source()
-like functions outside of the
builtins.
with_init.file()
allows this.path()
to
be used in a user profile.
package:this.path
also provides functions for
constructing and manipulating file paths:
path.join()
, basename2()
, and
dirname2()
are drop in replacements for
file.path()
, basename()
, and
dirname()
which better handle drives and network
shares.
splitext()
, removeext()
,
ext()
, and ext<-()
split a path into root
and extension, remove a file extension, get an extension, or set an
extension for a file path.
path.split()
, path.split.1()
, and
path.unsplit()
split the path to a file into
components.
relpath()
, rel2here()
, and
rel2proj()
turn absolute paths into relative
paths.
This package started from a stack overflow posting:
https://stackoverflow.com/questions/1815606/determine-path-of-the-executing-script/64129649#64129649
If you like this package, please consider upvoting my answer so that
more people will see it! If you have an issue with this package, please
use bug.report(package = "this.path")
to report your
issue.
If you are unhappy with the performance of
package:this.path
, or would like to know other solutions,
here are some alternatives:
The working directory could always be changed to the directory of the executing script before running it. This would be:
cd /path/to
Rscript ./file.R
or:
source("/path/to/file.R", chdir = TRUE)
This fails for executable R scripts and it ignores the simple fact that it is sometimes legitimately desirable to have the working directory set elsewhere.
utils::getSrcFilename()
provides the ability to retrieve
the filename of a source reference. Everywhere this.path()
would be used, replace it with
utils::getSrcFilename(function() NULL, full.names = TRUE)
.
This comes with some issues such as:
fails in interactive use since scripts must be run with
source()
fails when running R scripts from a shell
option keep.source
must be set to
TRUE
it returns the non-normalized source file’s
filename
Instead of changing the working directory to the directory of the executing script and referring to files with relative file paths, refer to files with absolute file paths. This would be:
"C:/path/to/file"
or
"//host/share/path/to/file"
on Windows
"/path/to/file"
under Unix-alikes
This has some undesirable consequences:
If the files are moved to a new location, or any of the file path components are renamed, the file paths need to be updated for every use for every script.
If the files are hosted on a network share, users with differing network drive mappings or even differing operating systems may not have a common absolute file path to refer to the same location.
There are a few other packages and functions that provide the ability to retrieve the path of the current R script:
package:envDocument
specifically envDocument::getScriptPath()
package:funr
specifically funr::get_script_path()
and
funr::sys.script()
package:gtools
specifically gtools::script_file()
package:scriptName
specifically scriptName::current_filename()
package:whereami
specifically whereami::thisfile()
These are lacking in functionality compared to
package:this.path
:
this.path()
is compatible with the following
GUIs:
‘Rgui’
‘RStudio’ (including background jobs)
‘Positron’
‘VSCode’ + ‘REditorSupport’
‘Jupyter’
this.path()
is compatible with the following
functions and packages:
sys.source()
debugSource()
in ‘RStudio’
compiler::loadcmp()
utils::Sweave()
Related to source()
, this.path()
:
takes argument chdir
into account.
recognizes that file = ""
,
file = "clipboard"
, and file = "stdin"
are not
referring to files and skips them.
accounts for file
being a URL pathname.
accounts for file
being a connection instead of a
character string.
skips calls in which file
is missing and
exprs
is used instead.
this.path()
does a better job of extracting
FILE from the command line arguments -f FILE
and
--file=FILE
.
this.path()
saves all normalized paths within their
appropriate environments, making it much faster subsequent times within
the same script and independent of working directory.
If this.path()
does not find an executing script, it
throws an error. This is better than returning NULL
; if the
executing script cannot be found, obviously there is a problem that
needs to be addressed, so the script should not continue.
These packages also have some objectively incorrect coding issues such as:
searching only the first call on the stack
failing to check the command line arguments
imposing unnecessary restrictions on pathnames
searching the call stack in the wrong order
returning the directory instead of the path of the executing script
searching for a path in the wrong order
searching for a source call by frame variables instead of function value
loading unnecessary additional packages without explicit user permission
searching for a source call by name instead of value
returning a non-normalized path
changing global options without explicit user permission
package:here
provides function here::here()
with the ability to retrieve
the project root, but it lacks functionality:
here::here()
returns the initial working directory
when it cannot find the project’s directory. If the project’s directory
cannot be found, there is something wrong and an error should be thrown,
but it doesn’t, and this leads to incorrect / / unexpected
behaviour.
here::here()
does not work when the initial working
directory is set outside the context of the project. Occasionally, I
would set the working directory to a temporary directory where my
R script would create a log file of the details of the
script. This leads to package:here
not being able to find
the project root and incorrectly returning the initial working
directory.
here::here()
does not work when multiple projects
are in use nor for projects containing sub-projects. In my scenario, I
had a project A
and a project B
. I would run a
script in B
which runs a script in A
, but the
project root is already set to the root of B
, so the script
in A
fails.
package:rprojroot
is the R package upon which package:here
is built. It provides a list of project root criteria
rprojroot::criteria
. It is not useful on its own since,
like here::here()
, it does not work when the working
directory is set outside the context of the project. However, you can
combine it with package:this.path
to get the best
results:
## replace 'rprojroot::is_r_package' with desired criterion
<- this.path::make_fix_file(rprojroot::is_r_package) fix_file
If the default criteria are not sufficient for your needs, you can make your own using:
rprojroot::has_basename()
rprojroot::has_dir()
rprojroot::has_file()
rprojroot::has_file_pattern()
rprojroot::root_criterion()
package:rprojroot
recommends using
<criterion>$find_file(path = whereami::thisfile())
for this purpose, but as mentioned in section Alternative
4, whereami::thisfile()
is seriously lacking
compared to this.path::this.path()
.
package:box
package:box
provides two related functions:
box::file()
constructs file paths against the
directory of the executing script.
box::use()
imports an R script as a
module.
These both lack the same functionality as the packages listed in
Alternative 4. box::file()
should not be
used in favour of this.path::here()
. However,
box::use()
is still extremely useful, it just needs to be
combined with package:this.path
to get the best
results:
::with_script_path(
this.path::use(
box<import 1>,
<import 2>,
<...>
) )
This explicitly tells package:box
the path of the
current script so that relative imports will work correctly.
If you think I have overlooked some of the alternatives, or think
there are any improvements I could make to
package:this.path
, please let me know, I am open to
suggestions! I hope this package serves you well!