Leer y Escribir Archivos en Python, Node.js y Bash
Cómo leer y escribir archivos de forma segura en Python, Node.js, Bash y Java, con escritura atómica y streaming.
Visión General
Pocas tareas de E/S aparecen tan seguido como leer y escribir archivos: cargar configuración, procesar logs, exportar informes, persistir estado. Hacerlo de forma segura implica manejar bien la codificación y cerrar siempre el descriptor del archivo.
Los archivos son la interfaz común entre un programa y el almacenamiento persistente. Guardar preferencias, revisar logs del servidor, exportar un CSV: las reglas no cambian — abrir el archivo, hacer el trabajo y liberar el descriptor aunque algo falle. Los runtimes actuales se encargan del búfer, la codificación y la limpieza por ti, pero saber qué pasa por debajo te sirve cuando persigues una lectura lenta o un archivo corrupto.
Abajo encontrarás la lectura/escritura idiomática para archivos de texto en Python, Node.js y Bash, más el patrón de streaming para archivos demasiado grandes para la memoria.
Cuándo Usar
Saca 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.
No uses esta receta cuando el archivo es demasiado grande para el búfer o necesitas un pipeline de transformación continuo: ese caso lo cubre Stream Processing.
Solución
Python
En Python, with envuelve el archivo en un gestor de contexto: el descriptor se cierra solo aunque el bloque lance una excepción. Pasa 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
Bajo node:fs/promises, Node.js trae una API basada en promesas que nunca bloquea el event loop: innegociable para un servidor que atiende 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
En Bash, el trabajo con archivos pasa por la redirección de shell. El operador > sobrescribe el archivo destino y >> añade al final: la opción más rápida para escrituras pequeñas dentro de scripts.
# Escribir (sobrescribir) y añadir
echo "Hola, archivo!" > notes.txt
echo "Otra línea" >> notes.txt
# Leer
cat notes.txt
Explicación
- Python se apoya en la sentencia
with(un gestor de contexto) para que el archivo se cierre solo aunque algo falle.open()recibe una cadena de modo:"r"lee,"w"escribe y trunca,"a"añade,"x"crea solo si el archivo no está. Pasa siempreencoding="utf-8". - JavaScript:
fs/promiseshace el trabajo pesado. Las síncronasreadFileSync/writeFileSynccongelan el event loop, así que mantenlas fuera de los manejadores de peticiones. Para archivos grandes,createReadStream()procesa los datos en bloques. - Bash trabaja con redirección:
>sobrescribe,>>añade al final ycatimprime el contenido. Para análisis estructurado, pasa la salida decatporjqpara JSON oawkpara CSV.
Las escrituras atómicas siguen una secuencia fija que protege a los lectores de archivos a medio escribir:
Para convertir el contenido de un archivo en datos estructurados, consulta Parsear JSON.
Errores que te vas a encontrar
| Error | De dónde viene | Solución |
|---|---|---|
FileNotFoundError / ENOENT | ruta ausente o un typo | comprueba pathlib.Path.exists() / fs.access() primero |
PermissionError / EACCES | archivo de solo lectura o falta el bit de ejecución en un directorio padre | arregla permisos (chmod); reintentar no sirve |
IsADirectoryError / EISDIR | abriste un directorio como si fuera archivo | verifica path.is_file() antes de abrir |
UnicodeDecodeError | el archivo no es UTF-8 | detecta la codificación real (file -i, chardet) o pásala explícita |
ENOSPC | disco lleno a mitad de escritura | la escritura atómica mantiene el original intacto; haz visible el error y alerta |
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 corrompen caracteres no ASCII en Windows o macOS. - Usa gestores de contexto / APIs async:
withen Python,fs/promisesen Node, para evitar fugas de descriptores y bloqueos del event loop. La limpieza corre igual si una excepción golpea a mitad del bloque. - Comprueba que la ruta existe: un archivo ausente debe degradar con elegancia, no tumbar el proceso. En Python,
pathlib.Path.exists()te lo dice por adelantado; en Node,fs.access()ofs.stat()hacen el trabajo. - Procesa archivos grandes en streaming: no cargues gigabytes en memoria cuando una línea cada vez basta. Python te da
for line in f, Node tienereadlineocreateReadStream, y Bash tienewhile read line. - Escribe de forma atómica: deja los bytes en un archivo temporal y luego renombra; un fallo a mitad de escritura deja el original intacto.
- Usa rutas absolutas en scripts: las rutas relativas se rompen en cuanto cambia el directorio de trabajo. Resuélvelas a forma absoluta —
pathliben Python,path.resolve()en Node — antes del primeropen(). - Establece permisos restrictivos en archivos sensibles: ¿secretos en un archivo de configuración? Ciérralo a solo su propietario (
chmod 600).
Errores Comunes
- Olvidar cerrar el descriptor: provoca fugas de descriptores y acaba agotando el límite del proceso; usa siempre
withotry/finally. - Bloquear el event loop en Node: no pongas
readFileSyncdentro de manejadores de peticiones; una sola lectura síncrona congela todo el servidor para todos los usuarios concurrentes. - Codificación incorrecta: lee UTF-8 como ASCII y el texto no inglés se vuelve mojibake en tus logs y en la UI.
- Sobrescribir con
>: usar>en lugar de>>en Bash borra el archivo en silencio, sin deshacer ni confirmación. - Ignorar errores: un archivo ausente o un fallo de permisos merece un manejador de verdad, no un
catchvacío. Regístralo y falla de forma controlada. - Leer archivos completos en memoria: cargar un archivo de logs de 10 GB en una cadena tumbará tu proceso. Comprueba primero el tamaño del archivo, o procesa en streaming cualquier cosa de más de unos megabytes.
- Escribir en el mismo archivo que estás leyendo: sobrescribe tu entrada sobre la marcha y puedes truncarla a mitad de lectura: datos perdidos.
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 un valor por defecto para archivos ausentes."""
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 las líneas del archivo, descartando vacías y saltos finales."""
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: lectura/escritura en streaming 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 valor por defecto
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
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);
}
// Procesar 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, dar un valor por defecto
safe_read() {
local file="$1"
local default="${2:-}"
if [[ -f "$file" && -r "$file" ]]; then
cat "$file"
else
printf '%s' "$default"
fi
}
# Añadir con timestamp (para registro)
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 secretos)
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"
Ver También
open()de Python y el móduloio: modos, búfer y texto vs binario.- La documentación de la API
fs/promises: cada operación de archivo async que trae Node.js, amiga del event loop. - Redirección en Bash: la referencia completa de
>,>>y here-docs. - La API NIO
Files:Files,Pathsy la opciónATOMIC_MOVEusada en el ejemplo de Java.
Código complementario: utilidades ejecutables de lectura/escritura segura para Python, Node.js, Java y Bash están en el repo complementario.
Preguntas frecuentes
¿Cómo añado contenido en vez de sobrescribir?
Usa el modo append: open(p, "a") en Python, appendFile en Node, >> en Bash. Lo que ya estaba queda donde estaba; los datos nuevos van al final.
¿Por qué debo evitar readFileSync en Node.js?
Congela el event loop de un solo hilo, así que todas las demás peticiones esperan a que termine la lectura. Usa fs/promises en cualquier código de servidor en producción.
¿Cómo leo un archivo grande sin quedarme sin memoria?
Procésalo línea a línea: for line in f en Python, createReadStream en Node, while read line en Bash. El uso de memoria se mantiene plano, tanto si el archivo pesa 10 KB como 10 GB.
¿Cómo escribo de forma segura en un archivo que otros procesos podrían estar leyendo?
Escribe en un archivo temporal dentro del mismo sistema de archivos y luego renómbralo de forma atómica sobre el destino. Los lectores reciben el archivo viejo completo o el nuevo completo, nunca uno a medio escribir.
¿Cuál es la diferencia entre modo texto y modo binario?
El modo texto aplica traducción de saltos de línea según la plataforma (\r\n en Windows) y decodificación. El modo binario lee los bytes en crudo, sin tocarlos. Usa modo binario con imágenes, archivos comprimidos o cualquier cosa que necesite fidelidad byte a byte.
¿Cómo bloqueo un archivo para evitar escrituras concurrentes?
No hay lock portable. POSIX te da fcntl.flock en Python y Windows tiene msvcrt.locking, pero Node.js no trae lock nativo. El patrón habitual ahí es un lockfile atómico: fs.open(p, 'wx') falla cuando el archivo ya existe, así que el propio lockfile actúa de mutex. Librerías como proper-lockfile lo encapsulan.
Recursos Relacionados
Parsear JSON en Python, JavaScript y Java
Parsea cadenas JSON a objetos nativos en Python, JavaScript y Java — con manejo de errores, streaming para archivos grandes y validación de esquema.
RecipeLlamar 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.
RecipeProcesar Archivos Grandes con Streams
Cómo leer, transformar y escribir archivos grandes eficientemente usando streams sin cargar archivos completos en memoria en Python, Node.js y Java.
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.
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