Jak sprawdzić, czy plik istnieje w Go?

435

Standardowa biblioteka Go nie ma funkcji służącej wyłącznie do sprawdzania, czy plik istnieje, czy nie (takiej jak Python os.path.exists). Jak idiomatyczny sposób to zrobić?

Sridhar Ratnakumar
źródło
Naprawdę tego nie rozumiem. W tej samej chwili mówisz, że nie ma standardowej funkcji i piszesz odpowiedź za pomocą standardowej funkcji. Czego mi brakuje ? Czy nie powinno się przynajmniej rozwiązać pytania?
Denys Séguret,
@dystroy - naprawiono pytanie.
Sridhar Ratnakumar,
11
Lepiej unikać pytania o istnienie pliku. Biorąc pod uwagę racjonalny charakter odpowiedzi, uzyskane informacje mówią, że tak naprawdę nic użytecznego nad plikiem nie istniało w zadanym czasie - ale może już nie istnieć. Zalecanym sposobem jest po prostu otwarcie pliku i sprawdzenie, czy to się nie powiedzie.
zzzz
2
Zostało to już odpowiedział tutaj
Sergey Koulikov
2
@zzzz (wiem, że minęły lata, ten komentarz jest dla nowych czytelników) Zgadzam się w ogólnym przypadku. Ale moja aplikacja ładuje bibliotekę innej firmy, która przyjmuje pewną ścieżkę pliku jako dane inicjujące, ale segfuje, jeśli plik nie istnieje. Myślę, że jest to prawidłowy scenariusz sprawdzania, czy plik istnieje bez próby otwarcia go, aby móc zgłosić błąd bez krytycznej awarii, ponieważ mój kod nie musi odczytywać zawartości pliku ani zapisywać bezpośrednio w pliku.
Sergio Acosta

Odpowiedzi:

692

Aby sprawdzić, czy plik nie istnieje, odpowiednik Pythona if not os.path.exists(filename):

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

Aby sprawdzić, czy plik istnieje, odpowiednik Pythona if os.path.exists(filename):

Edytowane: według ostatnich komentarzy

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


}
Sridhar Ratnakumar
źródło
3
czasami zwraca ENOTDIR zamiast NOTEXIST, na przykład, jeśli /etc/bashrcistnieje, /etc/bashrc/foobarzwróciENOTDIR
lidaobing
43
Drugi fragment jest bardziej subtelny; warunek powinien być !os.IsNotExist(err). Możliwe, że plik istnieje, ale os.Statnie działa z innych powodów (np. Pozwolenie, awaria dysku). Użycie err == niljako warunku niepoprawnie klasyfikuje takie awarie jako „plik nie istnieje”.
sqweek
9
Aby sprawdzić, czy plik istnieje, jest nieprawidłowy: err jest zerowy, jeśli plik istnieje
tangxinfa
1
Pamiętaj, aby rozwinąć ~, inaczej zwróci fałsz ... stackoverflow.com/questions/17609732/
Marcello de Sales
Możesz użyć os.IsExist () w zależności od przypadku, może być bardziej idiomatycznie zamiast podwójnej negacji podczas robienia! Os.IsNotExistant ()
Ariel Monaco
126

Odpowiedz przez Caleb Spare opublikowany na liście mailingowej gonuts .

[...] W rzeczywistości nie jest to bardzo często potrzebne, a [...] korzystanie os.Statjest dość łatwe w przypadkach, w których jest to wymagane.

[...] Na przykład: jeśli zamierzasz otworzyć plik, nie ma powodu, aby sprawdzać, czy plik istnieje najpierw. Plik może zniknąć między sprawdzaniem a otwieraniem, a mimo to musisz sprawdzić os.Openbłąd niezależnie od tego. Więc po prostu zadzwoń os.IsNotExist(err)po próbie otwarcia pliku i poradzić sobie z jego nieistnieniem (jeśli wymaga to specjalnej obsługi).

[...] Nie musisz wcale sprawdzać istniejących ścieżek (i nie powinieneś).

  • os.MkdirAlldziała niezależnie od tego, czy ścieżki już istnieją. (Musisz także sprawdzić błąd z tego połączenia).

  • Zamiast używać os.Create, powinieneś użyć os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666). W ten sposób pojawi się błąd, jeśli plik już istnieje. W przeciwieństwie do twojej wersji, która wcześniej sprawdza istnienie, nie jest to warunek wyścigu z czymś innym, co tworzy plik.

Zaczerpnięte z: https://groups.google.com/forum/#!msg/golang-nuts/Ayx-BMNdMFo/4rL8FFHr8v4J

OscarRyz
źródło
30

