Как проверить, существует ли файл в Go?


436

Стандартная библиотека Go не имеет функции, предназначенной исключительно для проверки того, существует файл или нет (как в Python os.path.exists). Какой идиоматический способ сделать это?


Я действительно не понимаю. В тот же момент вы говорите, что нет стандартной функции, и пишете ответ со стандартной функцией. Что мне не хватает? Не должен ли хотя бы вопрос быть исправленным?
Денис Сегюре

@dystroy - исправил вопрос.
Шридхар Ратнакумар

11
Лучше не спрашивать о существовании файла. Из-за нечеткой природы ответа, полученная информация говорит, что на самом деле ничего полезного выше файла не существовало в запрошенное время - но, возможно, его больше не существует. Рекомендованный способ - просто открыть файл и проверить, не удалось это или нет.
zzzz

2

2
@zzzz (я знаю, что это были годы, этот комментарий для новых читателей) Я согласен в общем случае. Но мое приложение загружает стороннюю библиотеку, которая принимает какой-либо путь к файлу в качестве данных инициализации, но при обнаружении такого файла возникает ошибка, если файл не существует. Я думаю, что это правильный сценарий для проверки, существует ли файл, когда вы пытаетесь открыть его, чтобы иметь возможность сообщить об ошибке без фатального сбоя, поскольку мой код не должен читать содержимое файла или записывать в файл напрямую.
Серхио Акоста,

Ответы:


693

Чтобы проверить, не существует ли файл, эквивалентный Python if not os.path.exists(filename):

if _, err := os.Stat("/path/to/whatever"); os.IsNotExist(err) {
  // 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 os.IsNotExist(err) {
  // 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


}

3
иногда он возвращает ENOTDIR вместо NOTEXIST, например, если он /etc/bashrcсуществует, /etc/bashrc/foobarвозвращаетсяENOTDIR
lidaobing

43
Второй фрагмент более тонко неверен; условие должно быть !os.IsNotExist(err). Возможно, файл существует, но os.Statне работает по другим причинам (например, разрешение, сбой диска). Использование err == nilв качестве условия неправильно классифицирует такие сбои как «файл не существует».
пятницу

9
Проверить, существует ли файл, неверно: err равно nil, если файл существует
tangxinfa

1
Удостоверьтесь, что вы расширили ~, иначе он вернет false ... stackoverflow.com/questions/17609732/…
Марчелло де Сэйлс

Вы можете использовать os.IsExist () в зависимости от случая, может быть более идиоматически вместо двойного отрицания при выполнении! Os.IsNotExistant ()
Ариэль Монако

126

Ответ от Caleb Spare опубликован в списке рассылки Gonuts .

[...] Это на самом деле не нужно очень часто, и [...] использование os.Statдостаточно просто для случаев, когда это требуется.

[...] Например: если вы собираетесь открыть файл, нет причин проверять, существует ли он первым. Файл может исчезнуть между проверкой и открытием, и в любом случае вам нужно будет проверить os.Openошибку независимо. Таким образом, вы просто звоните os.IsNotExist(err)после того, как пытаетесь открыть файл, и справляетесь с его несуществованием там (если это требует специальной обработки).

[...] Вам не нужно проверять пути, существующие вообще (и не нужно).

  • os.MkdirAllработает ли пути уже существуют. (Также вам необходимо проверить ошибку этого звонка.)

  • Вместо того, чтобы использовать os.Create, вы должны использовать os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666). Таким образом, вы получите ошибку, если файл уже существует. Кроме того, это не имеет условия гонки с чем-то другим, создающим файл, в отличие от вашей версии, которая заранее проверяет существование.

Взято из: https://groups.google.com/forum/#!msg/golang-nuts/Ayx-BMNdMFo/4rL8FFHr8v4J


31

Вы должны использовать os.Stat()и os.IsNotExist()функцию , как в следующем примере:

// Exists reports whether the named file or directory exists.
func Exists(name string) bool {
    if _, err := os.Stat(name); err != nil {
        if os.IsNotExist(err) {
            return false
        }
    }
    return true
}

Пример извлечен отсюда .


