Skip to content

Prüfen, ob eine Datei oder ein Verzeichnis existiert snippet

Die Frage „existiert dieser Pfad?“ beantworten, bevor Config-, Cache- oder Lock-Dateien angefasst werden — und die Antwort als Hinweis behandeln, nicht als Versprechen.

Die Frage „existiert dieser Pfad?“ beantworten, bevor Config-, Cache- oder Lock-Dateien angefasst werden — und die Antwort als Hinweis behandeln, nicht als Versprechen. Jede Mainstream-API folgt Symlinks, daher liest sich ein toter Link als abwesend, und fast alle falten „kann nicht feststellen“ (Permission denied auf einem Elternverzeichnis) zu einem schlichten false und löschen damit den Unterschied zwischen fehlt und unlesbar; wenn es darauf ankommt, dreifach verzweigen: existiert / definitiv nicht / unbestimmt. Die tiefere Falle ist TOCTOU — der Eintrag kann zwischen Prüfung und Nutzung verschwinden, daher ist für Open-if-present-Flows das robuste Muster, die Operation zu versuchen und den Not-found-Fehler abzufangen. In C hat access(2) eine eigene Falle: Sie prüft die reale UID, nicht die effektive, sodass setuid-Binaries falsche Antworten bekommen. SQL ist ausgelassen — eine Engine befragt ihren eigenen Katalog statt des Client-Dateisystems, und serverseitige Sonden wie Postgres' pg_stat_file sind Superuser-only und beschreiben die Platte des Datenbank-Hosts.

Runnable recipe · 14 languages
Files & Datafilesystemexistence-checksymlinksio

Every language

14 languages, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
import { access, constants } from 'node:fs/promises';

async function exists(path) {
  try {
    await access(path, constants.F_OK);
    return true;
  } catch {
    return false;
  }
}

console.log(await exists('package.json') ? 'exists' : 'missing');

Node has no boolean-returning exists in the promises API — the long-deprecated callback fs.exists aside, access-plus-catch is the blessed pattern (fs.existsSync is fine in sync scripts). access also conflates EACCES with ENOENT: match err.code if you need to distinguish.

TSTypeScript
import { stat } from 'node:fs/promises';

async function describe(path: string): Promise<'file' | 'directory' | 'missing'> {
  try {
    const s = await stat(path);
    return s.isDirectory() ? 'directory' : 'file';
  } catch (err) {
    if ((err as NodeJS.ErrnoException).code === 'ENOENT') return 'missing';
    throw err;
  }
}

console.log(await describe('package.json'));

Distinguishing ENOENT from EACCES is the whole point — permission failures rethrow instead of masquerading as "missing", and one stat call answers both existence and type.

GoGo
package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	if _, err := os.Stat("go.mod"); err == nil {
		fmt.Println("exists")
	} else if errors.Is(err, os.ErrNotExist) {
		fmt.Println("missing")
	} else {
		panic(err) // permission denied on a parent, etc.
	}
}

os.Stat follows symlinks — use os.Lstat to test the link itself, which is how a broken symlink reads "missing" under Stat but "exists" under Lstat. The three-way branch keeps ErrNotExist distinct from other errors; errors.Is works across wrapped errors and os.IsNotExist(err) remains equivalent for unwrapped ones.

RsRust
use std::path::Path;

fn main() {
    let path = Path::new("Cargo.toml");

    match path.try_exists() {
        Ok(true) => println!("exists"),
        Ok(false) => println!("missing"),
        Err(e) => panic!("cannot tell: {e}"),
    }

    println!("is a directory: {}", path.is_dir());
}

exists() — and is_dir/is_file — swallow IO errors and report false; try_exists (Rust 1.70+) is the one that surfaces the absent-vs-unreadable distinction as a Result. All of them follow symlinks.

PHPPHP
<?php
$path = 'composer.json';

var_dump(file_exists($path));
var_dump(is_file($path));
var_dump(is_dir(__DIR__));

file_exists follows symlinks, so a broken link reports false — is_link sees the link itself. PHP caches stat results per path for the request: after unlink/rename, call clearstatcache() or you read stale answers. is_file and is_dir return false for the opposite type, so a directory named composer.json makes is_file false while file_exists stays true.

PyPython
from pathlib import Path

path = Path('pyproject.toml')

if path.exists():
    print('directory' if path.is_dir() else 'file')
else:
    print('missing')

exists() maps every OSError — permission errors included — to False, same as os.path.exists; catch explicitly when missing-vs-unreadable matters. is_symlink() detects the link itself, so a broken symlink can be "not exists but is_symlink". Pythonic alternative for open-if-present: try the open() and catch FileNotFoundError instead of check-then-open.