Należy użyć funkcji os.Stat()i, os.IsNotExist()jak w poniższym przykładzie:

// 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
}

Przykład został wyciągnięty stąd .

Afriza N. Arief
źródło
12
Uwaga: jak wskazano stackoverflow.com/a/22467409/712014 , ten kod zwraca wartość true, nawet jeśli plik nie istnieje, na przykład gdy Stat () zwraca odmowę dostępu.
Michael
19

Przykład przez user11617 jest nieprawidłowe; zgłosi, że plik istnieje nawet w przypadkach, w których go nie ma, ale wystąpił błąd innego rodzaju.

Podpis powinien mieć postać Exists (string) (bool, error). A potem, jak to się dzieje, strony połączeń nie są lepsze.

Kod, który napisał, byłby lepszy jako:

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

Ale proponuję to zamiast tego:

func Exists(name string) (bool, error) {
  _, err := os.Stat(name)
  if os.IsNotExist(err) {
    return false, nil
  }
  return err != nil, err
}
użytkownik3431012
źródło
7
Co to jest przykład 5? Czy mógłbyś być konkretny, proszę.
xlm
1
Drugi przykład musi zniszczyć wiele zwracanych wartości - np. _, Err: = os.Stat (name)
David Duncan
6
Po co wracać err != nilzamiast err == nil? Jeśli wystąpił błąd, plik prawdopodobnie nie istnieje?
idbrii
14

Inne pominięte odpowiedzi to to, że ścieżka podana do funkcji może być katalogiem. Poniższa funkcja zapewnia, że ​​ścieżka jest tak naprawdę plikiem.

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

Kolejna rzecz, na którą należy zwrócić uwagę: ten kod może nadal prowadzić do wyścigu, w którym inny wątek lub proces usuwa lub tworzy określony plik, gdy funkcja fileExists jest uruchomiona.

Jeśli martwisz się tym, użyj blokady w wątkach, szereguj dostęp do tej funkcji lub skorzystaj z semafora między procesami, jeśli zaangażowanych jest wiele aplikacji. Jeśli w grę wchodzą inne aplikacje, poza twoją kontrolą, nie masz szczęścia.

ZuBsPaCe
źródło
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)
    }
tangxinfa
źródło
7

Przykład funkcji:

func file_is_exists(f string) bool {
    _, err := os.Stat(f)
    if os.IsNotExist(err) {
        return false
    }
    return err == nil
}
honmaple
źródło
1
Czy to nie jest zbędne?
Ilia Choly
6

Spójrzmy najpierw na kilka aspektów, obie funkcje dostarczane przez ospakiet golangnie są narzędziami, ale narzędziami do sprawdzania błędów, co mam na myśli to, że są one tylko opakowaniem do obsługi błędów na różnych platformach.

Zasadniczo, jeśli os.Statjeśli ta funkcja nie daje żadnego błędu, oznacza to, że plik istnieje, jeśli musisz sprawdzić, jaki to jest błąd, oto użycie tych dwóch funkcji os.IsNotExisti os.IsExist.

Można to rozumieć jako Statbłąd zgłaszania pliku, ponieważ nie istnieje lub jest to błąd zgłaszania, ponieważ istnieje i jest z nim jakiś problem.

Parametr, który te funkcje przyjmują, jest typem error, chociaż możesz być w stanie przekazaćnil , ale nie ma to sensu.

Wskazuje to również na fakt IsExist is not same as !IsNotExist, że są to dwie różne rzeczy.

Więc jeśli chcesz wiedzieć, czy dany plik istnieje w go, wolałbym najlepiej:

if _, err := os.Stat(path/to/file); !os.IsNotExist(err){
   //TODO
} 
Farhaan Bukhsh
źródło
1

Jak wspomniano w innych odpowiedziach, możliwe jest skonstruowanie wymaganego zachowania / błędów przy użyciu różnych flag z os.OpenFile. W rzeczywistości os.Createjest to po prostu rozsądny skrót do robienia tego:

// 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)
}

Powinieneś sam połączyć te flagi, aby uzyskać interesujące Cię zachowanie:

// 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.
)

W zależności od tego, co wybierzesz, będziesz otrzymywać różne błędy.

Oto przykład, w którym chcę otworzyć plik do zapisu, ale obetnę istniejący plik tylko wtedy, gdy użytkownik powie, że jest OK:

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) 
   }
}
Sebastian N.
źródło
0

Najlepszy sposób sprawdzenia, czy plik istnieje:

if _, err := os.Stat("/path/to/file"); err == nil || os.IsExist(err) {
    // your code here if file exists
}
AlSan
źródło