如何在Go中检查文件是否存在?

668

Go的标准库没有专门用于检查文件是否存在的函数(例如Python的os.path.exists)。那么,什么是惯用的方法?


2
我真的不太明白。你同一分钟里说没有标准函数,又写了一个带有标准函数的答案。我错过了什么吗?至少问题应该被修复吧? - Denys Séguret
21
最好避免查询文件是否存在。由于答案的性质可能会让人产生歧义,获得的信息实际上并没有提供有关所询问时间内文件是否存在的有用信息 - 但该文件现在可能已不存在了。推荐的方法是简单地打开文件并检查是否失败。 - zzzz
4
@zzzz(我知道这已经过去很多年了,这个评论是给新读者的)一般情况下我同意。但是我的应用程序加载了一个第三方库,该库以一些文件路径作为初始化数据,但如果文件不存在则会导致段错误。我认为在不尝试打开文件的情况下检查文件是否存在是一个有效的场景,以便能够报告错误而不会发生致命崩溃,因为我的代码不需要直接读取文件内容或写入文件。 - Sergio Acosta
14个回答

1043

要检查文件是否不存在,相当于Python中的if not os.path.exists(filename)

if _, err := os.Stat("/path/to/whatever"); errors.Is(err, os.ErrNotExist) {
  // path/to/whatever does not exist
}

要检查文件是否存在,等效于 Python 的 if os.path.exists(filename):

编辑:根据最近的评论

if _, err := os.Stat("/path/to/whatever"); err == nil {
  // path/to/whatever exists

} else if errors.Is(err, os.ErrNotExist) {
  // path/to/whatever does *not* exist

} else {
  // Schrodinger: file may or may not exist. See err for details.

  // Therefore, do *NOT* use !os.IsNotExist(err) to test for file existence


}

6
有时候,它会返回ENOTDIR而不是NOTEXIST,例如,如果/etc/bashrc存在,那么/etc/bashrc/foobar将返回ENOTDIR - lidaobing
57
第二个代码片段有更微妙的错误;条件应该是!os.IsNotExist(err)。文件可能存在但是os.Stat因为其他原因(例如权限、磁盘故障)而失败。使用err == nil作为条件会错误地将这种失败归类为“文件不存在”。 - sqweek
10
检查文件是否存在是错误的:如果文件存在,err为nil。 - tangxinfa
2
一定要展开 ~ 否则它会返回 false... https://dev59.com/i2Mm5IYBdhLWcg3wburb#43578461 - Marcello DeSales
10
最新的文档建议在任何新代码中使用errors.Is(err, os.ErrNotExist):https://github.com/golang/go/blob/master/src/os/error.go#L90-L91 - Pal Kerecsenyi
显示剩余4条评论

159

gonuts邮件列表中,Caleb Spare的回答如下:

 

[...] 实际上并不经常需要检查路径是否存在,使用 os.Stat 对于需要检查的情况来说已经足够简单了。

 

[...] 例如:如果您要打开文件,则没有理由先检查其是否存在。文件可能会在检查和打开之间消失,而无论如何,您都需要在尝试打开文件后检查os.Open错误。因此,您只需在尝试打开文件后调用os.IsNotExist(err),并在那里处理它的不存在(如果需要进行特殊处理)。

 

[...] 您根本不需要检查路径是否存在(也不应该检查)。

 
       
  • os.MkdirAll无论路径是否已经存在都可以工作。(还需要检查该调用的错误。)

  •    
  • 您应该使用os.OpenFile(path,os.O_RDWR | os.O_CREATE | os.O_EXCL,0666),而不是使用os.Create。这样,如果文件已经存在,您将收到一个错误。与您的版本不同,这种方式没有竞争条件阻碍其他东西创建文件。

  •  

引用自:https://groups.google.com/forum/#!msg/golang-nuts/Ayx-BMNdMFo/4rL8FFHr8v4J


1
非常感谢您提供的引用,因为我在创建文件仅当文件不存在(os.O_EXCL)方面遇到了很多麻烦,因为我总是记不清这些标志的含义。我原本以为os.O_CREATE已经暗示了文件必须被创建并且在此之前不存在。 - natiiix

69
首先需要考虑的是,通常情况下你不仅想要检查一个文件是否存在,而是在文件存在时对其进行操作。在 Go 中,如果你尝试对不存在的文件执行某些操作,结果应该是特定的错误(os.ErrNotExist),最好的做法是检查返回的 err 值(例如调用 os.OpenFile(...) 函数时)是否为 os.ErrNotExist
以前推荐的做法是:
file, err := os.OpenFile(...)
if os.IsNotExist(err) {
    // handle the case where the file doesn't exist
}

然而,自从Go 1.13(在2019年末发布)中添加了errors.Is新的建议是使用errors.Is(参考链接)

file, err := os.OpenFile(...)
if errors.Is(err, os.ErrNotExist) {
    // handle the case where the file doesn't exist
}

通常最好避免在尝试执行操作前使用 os.Stat 检查文件是否存在,因为在您对其进行操作之前的时间窗口中,文件始终可能被重命名、删除等。

但是,如果您可以接受这个警告,并且您真的只想检查文件是否存在而不会继续对其进行有用的操作(例如,假设您正在编写一个无意义的 CLI 工具,它告诉您文件是否存在然后退出 ¯\_(ツ)_/¯),那么建议的方法是:

