Skip to content

Crear y limpiar un archivo temporal snippet

Crea un archivo de trabajo con nombre único, úsalo y luego bórralo — la columna vertebral de los patrones de guardado atómico, de entregar trabajo a programas externos y de evitar que los tests llenen el filesystem de basura.

Crea un archivo de trabajo con nombre único, úsalo y luego bórralo — la columna vertebral de los patrones de guardado atómico, de entregar trabajo a programas externos y de evitar que los tests llenen el filesystem de basura. Los dos modos de fallo clásicos son los nombres caseros (un PID o timestamp colisiona en cuanto dos procesos se solapan; las APIs de la familia mkstemp crean el archivo de forma exclusiva así que la carrera es imposible, tu sufijo no) y la limpieza que nunca se ejecuta (los hooks de delete-on-exit y el «el OS barre /tmp tarde o temprano» fugan mal en servidores longevos y contenedores con un tmpfs montado). Cada ejemplo empareja la creación con una eliminación que sobrevive al camino del error. SQL se omite: los lenguajes de consulta no exponen ninguna API de archivos temporales — el almacenamiento temporal es asunto del propio motor (tablespaces temp, tablas TEMPORARY) y la entrega de archivos corresponde al cliente.

Receta ejecutable · 14 lenguajes
Files & Datatemp-filescleanupfilestmpdir

Every language

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

JSJavaScript
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

// A private temp DIRECTORY, not a bare file: an exclusive collision-proof
// name plus room for whatever else the task needs next to it.
const dir = await mkdtemp(join(tmpdir(), 'app-'));
const file = join(dir, 'work.txt');

try {
  await writeFile(file, 'scratch data\n', 'utf8');
  console.log(file);
  // ...hand `file` to a child process, a test, a parser...
} finally {
  // force: no throw if creation half-failed; recursive: takes the dir too
  await rm(dir, { recursive: true, force: true });
}

mkdtemp appends six random characters to the prefix and creates the directory with mode 0700 — never build temp names from Date.now() or process.pid. rm with force:true makes cleanup idempotent; without it a failed writeFile lets the finally block throw ENOENT and mask the real error. Sync twin: fs.mkdtempSync.

TSTypeScript
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

/** Run `fn` with a fresh temp file path; always remove it afterwards. */
async function withTempFile(
  fn: (path: string) => Promise<void>,
): Promise<void> {
  const dir = await mkdtemp(join(tmpdir(), 'app-'));
  const path = join(dir, 'work.txt');
  try {
    await writeFile(path, 'scratch data\n', 'utf8');
    await fn(path);
  } finally {
    await rm(dir, { recursive: true, force: true });
  }
}

await withTempFile(async (path) => console.log(path));

The finally block is the whole point — it runs when fn rejects too, so the temp directory never survives a task that died mid-flight. TS 5.2+ on Node 20 offers `await using` (Symbol.asyncDispose) as the structural alternative to the callback wrapper.

GoGo
package main

import (
	"fmt"
	"os"
)

func main() {
	f, err := os.CreateTemp("", "app-*.txt") // "" = os.TempDir(), exclusive create
	if err != nil {
		panic(err)
	}
	path := f.Name()

	// LIFO: Close runs first, Remove second — required on Windows,
	// where an open file cannot be unlinked.
	defer os.Remove(path)
	defer f.Close()

	if _, err := f.WriteString("scratch data\n"); err != nil {
		panic(err)
	}
	fmt.Println(path)
}

os.CreateTemp replaced ioutil.TempFile; the * in the pattern marks where the random suffix lands (no * means append at the end). The ignored Remove error is deliberate best-effort — but treat the OS sweeping /tmp as a backstop, not the mechanism. os.MkdirTemp is the directory twin.

RsRust
use std::io::Write;

fn main() -> std::io::Result<()> {
    let mut file = tempfile::NamedTempFile::new()?;
    writeln!(file, "scratch data")?;
    println!("temp file: {}", file.path().display());

    // ...pass file.path() around — it is a real, visible path...

    Ok(())
} // `file` drops here: closed AND unlinked, even on panic-unwind

