13

I have a written a script that when it is sourced checks if the script is being run interactively using interactive(). If it is run interactively, it does not search for command line arguments. However, if it is not run interactively, it searches for command line arguments and throws an error.

This is normally fine, but sometimes I write a second R script that I want to run independently just to process some data. So Script2 sources Script1, and Script1 detects that it is not being run interactively, and begins searching for command line arguments and throwing errors.

Is there a way besides interactive() that a script can detect its context? For example, I would want separate behavior when it is being run directly vs when it is being loaded for access to one of its internal functions. With packages I could do something like dplyr::arrange() to access arrange without having to load all of dplyr.

EDIT: My current very janky workaround has been to start an interactive session, source Script1, use save.image() to save the functions, and then in Script2 use load to load the saved .RData file. But obviously this is not...elegant.


I don't think the exact code I use is that relevant, but including it in case someone feels this is important to the answer...

Stripped down example code:

#!/usr/bin/env Rscript

library(optparse)

function1 <- function(etc,etc) {}
function2 <- function(etc,etc) {}

if(!interactive()) { 

    # example call
    # Rscript create_reference_file.R -c cd4cd8 -o /home/outputfolder/ 

    option_list = list(
        make_option(c('-c', '--cell'), type = 'character', default = NULL,
                    help = 'the name of the cell',
                    metavar = 'character'),
        make_option(c('-o','--outdir'), type = 'character', default = NULL, 
                    help = 'the location where you wish to store your output',
                    metavar = 'character'),
    )

    opt_parser <- OptionParser(option_list = option_list)
    opt <- parse_args(opt_parser)

    function1(opt); function2(opt) # etc etc, I do stuff with the opt inputs
}
Brandon
  • 1,722
  • 1
  • 19
  • 32
  • 4
    You're looking for something akin to python's `if __name__ == '__main__':`, right? – r2evans Dec 21 '17 at 20:41
  • (If you don't speak python, here's some background on that reference: https://stackoverflow.com/questions/419163/what-does-if-name-main-do) – r2evans Dec 21 '17 at 20:54
  • Yes! That is basically the same functionality that I am looking for (or something that can achieve an analogous result). – Brandon Dec 21 '17 at 21:21

1 Answers1

23

EDIT

Okay, this is a LOT more like python's __name__ trick. (Previous answer below, kept for historical reasons.)

function1 <- function(etc,etc) {}
function2 <- function(etc,etc) {}

if (sys.nframe() == 0L) {
    library(optparse)
    # ...
}

It is about as minimalist as one could hope for, does not require the sourceing script to know anything about it, and seems to work well even when nested.

Other possible mechanisms could be used (additional functions required) by looking at script names, per Rscript: Determine path of the executing script. Many plausible (some really good) solutions exist there, but they all require a pre-defined function not defined in a base package (or non-trivial code included in the script to be sourced). If you want to "assume package X is installed", then your script becomes potentially non-portable.


(Previous answer, I suggest you use above.)

I'll throw this out as a hack ... it's only slightly less janky than your workaround, but it relies on the calling script knowing something of what the called script is testing for.

If the calling script sets a variable:

BEING_SOURCED_FROM_SOMEWHERE <- TRUE

then the called script can check for it:

function1 <- function(etc,etc) {}
function2 <- function(etc,etc) {}

if (! exists("BEING_SOURCED_FROM_SOMEWHERE")) {
  library(optparse)
  # ...
}

I don't like it. It isn't as flexible as python's

if __name__ == "__main__":
    import optparse
    # ...

But I think I dislike it less than your use of save and load for function definitions.

r2evans
  • 141,215
  • 6
  • 77
  • 149
  • BTW: feel free to let your original question *stew* for a bit to see if others have more elegant solutions. If you accept it too soon, other answerers might forego even reading the question. :-) – r2evans Dec 21 '17 at 21:33
  • Perhaps @DirkEddelbuettel has something within the [`littler`](http://dirk.eddelbuettel.com/code/littler.html) utility? – r2evans Dec 21 '17 at 21:36
  • It's not perfect, but definitely a big step in the right direction compared to what I was attempting before. – Brandon Dec 21 '17 at 21:45
  • 2
    @Brandon, I think my edit includes the elegant solution you were seeking. – r2evans Dec 23 '17 at 17:24
  • That's a great find! This is much better, and I agree, I prefer to stick to base to achieve this as others will likely have to run this code. – Brandon Dec 23 '17 at 21:15
  • 2
    minor thought: for the benefit of late-comers, edit your answer so your minimalist & elegant EDIT using sys.nframe comes first... – malcook Aug 25 '18 at 03:46
  • 1
    When wanting to treat "sourcing" the file in R studio as running the script, I had to edit the condition to `if ( if(!is.na(Sys.getenv("RSTUDIO", unset = NA))) sys.nframe() == 4L else sys.nframe() == 0L) { # interactive code here }`. Messy and hacky, but works for me. – Josiah Yoder Jul 15 '19 at 16:45
  • 1
    I didn't know that (I don't use RStudio IDE), thank you! Perhaps code golf'd a little: `if (sys.nframe() == (4L * (!is.na(Sys.getenv("RSTUDIO", unset = NA))))) {...}`. – r2evans Jul 15 '19 at 17:01