Leer y Escribir Archivos
Cómo leer y escribir archivos de forma segura en varios lenguajes de programación.
Visión General
Leer y escribir archivos es una de las tareas de E/S más habituales: cargar configuración, procesar logs, exportar informes o persistir estado. Hacerlo de forma segura implica manejar bien la codificación y cerrar siempre el descriptor del archivo.
Los archivos son la interfaz universal entre programas y almacenamiento persistente. Ya sea que estés guardando preferencias de usuario, leyendo logs del servidor o generando una exportación CSV, los mismos principios aplican: abrir el archivo, realizar la operación y asegurar que el recurso se libere incluso cuando ocurren errores. Los runtimes modernos proporcionan abstracciones de alto nivel que manejan buffering, codificación y limpieza automáticamente, pero entender la mecánica subyacente te ayuda a depurar problemas de rendimiento y evitar corrupción de datos.
Aqui se muestra la forma de la forma idiomática de leer y escribir archivos de texto en Python, JavaScript (Node.js) y Bash, además de cómo hacer streaming de archivos grandes sin agotar la memoria.
Cuándo Usar
Usa esta receta cuando:
- Cargas archivos de configuración o datos al arrancar. Consulta Parse JSON para archivos de config estructurados.
- Generas informes, exportaciones o logs para auditoría y análisis
- Procesas texto línea a línea (CSV, logs, fixtures)
- Persistes pequeñas cantidades de estado sin una base de datos
- Lees y escribes archivos de configuración JSON o YAML
- Haces streaming de archivos de logs grandes sin cargarlos completamente en memoria
- Creas archivos temporales para procesamiento intermedio en pipelines de datos. Consulta Call REST API para descargar datos remotos.
Solución
Python
La sentencia with de Python crea un context manager que cierra automáticamente el archivo, incluso si se levanta una excepción dentro del bloque. Especifica siempre encoding="utf-8" para evitar valores por defecto dependientes de la plataforma.
# Escribir
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("Hola, archivo!\n")
# Leer
with open("notes.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
JavaScript
Node.js proporciona una API basada en promesas bajo node:fs/promises que evita bloquear el event loop. Esto es esencial para aplicaciones de servidor que manejan peticiones concurrentes.
import { readFile, writeFile } from "node:fs/promises";
await writeFile("notes.txt", "Hola, archivo!\n", "utf-8");
const content = await readFile("notes.txt", "utf-8");
console.log(content);
Bash
Bash usa redirección de shell para operaciones de archivo. El operador > sobrescribe el archivo destino, mientras que >> añade. Estas son las formas más rápidas de escribir pequeñas cantidades de datos desde scripts.
# Escribir (sobrescribir) y añadir
echo "Hola, archivo!" > notes.txt
echo "Otra línea" >> notes.txt
# Leer
cat notes.txt
Explicación
- Python usa la sentencia
with(context manager) para que el archivo se cierre siempre, incluso ante un error. La funciónopen()acepta un string de modo:"r"para lectura,"w"para escritura (truncar),"a"para append, y"x"para creación exclusiva. Especifica siempreencoding="utf-8". - JavaScript usa la API basada en promesas
fs/promises. Prefiérela sobre las síncronasreadFileSync/writeFileSync, que bloquean el event loop. Para archivos grandes, usacreateReadStream()para procesar datos en chunks. - Bash usa redirección:
>sobrescribe,>>añade.catimprime el contenido. Para parsing estructurado, combinacatconjqpara JSON oawkpara CSV.
Para convertir el contenido de un archivo en datos estructurados, consulta Parsear JSON.
Variantes
| Lenguaje | Leer | Escribir | Añadir |
|---|---|---|---|
| Python | open(p).read() | open(p, "w") | open(p, "a") |
| JavaScript | readFile(p) | writeFile(p, data) | appendFile(p, data) |
| Bash | cat p | > p | >> p |
Lo que funciona
- Define siempre la codificación: un
utf-8explícito evita valores por defecto dependientes de la plataforma que pueden corromper caracteres no-ASCII en Windows o macOS. - Usa context managers / APIs async:
withen Python,fs/promisesen Node, para evitar fugas de descriptores y bloqueos del event loop. Estas abstracciones garantizan limpieza incluso cuando ocurren excepciones. - Comprueba que la ruta existe: Path. access()
ofs. stat()`. - Procesa archivos grandes en streaming: lee línea a línea en vez de cargar gigabytes en memoria. Python proporciona
for line in f; Node proporcionareadlineocreateReadStream; Bash proporcionawhile read line. - Escribe de forma atómica: escribe en un archivo temporal y luego renómbralo, para no corromper datos ante un fallo. Si el proceso muere durante la escritura, el archivo original permanece intacto.
- Usa rutas absolutas en scripts: las rutas relativas se rompen cuando cambia el directorio de trabajo. Resuelve rutas con
pathlib(Python) opath. resolve()(Node) antes de abrir archivos. - Establece permisos restrictivos en archivos sensibles: los archivos de configuración que contienen secrets deben ser legibles solo por el owner (
chmod 600).
Errores Comunes
- Olvidar cerrar el descriptor: provoca fugas de descriptores y eventualmente agota el límite del proceso; usa siempre
withotry/finally. - Bloquear el event loop en Node: Una sola lectura síncrona puede congelar todo tu servidor para todos los usuarios concurrentes.
- Codificación incorrecta: leer UTF-8 como ASCII corrompe los caracteres no ingleses y puede producir mojibake en logs o output orientado al usuario.
- Sobrescribir con
>: usar>en lugar de>>en Bash borra el archivo en silencio sin undo ni confirmación. - Ignorar errores: un archivo ausente o un error de permisos debe manejarse, no tragarse con un catch vacío. Registra el error y falla con elegancia.
- Leer archivos completos en memoria: cargar un archivo de logs de 10 GB en un string hará crash tu proceso.
- Escribir en el mismo archivo que estás leyendo: sobrescribir un archivo de entrada in-place puede truncarlo antes de que termines de leer, resultando en pérdida de datos.
Soluciones Avanzadas
Python: Escritura atómica con pathlib y manejo de errores
import os
import tempfile
from pathlib import Path
from typing import Any
def safe_write(path: str | Path, data: str, encoding: str = 'utf-8') -> None:
"""Escribe texto atómicamente: archivo temporal + renombrado. Seguro ante crashes."""
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(
dir=path.parent, suffix='.tmp', prefix=path.name
)
try:
with os.fdopen(fd, 'w', encoding=encoding) as f:
f.write(data)
f.flush()
os.fsync(f.fileno())
os.replace(tmp_path, path)
except Exception:
Path(tmp_path).unlink(missing_ok=True)
raise
def safe_read(path: str | Path, encoding: str = 'utf-8',
default: str | None = None) -> str | None:
"""Lee texto con fallback elegante para archivos faltantes."""
path = Path(path)
if not path.exists():
return default
try:
return path.read_text(encoding=encoding)
except PermissionError:
raise PermissionError(f"No se puede leer {path}: permiso denegado")
except UnicodeDecodeError as e:
raise UnicodeDecodeError(
e.encoding, e.object, e.start, e.end,
f"El archivo {path} no es {encoding} válido"
)
def read_lines_lazy(path: str | Path, encoding: str = 'utf-8') -> list[str]:
"""Lee líneas del archivo de forma lazy, stripping whitespace de cada línea."""
path = Path(path)
with path.open('r', encoding=encoding) as f:
return [line.rstrip('\n\r') for line in f if line.strip()]
def write_json_atomic(path: str | Path, data: Any, indent: int = 2) -> None:
"""Serializa JSON y escribe atómicamente."""
import json
text = json.dumps(data, indent=indent, ensure_ascii=False, default=str)
safe_write(path, text)
# Uso
# safe_write('/etc/app/config.yaml', 'key: value\n')
# content = safe_read('/etc/app/config.yaml', default='key: default\n')
# write_json_atomic('/data/state.json', {'users': 42, 'active': 10})
Node.js: Streaming read/write con recuperación de errores
const fs = require('fs');
const { pipeline } = require('stream');
const { promisify } = require('util');
const { createReadStream, createWriteStream } = fs;
const pipe = promisify(pipeline);
async function streamFile(srcPath, destPath, transformFn) {
const tmpPath = destPath + '.tmp';
const readStream = createReadStream(srcPath, { encoding: 'utf-8' });
const writeStream = createWriteStream(tmpPath, { encoding: 'utf-8' });
let lineBuffer = '';
const lineTransform = new (require('stream').Transform)({
transform(chunk, encoding, callback) {
lineBuffer += chunk;
const lines = lineBuffer.split('\n');
lineBuffer = lines.pop();
for (const line of lines) {
const result = transformFn(line);
if (result !== null) this.push(result + '\n');
}
callback();
},
flush(callback) {
if (lineBuffer) {
const result = transformFn(lineBuffer);
if (result !== null) this.push(result + '\n');
}
callback();
},
});
try {
await pipe(readStream, lineTransform, writeStream);
await fs.promises.rename(tmpPath, destPath);
} catch (err) {
try { await fs.promises.unlink(tmpPath); } catch {}
throw err;
}
}
async function readLines(path) {
const content = await fs.promises.readFile(path, 'utf-8');
return content.split('\n').filter(l => l.trim());
}
async function appendLine(path, line) {
await fs.promises.appendFile(path, line + '\n', 'utf-8');
}
// Uso
// streamFile('input.log', 'output.log', line => line.toUpperCase());
// const lines = await readLines('config.txt');
// await appendLine('app.log', `[${new Date().toISOString()}] Iniciado`);
Java: Operaciones de archivo NIO con escritura atómica
import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.ArrayList;
public class FileOps {
// Escritura atómica: archivo temporal + Files.move con ATOMIC_MOVE
public static void atomicWrite(Path path, String content) throws IOException {
Path parent = path.getParent();
if (parent != null) Files.createDirectories(parent);
Path tmp = Files.createTempFile(parent, path.getFileName().toString(), ".tmp");
try {
Files.writeString(tmp, content, StandardCharsets.UTF_8);
Files.move(tmp, path, StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
Files.deleteIfExists(tmp);
throw e;
}
}
// Lectura segura con fallback
public static String safeRead(Path path, String defaultValue) {
if (!Files.exists(path)) return defaultValue;
try {
return Files.readString(path, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException("Error al leer " + path, e);
}
}
// Leer todas las líneas de forma lazy
public static List<String> readLines(Path path) throws IOException {
return Files.readAllLines(path, StandardCharsets.UTF_8);
}
// Añadir una línea
public static void appendLine(Path path, String line) throws IOException {
String entry = line + System.lineSeparator();
Files.writeString(path, entry,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
}
// Stream líneas con try-with-resources
public static void processLines(Path path, LineHandler handler) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
handler.handle(line);
}
}
}
@FunctionalInterface
public interface LineHandler {
void handle(String line) throws IOException;
}
}
// Uso
// FileOps.atomicWrite(Path.of("/etc/app/config.yaml"), "key: value\n");
// String config = FileOps.safeRead(Path.of("config.yaml"), "key: default\n");
// FileOps.processLines(Path.of("large.log"), line -> {
// if (line.contains("ERROR")) System.err.println(line);
// });
Bash: Operaciones de archivo seguras con verificación de errores
#!/usr/bin/env bash
set -euo pipefail
# Escritura segura: escribir a archivo temporal, luego renombrado atómico
safe_write() {
local file="$1"
local content="$2"
local tmp="${file}.tmp.$$"
local dir
dir="$(dirname "$file")"
mkdir -p "$dir"
printf '%s' "$content" > "$tmp"
mv "$tmp" "$file"
}
# Lectura segura: verificar existencia primero, proporcionar default
safe_read() {
local file="$1"
local default="${2:-}"
if [[ -f "$file" && -r "$file" ]]; then
cat "$file"
else
printf '%s' "$default"
fi
}
# Append con timestamp (para logging)
log_append() {
local file="$1"
local message="$2"
local timestamp
timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf '[%s] %s\n' "$timestamp" "$message" >> "$file"
}
# Leer archivo línea por línea con manejo de errores
read_lines() {
local file="$1"
if [[ ! -f "$file" ]]; then
echo "Error: $file no encontrado" >&2
return 1
fi
while IFS= read -r line || [[ -n "$line" ]]; do
echo "$line"
done < "$file"
}
# Crear archivo con permisos restrictivos (para secrets)
create_secret_file() {
local file="$1"
local content="$2"
# Crear con permisos 600 directamente
(umask 077; printf '%s' "$content" > "$file")
echo "Creado $file con permisos 600"
}
# Uso
# safe_write /etc/app/config.txt "key=value"
# content=$(safe_read /etc/app/config.txt "key=default")
# log_append /var/log/app.log "Aplicación iniciada"
# create_secret_file /etc/app/secret.key "my-secret-key-123" Preguntas frecuentes
¿Cómo añado contenido en vez de sobrescribir?
Abre en modo append: open(p, "a") en Python, appendFile en Node, o >> en Bash. Esto preserva el contenido existente y agrega nuevos datos al final.
¿Por qué debo evitar readFileSync en Node.js?
Bloquea el event loop de un solo hilo, congelando el resto de peticiones hasta que termine la lectura. Usa fs/promises en su lugar para cualquier código de servidor en producción.
¿Cómo leo un archivo grande sin quedarme sin memoria?
Prosésalo línea a línea: for line in f en Python, createReadStream en Node, o while read line en Bash. Esto mantiene el uso de memoria constante independientemente del tamaño del archivo.
¿Cómo escribo de forma segura en un archivo que otros procesos podrían estar leyendo?
Escribe en un archivo temporal en el mismo filesystem, luego renómbralo atómicamente sobre el destino. Los lectores verán o el archivo viejo completo o el nuevo completo, nunca uno parcialmente escrito.
¿Cuál es la diferencia entre modo texto y modo binario?
El modo texto aplica traducción de newlines específica de la plataforma (\r\n en Windows) y codificación. El modo binario lee bytes raw sin transformación. Usa modo binario para imágenes, archivos comprimidos, o cuando necesitas fidelidad byte por byte exacta.
Recursos Relacionados
Llamar a una API REST: Python, JS, Java y Go
Cómo hacer peticiones HTTP a una API REST y manejar la respuesta JSON en Python, JavaScript, Java y Go.
RecipeParsear JSON
Cómo parsear cadenas JSON a estructuras de datos nativas en varios lenguajes de programación.
RecipeExpresiones Regulares
Cómo usar expresiones regulares para matching de patrones, validación y extracción de texto en Python, JavaScript y Java.
RecipeScripting en Bash para Automatizacion DevOps y Tareas de
Como escribir scripts Bash robustos para automatizar despliegues, monitoreo de sistemas, rotacion de logs y tareas de mantenimiento rutinarias
RecipeExportar Datos a CSV y Excel
Cómo exportar datos estructurados a archivos CSV y Excel de forma eficiente.
RecipeValidación de Subida de Archivos
Cómo manejar subidas de archivos de forma segura con validación de tamaño, tipo y contenido.