使用r进行文件夹管理:检查目录是否存在,如果不存在则创建

510

我经常写一些生成大量输出的R脚本。我认为将这些输出放在它们自己的目录中会更加清晰。我编写了下面的代码,它会检查文件夹是否存在并进入该文件夹,或者创建该文件夹然后再进入。有更好的处理方法吗?

mainDir <- "c:/path/to/main/dir"
subDir <- "outputDirectory"

if (file.exists(subDir)){
    setwd(file.path(mainDir, subDir))
} else {
    dir.create(file.path(mainDir, subDir))
    setwd(file.path(mainDir, subDir))
    
}

1
我确定我见过一个R函数,它创建一个带有随机生成名称的临时目录并返回该名称。我认为还有一个类似的函数可以创建临时文件。我无法立即找到它们,但Databel包(http://cran.r-project.org/web/packages/DatABEL/index.html)有一个名为get_temporary_file_name的函数。 - PaulHurleyuk
65
在 R 代码中,你永远不应该使用 setwd() 函数,因为它会破坏使用工作目录的原本设计意图,这样做会使你的代码难以在不同计算机间移植。 - hadley
7
有趣的话题需要思考,我很希望你能分享其他实现同样目的的方法。在工作中,所有的电脑都连接到同一个网络,因此文件路径是一致的。如果不一致,那么我们需要解决的问题比脚本的可移植性更大。在这个特定的例子中,我正在编写一个脚本,该脚本将在机器上加载,并在国家公园周围运行2年。这个脚本将从本地 SQL 实例获取数据,进行一些处理,并输出一个 .csv 文件。最终产品将是一个 .bat 文件,最终用户将永远不需要修改它。 - Chase
1
@Marek - 啊,我明白了。所以你的意思是我应该用类似于write.table(file = "path/to/output/directory", ...)的东西来替换我的setwd()调用吗? - Chase
8
可以这样翻译:是的,或者将out_dir <- "path/to/output/directory"参数化,然后使用write.table(file = file.path(out_dir,"table_1.csv"), ...)。甚至可以使用out_file <- function(fnm) file.path("path/to/output/directory", fnm),然后使用write.table(file = out_file("table_1.csv"), ...)(我在使用网络驱动器时使用类似的方法)。 - Marek
显示剩余4条评论
10个回答

507

使用 showWarnings = FALSE

dir.create(file.path(mainDir, subDir), showWarnings = FALSE)
setwd(file.path(mainDir, subDir))

dir.create()不会因为目录已经存在而崩溃,只会输出警告信息。如果您可以接受看到警告信息,那么这样做就没有问题:

dir.create(file.path(mainDir, subDir))
setwd(file.path(mainDir, subDir))

72
当使用 showWarnings = FALSE 时要注意,这也会隐藏其他警告,例如目录无法创建。 - zelanix
6
有没有一种方法可以只抑制一个特定的警告? - Bas
3
你好,我想创建嵌套目录,例如如果我在test1文件夹中,则在其中创建test2文件夹,在其中再创建test3文件夹……但现在我遇到了问题。是否有一种方法可以创建三级目录,即使directory1不存在? - Praveen Kesani
12
这是您要找的吗?dir.create("test1/test2/test3/", recursive=TRUE) - dean.
11
非常晚的回复,但是 suppressWarnings(<statement>) 只会抑制该语句产生的警告。 - Ram RS

201

从2015年4月16日起,随着R 3.2.0的发布,有一个名为dir.exists()的新功能。要使用此功能并在目录不存在时创建目录,您可以使用以下代码:

ifelse(!dir.exists(file.path(mainDir, subDir)), dir.create(file.path(mainDir, subDir)), FALSE)

如果该目录已经存在或无法创建,将返回FALSE,如果该目录不存在但成功创建,则返回TRUE

请注意,如果您只想检查目录是否存在,可以使用

dir.exists(file.path(mainDir, subDir))

13
请注意,对于非向量化分支,使用ifelse()并不是一个好的实践。 - Lionel Henry
3
由于你的代码错误地表现出似乎正在发生向量化的事情。这就像使用向量化的 | 而不是标量的 ||。它可以工作,但是这样做是不好的做法。 - Lionel Henry
2
天啊,我使用|来编写我的if语句是错误的,这是因为矢量化技术导致有时候不能使用||吗?我知道这离题了,但我太渴望找出答案了。我一定会去阅读更多关于矢量化技术的内容。谢谢。 - Bas
5
如果我们应该避免使用 ifelse,那么最佳实践方法是什么? - KillerSnail
9
使用 if 和 else ;) - Lionel Henry
显示剩余5条评论

40

这是一个简单的检查,如果目录不存在则创建目录:

## Provide the dir name(i.e sub dir) that you want to create under main dir:
output_dir <- file.path(main_dir, sub_dir)

if (!dir.exists(output_dir)){
dir.create(output_dir)
} else {
    print("Dir already exists!")
}

37

一句话概括:

if (!dir.exists(output_dir)) {dir.create(output_dir)}

示例:

dateDIR <- as.character(Sys.Date())
outputDIR <- file.path(outD, dateDIR)
if (!dir.exists(outputDIR)) {dir.create(outputDIR)}

