get filename and path of `source`d file

2020-02-02 11:12发布

How can a sourced or Sweaved file find out its own path?

Background:

I work a lot with .R scripts or .Rnw files. My projects are organized in a directory structure, but the path of the project's base directory frequently varies between different computers (e.g. because I just do parts of data analysis for someone else, and their directory structure is different from mine: I have projects base directories ~/Projects/StudentName/ or ~/Projects/Studentname/Projectname and most students who have just their one Project usually have it under ~/Measurements/ or ~/DataAnalysis/ or something the like - which wouldn't work for me).

So a line like

    setwd (my.own.path ()) 

would be incredibly useful as it would allow to ensure the working directory is the base path of the project regardless of where that project actually is. Without the need that the user must think of setting the working directory.

Let me clarify: I look for a solution that works with pressing the editor's/IDE's source or Sweave Keyboard shortcut of the unthinking user.

标签: r sweave
6条回答
ら.Afraid
2楼-- · 2020-02-02 11:52

I have no direct solution how to obtain the directory of the file itself but if you have a limited range of directories and directory structures you can probably use

 if(file.exists("c:/somedir")==TRUE){setwd("c:/somedir")}

You could check out the pattern of the directory in question and then set the dir. Does this help you?

查看更多
戒情不戒烟
3楼-- · 2020-02-02 11:53

This answer works for source and also inside nvim-R - I have no idea if it works with knitr and similar things. Any feedback appreciated.

If you have multiple scripts source-ing each other, it is important to get the correct one. That is, the largest i for which sys.frame(i)$ofile exists.

get.full.path.to.this.sourced.script = function() {    
    for(i in sys.nframe():1) {  # Go through all the call frames,
                                # in *reverse* order.
        x = sys.frame(i)$ofile
        if(!is.null(x))               # if $ofile exists,
            return(normalizePath(x))  #  then return the full absolute path
    }
}
查看更多
贼婆χ
4楼-- · 2020-02-02 11:56

Starting from gsk3's Seb's suggestions, here's an idea:

  • the combination of username (login) and IP or name of the computer could be used to select the right directory.

That leads to something like:

    setwd (switch (paste (Sys.info () [c ("user", "nodename")], collapse="."), 
           user.laptop  = "~/Messungen",
           user2.server = "~/Projekte/Projekt/",
           ))

So there is an automatic solution, that

  • works with source
  • works with Sweave
  • even works for interactive sessions where the commands are sent line by line

  • the combination of user and nodename of course needs to be specific

  • the paths need to be edited by hand, though.

Improvements welcome!


Update:

Gabor Grothendieck answered the following to a related question on r-help today:

this.dir <- dirname(parent.frame(2)$ofile)
setwd(this.dir)

which will work for source.


Another update: I now do most of the data analysis work in RStudio. RStudio's projects basically solve the problem: RStudio changes the working directory to the project root directory every time I switch between projects.

I can therefore put the project directory as far down my directory tree as I want (and the students can also put their copy wherever they want) and sync the data files and scripts/.Rnws via version control (We use a private git server). The RStudio project files are kept out of the version control, i.e. .gitignore contains .Rproj.user.

Obviously, within the project, the directory structure needs to be synchronized.

查看更多
别忘想泡老子
5楼-- · 2020-02-02 12:04

Just FYI, knitr will setwd() to the dir of the input file when (and only when) evaluating the code chunks, i.e. if you call knit('path/to/input.Rnw'), the working dir will be temporarily switched to path/to/. If you want to know the input dir in code chunks, currently you can call an unexported function knitr:::input_dir() (I may export it in the future).

查看更多
爷、活的狠高调
6楼-- · 2020-02-02 12:11

You can use sys.calls() to get the command used to source the file. Then you need a bit of trickery using regular expressions to get the pathname, bearing in mind that source("something/filename") could have used either the absolute or relative path. Here's a first attempt at putting all the pieces together: try inserting the following lines at the top of a source file.

whereFrom=sys.calls()[[1]]
# This should be an expression that looks something like
# source("pathname/myfilename.R")
whereFrom=as.character(whereFrom[2]) # get the pathname/filename
whereFrom=paste(getwd(),whereFrom,sep="/") # prefix it with the current working directory
pathnameIndex=gregexpr(".*/",whereFrom) # we want the string up to the final '/'
pathnameLength=attr(pathnameIndex[[1]],"match.length")
whereFrom=substr(whereFrom,1,pathnameLength-1)
print(whereFrom) # or "setwd(whereFrom)" to set the working directory

It's not very robust—for instance, it will fail on windows with source("pathname\\filename"), and I haven't tested what happens if you have one file sourcing another file—but you might be able to build a solution on top of this.

查看更多
三岁会撩人
7楼-- · 2020-02-02 12:11

An additional problem is that the working directory is a global variable, which can be changed by any script, so if your script calls another script, it will have to set the wd back. In RStudio I use Session -> Set Working Directory -> To Source File Location (I know, it's not ideal), and then my script does

wd = getwd ()
...
source ("mySubDir/myOtherScript.R", chdir=TRUE); setwd (wd)
...
source ("anotherSubDir/anotherScript.R", chdir=TRUE); setwd (wd)

In this way one can maintain a stack of working directories. I would love to see this implemented in the language itself.

查看更多
登录 后发表回答