CC
#include <stdio.h>
#include <sys/stat.h>

int main(void) {
    struct stat st;
    if (stat("Makefile", &st) == 0) {
        puts(S_ISDIR(st.st_mode) ? "directory" : "file");
    } else {
        perror("stat");
    }
    return 0;
}

stat follows symlinks; lstat reports the link itself, which is how a broken link is detected. The access(2) shortcut has a famous trap — it tests the real UID/GID, not the effective one, so setuid programs get wrong answers. When the goal is really "open it if possible", just call open(2) and read errno (ENOENT vs EACCES). POSIX; Windows uses _stat.

C++C++
#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

int main() {
    const fs::path p = "CMakeLists.txt";

    std::error_code ec;
    const bool ok = fs::exists(p, ec);
    std::cout << "exists: " << std::boolalpha << ok << '\n';
    if (ec) {
        std::cerr << "cannot tell: " << ec.message() << '\n';
    }

    std::cout << "is directory: " << fs::is_directory(p) << '\n';
}

C++17. The error_code overload reports failure through ec (and returns false) instead of throwing fs::filesystem_error like the plain overload — so check ec before trusting a false. is_directory has the same two-overload split; symlinks are followed.

C#C#
using System;
using System.IO;

if (File.Exists("app.config"))
    Console.WriteLine("app.config is a file");

if (Directory.Exists("bin"))
    Console.WriteLine("bin is a directory");

Both methods swallow exceptions and return false — a locked or unreadable path is indistinguishable from an absent one, and each returns false for the other's type (File.Exists on a directory is false). .NET 7 adds Path.Exists, covering either kind in one call.

JvJava
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;

public class Exists {
    public static void main(String[] args) {
        Path path = Path.of("pom.xml");

        System.out.println(Files.exists(path) ? "exists" : "missing");
        System.out.println(Files.isDirectory(path) ? "directory" : "file");
        System.out.println("link itself: " + Files.exists(path, LinkOption.NOFOLLOW_LINKS));
    }
}

Files.exists returns false when it cannot determine the answer (access denied) rather than throwing. The NOFOLLOW_LINKS variant is the lstat-style check — a broken symlink is false followed but true unfollowed. Path.of needs Java 11+; java.io.File.exists() is the legacy equivalent with no link options.

SwSwift
import Foundation

let fm = FileManager.default

var isDirectory: ObjCBool = false
if fm.fileExists(atPath: "Package.swift", isDirectory: &isDirectory) {
    print(isDirectory.boolValue ? "directory" : "file")
} else {
    print("missing")
}

The UnsafeMutablePointer<ObjCBool> out-parameter is an Objective-C carryover, but it is the only way to learn the type in the same syscall. fileExists follows symlinks, so a broken link reports false. For racing-safe checks, URL.checkResourceIsReachable() throws instead of returning a bare bool.

KtKotlin
import java.io.File
import java.nio.file.Files
import java.nio.file.LinkOption

fun main() {
    val file = File("build.gradle.kts")

    println(if (file.exists()) "exists" else "missing")
    println(if (file.isDirectory) "directory" else "file")

    val unfollowed = Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS)
    println("link itself: $unfollowed")
}

File.exists() swallows IOException and returns false — the same absent-vs-unreadable conflation as Java's Files.exists. The NIO Files.exists variant is the one that takes link options; file.toPath() bridges the two APIs.

RbRuby
path = 'Gemfile'

puts File.exist?(path) ? 'exists' : 'missing'
puts File.directory?(path) ? 'directory' : 'file'
puts File.symlink?(path) ? 'symlink' : 'not a symlink'

File.exist? follows symlinks — a broken link reads as missing, while File.lstat (or File.symlink?) sees the link itself. The File.exists? alias was removed in Ruby 3.2; the question-mark spelling is the one that survives. Check-then-open still races: rescue Errno::ENOENT around the open instead.

ZigZig
const std = @import("std");

fn exists(path: []const u8) bool {
    std.fs.cwd().access(path, .{}) catch return false;
    return true;
}

pub fn main() void {
    std.debug.print("exists: {}\n", .{exists("build.zig")});
}

std.fs.Dir.access is the cheap probe (faccessat under the hood) but folds every error into false — match on error.FileNotFound when missing-vs-unreadable matters. std.fs.cwd().statFile(path) costs a full stat and returns Stat.kind (.file, .directory, .sym_link) when the type is needed too.