Skip to content

Comprobar si existe un archivo o directorio snippet

Responde «¿está esta ruta?» antes de tocar configuración, cachés o archivos de lock — y trata la respuesta como una pista, no una promesa.

Responde «¿está esta ruta?» antes de tocar configuración, cachés o archivos de lock — y trata la respuesta como una pista, no una promesa. Todas las APIs mainstream siguen los symlinks, así que un enlace roto se lee como ausente, y casi todas pliegan el «no puedo saberlo» (permiso denegado en un padre) en un simple false, borrando la distinción entre inexistente e ilegible; cuando eso importa, ramifica en tres: existe / seguro que no / indeterminado. La trampa más profunda es TOCTOU — la entrada puede desaparecer entre la comprobación y el uso, así que para flujos de abrir-si-existe el patrón robusto es intentar la operación y capturar el error de not-found. El access(2) de C añade su propia trampa: prueba con el UID real, no con el efectivo, así que los binarios setuid obtienen respuestas equivocadas. SQL se omite — un motor consulta su propio catálogo y no el sistema de archivos del cliente, y las sondas del lado del servidor como pg_stat_file de Postgres son solo para superusuarios y describen el disco del host de la base de datos.

Receta ejecutable · 14 lenguajes
Files & Datafilesystemexistence-checksymlinksio

Every language

14 lenguajes, 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.