94

Is there an existing function to concatenate paths?

I know it is not that difficult to implement, but still... besides taking care of trailing / (or \) I would need to take care of proper OS path format detection (i.e. whether we write C:\dir\file or /dir/file).

As I said, I believe I know how to implement it; the question is: should I do it? Does the functionality already exist in existing R package?

Andrie
  • 176,377
  • 47
  • 447
  • 496
Adam Ryczkowski
  • 7,592
  • 13
  • 42
  • 68

2 Answers2

149

Yes, file.path()

R> file.path("usr", "local", "lib")
[1] "usr/local/lib"
R> 

There is also the equally useful system.file() for files in a package:

R> system.file("extdata", "date_time_zonespec.csv", package="RcppBDT")
[1] "/usr/local/lib/R/site-library/RcppBDT/extdata/date_time_zonespec.csv"
R> 

which will get the file extdata/date_time_zonespec.csv irrespective of

  1. where the package is installed, and
  2. the OS

which is very handy. Lastly, there is also

R> .Platform$file.sep
[1] "/"
R> 

if you insist on doing it manually.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
  • 4
    Great answer. But, the function does not take care of trailing "/", so `file.path("/home/user/","project")` results in invalid `/home/user//project`. Is there another function, or should I do it myself (trivially, though)? – Adam Ryczkowski Oct 28 '12 at 15:38
  • You get it if you start with an empty string: `file.path("", "home", "user", "project")` produces `"/home/user/project"` – Dirk Eddelbuettel Oct 28 '12 at 15:41
  • 6
    Note that `/home/user//project` and `/home/user/project` are both valid on Unix. What OS are you on? – flodel Oct 28 '12 at 15:41
  • @flodel Er... Linux. But I didn't know about it. Thanks! And it seems it is valid on Windows (on cmd.exe) too! – Adam Ryczkowski Oct 28 '12 at 15:42
  • 2
    This might be basic R knowledge but if you have yourlist=list("", "a", "b", "c") already and want to convert that to a path, `do.call(file.path, yourlist)` – Colin D Feb 23 '17 at 15:30
4

In case anyone wants, this is my own function path.cat. Its functionality is comparable with Python's os.path.join with the extra sugar, that it interprets the ...

With this function, you can construct paths hierarchically, but unlike the file.path, you leave the user the ability to override the hierarchy by putting an absolute path. And as an added sugar, he can put the ".." wherever he likes in the path, with obvious meaning.

e.g.

  • path.cat("/home/user1","project/data","../data2") yelds /home/user1/project/data2

  • path.cat("/home/user1","project/data","/home/user2/data") yelds /home/user2/data

The function works only with slashes as path separator, which is fine, since R transparently translates them to backslashes on Windows machine.

library("iterators") # After writing this function I've learned, that iterators are very inefficient in R.
library("itertools")

#High-level function that inteligentely concatenates paths given in arguments
#The user interface is the same as for file.path, with the exception that it understands the path ".."
#and it can identify relative and absolute paths.
#Absolute paths starts comply with "^\/" or "^\d:\/" regexp.
#The concatenation starts from the last absolute path in arguments, or the first, if no absolute paths are given.
path.cat<-function(...)
{
  elems<-list(...)
  elems<-as.character(elems)
  elems<-elems[elems!='' && !is.null(elems)]
  relems<-rev(elems)
  starts<-grep('^[/\\]',relems)[1]
  if (!is.na(starts) && !is.null(starts))
  {
    relems<-relems[1:starts]
  }
  starts<-grep(':',relems,fixed=TRUE)
  if (length(starts)==0){
    starts=length(elems)-length(relems)+1
  }else{
    starts=length(elems)-starts[[1]]+1}
  elems<-elems[starts:length(elems)]
  path<-do.call(file.path,as.list(elems))
  elems<-strsplit(path,'[/\\]',FALSE)[[1]]
  it<-ihasNext(iter(elems))
  out<-rep(NA,length(elems))
  i<-1
  while(hasNext(it))
  {
    item<-nextElem(it)
    if(item=='..')
    {
      i<-i-1
    } else if (item=='' & i!=1) {
      #nothing
    } else   {
      out[i]<-item
      i<-i+1
    }
  }
  do.call(file.path,as.list(out[1:i-1]))
}
Adam Ryczkowski
  • 7,592
  • 13
  • 42
  • 68
  • 1
    Cool comment, but there is an important difference: `normalizePath()` requires the path to be actually present on the R server. This may not always be desired. For me it is an important difference. OTOH manual says `normalizePath()` converts short names to long on Windows. – Adam Ryczkowski Jan 10 '16 at 17:49