tempfile = "3" in Cargo.toml. Deletion is tied to Drop, so a ? early-return or a panicking thread still cleans up — exactly the property the manual languages rebuild with defer/finally. Call file.persist("final.txt") when the file must survive.

PHPPHP
<?php
// tmpfile(): exclusive name in the system temp dir + deletion
// registered for fclose() and script shutdown.
$fh = tmpfile();
if ($fh === false) {
    throw new RuntimeException('tmpfile() failed');
}

// It returns a stream, not a path — recover the path like this:
$path = stream_get_meta_data($fh)['uri'];

fwrite($fh, "scratch data\n");
fflush($fh);

// ...rewind($fh) to read back, or pass $path to an external command...

fclose($fh); // removes the file

The handle owns the file: fclose() (or script termination) deletes it, so there is no unlink to forget — and no way to keep the file except copying it. Need a path you control? tempnam(sys_get_temp_dir(), 'app-') creates an exclusive empty file you must unlink yourself.

PyPython
import tempfile

with tempfile.NamedTemporaryFile(
    mode="w+", encoding="utf-8", delete=True
) as f:
    f.write("scratch data\n")
    f.flush()            # contents now visible at f.name to other processes
    print(f.name)

# leaving the with-block closes AND unlinks the file

delete=True is the default: close() unlinks. Pass delete=False when the file must outlive the handle — cleanup then becomes your job. On Windows the name cannot be reopened by other code while the handle is open (Linux: it can). tempfile.TemporaryFile is the anonymous variant with no portable name.

CC
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    char path[] = "/tmp/app-XXXXXX";   /* template rewritten in place */

    int fd = mkstemp(path);            /* atomically: random name + O_EXCL open */
    if (fd == -1) {
        perror("mkstemp");
        return 1;
    }

    dprintf(fd, "scratch data\n");

    if (close(fd) == -1) {             /* flush before anyone reads it */
        perror("close");
    }
    unlink(path);                      /* must run on EVERY exit path */
    return 0;
}

mkstemp is the only race-free POSIX spelling — never tmpnam(), which hands back a name another process can create first. Real programs centralize close+unlink (or fdopen + fclose) in one helper so no error branch can skip the unlink. Windows CRT: _mktemp_s + _open(..., _O_CREAT | _O_EXCL).

C++C++
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unistd.h>

namespace fs = std::filesystem;

// RAII over mkstemp: destructor closes + unlinks on every exit path,
// including exceptions. C++ has no std temp-file facility — POSIX creates.
class TempFile {
public:
    explicit TempFile(const fs::path& pattern)
        : path_(pattern.string()) {
        fd_ = ::mkstemp(path_.data());        // rewrites the trailing XXXXXX
        if (fd_ == -1) throw std::runtime_error("mkstemp failed");
    }
    ~TempFile() {
        if (fd_ != -1) ::close(fd_);
        std::error_code ec;
        fs::remove(path_, ec);                // best-effort, never throws
    }
    TempFile(const TempFile&) = delete;
    TempFile& operator=(const TempFile&) = delete;

    void write(std::string_view bytes) {
        if (::write(fd_, bytes.data(), bytes.size()) == -1)
            throw std::runtime_error("write failed");
    }
    const std::string& path() const { return path_; }

private:
    std::string path_;
    int fd_ = -1;
};

int main() {
    TempFile t(fs::temp_directory_path() / "app-XXXXXX");
    t.write("scratch data\n");
    std::cout << t.path() << '\n';
    // ...pass t.path() to other code...
}   // closed + unlinked here

C++17+ (non-const std::string::data(), <filesystem>). POSIX only — MSVC needs _mktemp_s/_open or a UUID-named file. The deleted copy constructor is load-bearing: a copied handle would close and unlink twice.

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

// GetTempFileName(): creates a ZERO-BYTE file and returns its path —
// the platform guarantees the name is fresh.
string path = Path.GetTempFileName();
try
{
    File.WriteAllText(path, "scratch data");
    Console.WriteLine(path);
    // ...hand the path to another process or a test...
}
finally
{
    File.Delete(path); // throws if the file is already gone — wrap if that can happen
}