19

就一般架构而言,我建议在目录创建方面采用以下结构。这可以涵盖大多数潜在问题,任何其他目录创建问题都将通过dir.create调用检测到。

mainDir <- "~"
subDir <- "outputDirectory"

if (file.exists(paste(mainDir, subDir, "/", sep = "/", collapse = "/"))) {
    cat("subDir exists in mainDir and is a directory")
} else if (file.exists(paste(mainDir, subDir, sep = "/", collapse = "/"))) {
    cat("subDir exists in mainDir but is a file")
    # you will probably want to handle this separately
} else {
    cat("subDir does not exist in mainDir - creating")
    dir.create(file.path(mainDir, subDir))
}

if (file.exists(paste(mainDir, subDir, "/", sep = "/", collapse = "/"))) {
    # By this point, the directory either existed or has been successfully created
    setwd(file.path(mainDir, subDir))
} else {
    cat("subDir does not exist")
    # Handle this error as appropriate
}

请注意,如果~/foo不存在,则调用dir.create('~/foo/bar')将失败,除非您指定recursive = TRUE


6
你是否有使用 paste( ... ) 而不是 file.path(mainDir, subDir) 的原因?此外,如果你将路径赋值给 path<- file.path(mainDir, subDir),可以在 if 语句中重复使用它 5 次,这会使代码更易读。 - MikeF

13

我在使用 R 2.15.3 递归地创建共享网络驱动器上的树结构时遇到了权限错误的问题。

为了解决这个问题,我手动创建了该结构;

mkdirs <- function(fp) {
    if(!file.exists(fp)) {
        mkdirs(dirname(fp))
        dir.create(fp)
    }
} 

mkdirs("H:/foo/bar")

11

在原始帖子中,使用file.exists()检测目录是否存在是一个问题。如果subDir包含现有文件的名称(而不仅仅是路径),file.exists()会返回TRUE,但是对setwd()的调用将失败,因为您无法将工作目录设置为指向文件。

我建议使用file_test(op="-d", subDir)来检查目录是否存在,如果subDir是现有目录,则返回"TRUE",但如果subDir是现有文件或不存在的文件或目录,则返回FALSE。同样,可以通过op="-f"检查文件是否存在。

另外,如另一条评论所述,工作目录是R环境的一部分,应该由用户控制,而不是脚本。理想情况下,脚本不应更改R环境。为解决此问题,我可能会使用options()来存储全局可用的目录,以便在其中输出所有内容。

因此,请考虑以下解决方案,其中someUniqueTag只是程序员定义的选项名称前缀,这使得具有相同名称的选项已经存在的可能性较小。(例如,如果您正在开发名为"filer"的软件包,则可以使用filer.mainDir和filer.subDir)。

以下代码将用于设置可供其他脚本稍后使用的选项(从而避免在脚本中使用setwd()),并在必要时创建文件夹:

mainDir = "c:/path/to/main/dir"
subDir = "outputDirectory"

options(someUniqueTag.mainDir = mainDir)
options(someUniqueTag.subDir = "subDir")

if (!file_test("-d", file.path(mainDir, subDir)){
  if(file_test("-f", file.path(mainDir, subDir)) {
    stop("Path can't be created because a file with that name already exists.")
  } else {
    dir.create(file.path(mainDir, subDir))
  }
}

然后,在需要操作subDir中的文件的任何后续脚本中,您可以使用类似以下的内容:

mainDir = getOption(someUniqueTag.mainDir)
subDir = getOption(someUniqueTag.subDir)
filename = "fileToBeCreated.txt"
file.create(file.path(mainDir, subDir, filename))

这个解决方案使工作目录完全由用户控制。


8

我知道这个问题是一段时间以前问的,但如果有用的话,here包对于不必引用特定文件路径和使代码更可移植非常有帮助。它将自动将您的工作目录定义为您的.Rproj文件所在的目录,因此以下内容通常就足够了,无需定义到您的工作目录的文件路径:

library(here)

if (!dir.exists(here(outputDir))) {dir.create(here(outputDir))}


4

我创建了一个名为hutils的包,其中包含函数provide.dir(path)provide.file(path),用于检查位于path的目录/文件是否存在,如果不存在则创建它们。


干得不错 - 非常干净,没有所有的“如果目录/文件存在”的检查。 - NullPumpkinException

3
要查找路径是否为有效目录,请尝试:
file.info(cacheDir)[1,"isdir"]

file.info不关心路径末尾的斜杠。

在Windows上,如果目录以斜杠结尾,则使用file.exists将失败,在没有斜杠的情况下将成功。因此,无法使用它来确定路径是否为目录。

file.exists("R:/data/CCAM/CCAMC160b_echam5_A2-ct-uf.-5t05N.190to240E_level1000/cache/")
[1] FALSE

file.exists("R:/data/CCAM/CCAMC160b_echam5_A2-ct-uf.-5t05N.190to240E_level1000/cache")
[1] TRUE

file.info(cacheDir)["isdir"]

除了没有包括 dir.create() 部分之外,这个答案有什么问题吗?这些语句是错误的还是被认为对解决问题没有帮助? - mschilli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接