12
Осторожно: как указал stackoverflow.com/a/22467409/712014 , этот код возвращает значение true, даже если файл не существует, например, когда Stat () возвращает отклоненное разрешение.
Майкл

19

Пример по user11617 неправилен; он сообщит, что файл существует даже в тех случаях, когда его нет, но произошла ошибка какого-либо другого рода.

Подпись должна быть 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
Что такое пример 5? Не могли бы вы быть конкретным, пожалуйста.
xlm

1
Ваш второй пример должен деструктурировать несколько возвращаемых значений - например, _, err: = os.Stat (name)
Дэвид Дункан,

6
Зачем возвращаться err != nilвместо err == nil? Если есть ошибка, то файл, вероятно, не существует?
idbrii

14

Что пропустили другие ответы, так это то, что путь к функции может быть каталогом. Следующая функция гарантирует, что путь действительно является файлом.

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

Еще один момент, на который следует обратить внимание: этот код все еще может привести к состоянию гонки, когда другой поток или процесс удаляет или создает указанный файл во время работы функции fileExists.

Если вас это беспокоит, используйте блокировку в своих потоках, сериализуйте доступ к этой функции или используйте межпроцессный семафор, если задействованы несколько приложений. Думаю, если другие приложения задействованы вне вашего контроля, вам не повезло.


12
    _, 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)
    }

7

Пример функции:

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

1
Разве это не избыточно?
Илья Чоли

6

Давайте сначала рассмотрим несколько аспектов. Обе функции, предоставляемые osпакетом, - golangэто не утилиты, а средства проверки ошибок. Я имею в виду, что они являются просто оболочкой для обработки ошибок на кроссплатформенной платформе.

Таким образом, в основном, os.Statесли эта функция не выдает никакой ошибки, что означает, что файл существует, если вам нужно проверить, что это за ошибка, то здесь следует использовать эти две функции os.IsNotExistи os.IsExist.

Это может быть понято как Statошибка выброса файла, потому что он не существует, или это ошибка выброса, потому что он существует, и есть некоторая проблема с ним.

Параметр, который принимают эти функции, имеет тип error, хотя вы можете передать nilего, но это не имеет смысла.

Это также указывает на тот факт IsExist is not same as !IsNotExist, что это две разные вещи.

Так что теперь, если вы хотите знать, существует ли данный файл в go, я бы предпочел лучший способ:

if _, err := os.Stat(path/to/file); !os.IsNotExist(err){
   //TODO
} 

1

Как уже упоминалось в других ответах, можно построить требуемое поведение / ошибки, используя различные флаги с os.OpenFile. На самом деле, os.Createэто просто сокращенная запись по умолчанию:

// Create creates or truncates the named file. If the file already exists,
// it is truncated. If the file does not exist, it is created with mode 0666
// (before umask). If successful, methods on the returned File can
// be used for I/O; the associated file descriptor has mode O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*File, error) {
    return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

Вы должны объединить эти флаги самостоятельно, чтобы получить интересующее вас поведение:

// Flags to OpenFile wrapping those of the underlying system. Not all
// flags may be implemented on a given system.
const (
    // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
    O_RDONLY int = syscall.O_RDONLY // open the file read-only.
    O_WRONLY int = syscall.O_WRONLY // open the file write-only.
    O_RDWR   int = syscall.O_RDWR   // open the file read-write.
    // The remaining values may be or'ed in to control behavior.
    O_APPEND int = syscall.O_APPEND // append data to the file when writing.
    O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
    O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
    O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
    O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)

В зависимости от того, что вы выберете, вы получите разные ошибки.

Вот пример, где я хочу открыть файл для записи, но я урежу существующий файл, только если пользователь сказал, что все в порядке:

var f *os.File
if truncateWhenExists {
    // O_TRUNC - truncate regular writable file when opened.
    if f, err = os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
        log.Fatalln("failed to force-open file, err:", err)
    }
} else {
    // O_EXCL - used with O_CREATE, file must not exist
    if f, err = os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644); err != nil {
        log.Fatalln("failed to open file, err:", err) 
   }
}

0

Лучший способ проверить, существует ли файл:

if _, err := os.Stat("/path/to/file"); err == nil || os.IsExist(err) {
    // your code here if file exists
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.