GetTempFileName cycles .tmp names and throws IOException once ~65k files sit in %TEMP% — long-running services prefer Path.Combine(Path.GetTempPath(), $"app-{Guid.NewGuid():N}.tmp") opened with FileShare.None. File.Delete on a missing path throws FileNotFoundException; catch it when failure paths may have removed the file already.

JvJava
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class CreateTempFile {
    public static void main(String[] args) throws IOException {
        Path tmp = Files.createTempFile("app-", ".txt"); // exclusive, in java.io.tmpdir
        try {
            Files.writeString(tmp, "scratch data\n");
            System.out.println(tmp);
            // ...pass tmp to other code...
        } finally {
            Files.deleteIfExists(tmp); // no throw if someone already removed it
        }
    }
}

Files.createTempFile(prefix, suffix, dir) — omit the dir and it honors java.io.tmpdir. File.deleteOnExit() is the leaky alternative: it only fires at JVM exit, so app servers that run for months accumulate files. The attributes overload sets POSIX perms when the content is sensitive.

SwSwift
import Foundation

let fm = FileManager.default
// No mkstemp equivalent in Foundation: assemble a unique name yourself.
let url = fm.temporaryDirectory
    .appendingPathComponent("app-\(UUID().uuidString).txt")

do {
    try "scratch data".write(to: url, atomically: true, encoding: .utf8)
    print(url.path)
    // ...work with the file...
} catch {
    fatalError("temp write failed: \(error)")
}

try? fm.removeItem(at: url) // idempotent; misses only a hard exit

UUID names make collisions vanish, but creation is still not exclusive — for hostile temp directories open the file with O_EXCL via Darwin/Glibc's open(2) instead. atomically: true writes a sibling file then renames: exactly the durable pattern. In real code, factor this into a function so defer { try? fm.removeItem(at: url) } also covers thrown errors.

KtKotlin
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.writeText

// Scoped resource: creation, your block, teardown — in that order.
// finally guarantees the unlink even when the block throws.
fun <R> withTempFile(
    prefix: String = "app-",
    suffix: String = ".txt",
    block: (Path) -> R,
): R {
    val tmp = Files.createTempFile(prefix, suffix)
    try {
        return block(tmp)
    } finally {
        Files.deleteIfExists(tmp)
    }
}

fun main() {
    withTempFile { tmp ->
        tmp.writeText("scratch data\n")
        println(tmp)
    }
}

Same JVM semantics as the Java impl: createTempFile is exclusive and honors java.io.tmpdir. kotlin.io.path supplies the writeText/deleteIfExists extension sugar (kotlin-stdlib jdk7 variant, the default on the JVM).

RbRuby
require 'tempfile'

# Block form: closed AND unlinked when the block ends — however it exits.
Tempfile.create('app-') do |f|
  f.write("scratch data\n")
  f.flush              # contents visible at f.path from here on
  puts f.path          # a real path — child processes can read it
end

# Tempfile.new is the manual spelling: close! unlinks at once, plain close
# leaves the file for a finalizer to remove later — too passive for servers.

Tempfile.create yields a Tempfile whose path is a real file in Dir.tmpdir; on Windows it stays locked until the block ends. Inside the block is the only safe window to use f.path — after it returns the file is gone.

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

pub fn main() !void {
    // No mkstemp in std: build a unique name from OS randomness.
    const name = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "/tmp/app-{d}.txt",
        .{std.crypto.random.int(u64)},
    );
    defer std.heap.page_allocator.free(name);

    {
        // .exclusive fails with error.PathAlreadyExists instead of
        // clobbering — with 2^64 names, retrying is academic.
        const f = try std.fs.createFileAbsolute(name, .{
            .exclusive = true,
            .truncate = false,
        });
        defer f.close();
        try f.writeAll("scratch data\n");
        std.debug.print("temp file: {s}\n", .{name});
    }

    std.fs.deleteFileAbsolute(name) catch |err| {
        std.debug.print("cleanup failed: {s}\n", .{@errorName(err)});
    };
}

std.crypto.random pulls from the OS CSPRNG. /tmp is a POSIX assumption — read std.posix.getenv("TMPDIR") in production. Cleanup here is manual and does NOT run when a try above fails mid-way; a real helper takes a block (like the Kotlin impl) so defer ordering covers every path. Zig 0.12+ API spelling.