7

In most scripting languages (e.g. Ruby, Python, etc.) the package manager (e.g. gem, pip, etc.) can install scripts as executables and link them to a directory referenced in the PATH variable (e.g. /usr/local/bin). This turns those executable scripts into shell commands that the user can run in an standalone manner and outside the programming interface.

I wonder if there is such a possibility in R as well. Given that R uses standard Makefiles, I guess there must be a way to do so, albeit a non-standard one. I already know we can read command line arguments in a R script using the docopt package. But is there a way to install the script as an executable upon the installation of a package?

It would be great to have a lead on this topic, but a single working example from CRAN would suffice as well.

1
  • Perhaps you are looking for the system.file() function? It can locate a file embedded within an installed R package Commented Mar 23, 2023 at 21:29

2 Answers 2

5

Short (and very sad) answer: You cannot. But read on.

Reasoning: R will only ever write package content to its own .libPaths() directory (or the first in case several are given), or a directory given by the user.

So, say, /usr/local/bin/ is simply out of reach. That is a defensible strategy.

It is also rather sad--I wrote littler (also CRAN page) for exactly this purpose: executable R scripts. And we have dozens of those at work called from cron jobs. So what do we do? A one-time soft-link from the scripts/ subdirectory of the package containing the script to /usr/local/bin. At package upgrades, the link persists as a soft-link.

And that's what I do for e.g. all the examples shipping with littler and more from other packages. Many of them use docopt too.

Sign up to request clarification or add additional context in comments.

2 Comments

But is there a standard bin path under R's .libPaths() that we can add to the path once for all, and then link all our executables there? That's how pip and gem function.
Just expand .libPaths(): it is one level above all the package directories.
5

I had a similar goal, this was the solution I settled on. I added a function to my R package that goes through and (verbosely) creates symlinks from ~/.local/bin to each script in my packages exec folder.

Other sensible default locations might be ~/.local/lib/R/bin or ~/bin, but I like ~/.local/bin the best.

So after installing the package, I direct users to run

Rscript -e 'mypackage::install_executable_scripts()'
#' @export
install_executable_scripts <- function(into = "~/.local/bin", overwrite = FALSE) {
  scripts <- dir(system.file("exec", package = "mypackage"),
                 full.names = TRUE)
  if (!dir.exists(into)) dir.create(into)
  into <- normalizePath(into)

  dests <- file.path(normalizePath(into), basename(scripts))
  if (any(already_exist <- file.exists(dests))) {
    if (overwrite) {
      to_del <- dests[already_exist]
      cli::cat_bullet("Deleting existing file: ", to_del,
                      bullet_col = "red")
      unlink(to_del)
    } else {
      cli::cat_bullet(sprintf(
        "Skipping script '%s' because a file by that name already exists at the destination",
        basename(scripts[already_exist])))
      scripts <- scripts[!already_exist]
      dests   <-   dests[!already_exist]
    }
  }
  if (length(scripts)) {
    file.symlink(scripts, dests)
    cli::cat_line("Created symlinks:")
    cli::cat_bullet(dests, " ->\n    ", scripts, bullet_col = "green")
  } else
    cli::cat_line("Nothing installed")

  PATHS <- normalizePath(strsplit(Sys.getenv("PATH"), ":", fixed = TRUE)[[1]],
                         mustWork = FALSE)
  if(!into %in% PATHS)
    warning(sprintf("destination '%s' is not on the PATH", into))
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.