How to Check if a File Exists

The os package provides functions to retrieve file information (os.Stat(...)). If this function returns an error, the package can also be used to check whether the error was because the file does not exist (using os.IsNotExist(...)). This is the basis of checking if a file does, or doesn’t, exist.


Despite the simplicity of the question, it can be a bit tricky to figure out the answer and I’ve seen a lot of confusion out there as to how to do it. The root cause is that there are actually three states that can come up:

  1. The file exists.
  2. The file doesn’t exist.
  3. We couldn’t determine if it exists or not (e.g. due to permissions, fs error).

The trouble comes when it is assumed that not being in category 2, means that you’re in category 1, when really you’re in either 1 or 3. It is further complicated by the fact that os.IsExist() is not useful in this situation.

The best way to understand it is to look at some code, then break down what is going on. I’ve seen each of the following options come up:

package main

import (
	"fmt"
	"os"
)

func testFile(filename string) {
	fmt.Println(filename + ":")

	if _, err := os.Stat(filename); os.IsNotExist(err) {
		fmt.Println("File definitely does not exist.")
	}

	// DON'T USE THIS!
	if _, err := os.Stat(filename); os.IsExist(err) {
		fmt.Println("This will never be reached!")
	}

	// Not sure when this would be useful.
	if _, err := os.Stat(filename); !os.IsNotExist(err) {
		fmt.Println("File might exist...")
	}

	if _, err := os.Stat(filename); err == nil {
		fmt.Println("File definitely exists!")
	}

	fmt.Println()
}

func main() {
	testFile("file-that-exists")
	testFile("file-that-doesnt-exist")
	testFile("restricted-file")
	testFile("restricted-dir/file-that-exists")
	testFile("restricted-dir/file-that-doesnt-exist")
}

And the output:

file-that-exists:
File might exist...
File definitely exists!

file-that-doesnt-exist:
File definitely does not exist.

restricted-file:
File might exist...
File definitely exists!

restricted-dir/file-that-exists:
File might exist...

restricted-dir/file-that-doesnt-exist:
File might exist...

Let’s run through each of the options used:

os.IsNotExist(err)

This is useful – if Stat returns an error, and that error is that the file does not exist, then you know for sure that the file doesn’t exist! Simple!

os.IsExist(err)

This is not useful here. To understand why we need to understand what the function is doing. It looks at an error message, and tells us if the error message indicates that a file exists. In the case of Stat, a file existing is not an error, so err will be nil. Now, IsExist(...) has no understanding of where the error came from, which means if it is looking at a value of nil it can’t get any information out of that and will return false as a result.

!os.IsNotExist(err)

Unfortunately, despite the double negative, not definitely not existing doesn’t mean existing. As an example, say there is a directory you can’t see in with a file you can’t see. An error will be returned saying permission denied. os.IsNotExist() will return false because the error message does not indicate the file does not exist (or that it exists). So all this tells you is that the file might exist, or it might not.

err == nil

Testing err == nil after a call to Stat will reliably tell you if the file is known to exist, and is the better alternative to checking !os.IsNotExist(err).

Conclusion

The difficulty really arises because there are 3 potential outcomes, the file exists, the file doesn’t exist, or we don’t know either way. Be careful with your testing, and remember to define what it is you want to know and what you are actually trying to achieve.

Leave a Reply

Your email address will not be published. Required fields are marked *