if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) {
    // file does not exist
} else {
    // file exists
}

我有一个问题,如何使用这种方法判断一个是文件还是目录? - Teocci
假设你正在谈论(不推荐使用的!)os.Stat方法:os.Stat返回info,err,如果err为nil,则info是一个FileInfo对象,您可以使用它来确定存在的东西是文件还是目录。有关更多详细信息,请参见此其他答案 - Dave Yarwood
好问题。我不确定,如果你在一个目录上调用OpenFile会发生什么。如果它返回一个错误,那么我认为在大多数情况下,处理这个错误的方式与处理其他错误的方式相同,应该足够了。 - Dave Yarwood
3
鲨鱼袭击很少见,除非在海滩上。需要特别检查文件是否存在的情况很少见,除非涉及到“如何在Go中检查文件是否存在”的问题。在我的情况下,如果文件存在,我的工作就完成了;如果它不存在,我必须生成它。 - Mattias Martens
那是一个很好的反例! - Dave Yarwood
显示剩余2条评论

55
你应该使用 os.Stat()os.IsNotExist() 函数,就像下面的例子一样:
func Exists(name string) (bool, error) {
    _, err := os.Stat(name)
    if err == nil {
        return true, nil
    }
    if errors.Is(err, os.ErrNotExist) {
        return false, nil
    }
    return false, err
}

编辑1:修复了在某些情况下返回true的问题。
编辑2:切换到使用errors.Is()而不是os.IsNotExist(),许多人认为这是最佳实践这里


19
注意:正如https://dev59.com/r2cs5IYBdhLWcg3w3HoG#22467409所指出的那样,这段代码会返回true,即使文件不存在,例如当Stat()返回权限被拒绝的情况。 - Michael
go build 未定义:错误 - CS QGB

30

其他答案中没有提到的是,传递给函数的路径实际上可能是一个目录。下面的函数确保该路径确实是一个文件。

func fileExists(filename string) bool {
    info, err := os.Stat(filename)
    if os.IsNotExist(err) {
        return false
    }
    return !info.IsDir()
}

需要指出的另一件事是:这段代码仍然可能导致竞态条件,即在fileExists函数运行时,另一个线程或进程删除或创建了指定的文件。

如果你担心这个问题,在你的线程中使用锁,序列化访问此函数,或者如果涉及多个应用程序,则使用进程间信号量。如果其他应用程序超出你的控制范围,那就没办法了。


如果涉及其他应用程序,您可能希望将文件保存在单独的文件夹中。如果您正在创建新文件,则 O_EXCL 是避免竞争的好帮手(如果您使用的平台支持该标志)。 - Olli
不要使用这个,因为在调用 return !info.IsDir()info 可能为空。 - igonejack

23
用户11617的示例是不正确的;即使在文件不存在但出现了其他错误的情况下,它也会报告该文件存在。

签名应为Exists(string) (bool, error)。然后,正如它所发生的那样,调用方也无法更好地处理。

他编写的代码应该改为:

func Exists(name string) bool {
    _, err := os.Stat(name)
    return !os.IsNotExist(err)
}

但是我建议使用以下方法:

func Exists(name string) (bool, error) {
  _, err := os.Stat(name)
  if os.IsNotExist(err) {
    return false, nil
  }
  return err != nil, err
}

7
第五个例子是什么?你能具体点吗? - xlm
1
你的第二个示例需要解构多个返回值 - 例如,_, err:= os.Stat(name) - David Duncan
10
为什么要返回 err != nil 而不是 err == nil?如果有错误,那么文件可能不存在? - idbrii

13
    _, err := os.Stat(file)
    if err == nil {
        log.Printf("file %s exists", file)
    } else if os.IsNotExist(err) {
        log.Printf("file %s not exists", file)
    } else {
        log.Printf("file %s stat error: %v", file, err)
    }

10
基本上

package main

import (
    "fmt"
    "os"
)

func fileExists(path string) bool {
    _, err := os.Stat(path)
    return !os.IsNotExist(err)
}

func main() {

    var file string = "foo.txt"
    exist := fileExists(file)
    
    if exist {
        fmt.Println("file exist")
    } else {

        fmt.Println("file not exists")
    }

}

运行示例

另一种方法

使用os.Open

package main

import (
    "fmt"
    "os"
)

func fileExists(path string) bool {
    _, err := os.Open(path) // For read access.
    return err == nil

}

func main() {

    fmt.Println(fileExists("d4d.txt"))

}


运行它


9

检查文件是否存在的最佳方法:

if _, err := os.Stat("/path/to/file"); err == nil || os.IsExist(err) {
    // your code here if file exists
}

8
功能示例:

以下是一个函数示例:

func file_is_exists(f string) bool {
    _, err := os.Stat(f)
    if os.IsNotExist(err) {
        return false
    }
    return err == nil
}

2
如果这个if语句是多余的吗? - Ilia Choly
目前虽然不是语法错误,但逻辑有问题。 该函数只应在文件不存在时返回false,但目前它在任何错误时都返回false。可能应该改为抛出panic异常。 此外,“file is exists”是错误的英语用法。 - LogicDaemon

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