Los programas de terminal suelen necesitar procesar varios archivos como si fueran un único flujo continuo. También es común leer la entrada estándar cuando no se proporcionan rutas. El módulo fileinput en Python resuelve ambos casos con un solo iterador: obtiene nombres desde sys.argv, usa sys.stdin cuando la lista está vacía y expone información como archivo actual, número acumulado de línea y posición dentro de cada archivo.
En esta guía aprenderás a usar fileinput.input(), la clase FileInput, hooks para archivos comprimidos, encodings explícitos, stdin, reescritura in-place, backups y tratamiento seguro de errores. El contenido complementa nuestras guías sobre pathlib, tempfile, shlex, filecmp y linecache.
Cuándo usar fileinput
El módulo es adecuado para filtros que aplican la misma operación línea por línea a cero, uno o varios archivos.
import fileinput
for linea in fileinput.input(encoding="utf-8"):
procesar(linea)Por defecto, los nombres vienen de sys.argv[1:]. Si la lista está vacía, la entrada viene de sys.stdin. El mismo script funciona así con rutas, pipes y redirecciones.
Ejemplos en terminal
python analizar.py app.log api.log
cat app.log | python analizar.pyEl primer comando recorre dos archivos. El segundo consume el pipe. Un nombre igual a - también representa stdin.
Proporcionar archivos explícitamente
El código reutilizable no debería depender de los argumentos del proceso cuando ya dispone de una lista de rutas.
archivos = ["enero.csv", "febrero.csv"]
with fileinput.input(files=archivos, encoding="utf-8") as entrada:
for linea in entrada:
procesar(linea)El contexto cierra el archivo actual y la secuencia incluso cuando ocurre una excepción.
Un único nombre también es válido
with fileinput.input(files="datos.txt", encoding="utf-8") as entrada:
for linea in entrada:
print(linea, end="")Usa lista o tupla cuando la colección se construya dinámicamente.
Los saltos se conservan
Las líneas devueltas mantienen el salto final cuando existe. La última línea de un archivo puede no tenerlo.
for linea in fileinput.input(files="datos.txt", encoding="utf-8"):
print(repr(linea))No apliques strip() sin pensar porque también elimina espacios y tabulaciones significativos. Para quitar solo terminadores usa una política explícita como rstrip("\r\n").
Nombre del archivo actual
fileinput.filename() devuelve la fuente de la última línea leída.
for linea in fileinput.input(files=archivos, encoding="utf-8"):
print(fileinput.filename(), linea, end="")Antes de la primera línea devuelve None. Las funciones globales dependen de una instancia activa creada por fileinput.input().
Número acumulado
lineno() cuenta todas las líneas desde el inicio de la secuencia.
for linea in fileinput.input(files=archivos, encoding="utf-8"):
print(fileinput.lineno(), linea, end="")Si el primer archivo tiene 100 líneas, la primera del segundo recibe el número acumulado 101.
Número dentro del archivo
filelineno() reinicia la cuenta en cada fuente.
for linea in fileinput.input(files=archivos, encoding="utf-8"):
print(
fileinput.filename(),
fileinput.filelineno(),
linea,
end="",
)Este valor es el más útil en mensajes de validación.
Detectar la primera línea
isfirstline() indica si la línea actual es la primera del archivo.
for linea in fileinput.input(files=archivos, encoding="utf-8"):
if fileinput.isfirstline():
print(f"--- {fileinput.filename()} ---")
print(linea, end="")Puede utilizarse para cabeceras, separadores e inicialización por archivo.
Detectar stdin
isstdin() informa si la última línea provino de la entrada estándar.
origen = "stdin" if fileinput.isstdin() else fileinput.filename()Si stdin aparece varias veces, las apariciones posteriores normalmente no entregan líneas porque el flujo ya se consumió.
Saltar el resto de un archivo
nextfile() cierra la fuente actual y avanza. Las líneas ignoradas no aumentan la cuenta acumulada.
for linea in fileinput.input(files=archivos, encoding="utf-8"):
if linea.startswith("FIN"):
fileinput.nextfile()
continue
procesar(linea)No puede saltar el primer archivo antes de leer alguna línea, y el nombre actual cambia después de obtener datos de la siguiente fuente.
Usar FileInput directamente
La clase ofrece las mismas operaciones sin depender del estado global.
from fileinput import FileInput
with FileInput(files=archivos, encoding="utf-8") as entrada:
for linea in entrada:
print(
entrada.filename(),
entrada.filelineno(),
linea,
end="",
)Esta forma es mejor para bibliotecas, servidores, pruebas y programas que necesitan secuencias independientes.
Evitar secuencias globales superpuestas
fileinput.input() instala un estado global. Abrir otra secuencia antes de cerrar la primera genera errores o resultados confusos. Los objetos FileInput hacen visible la propiedad del recurso.
Encoding y errores
Las versiones modernas aceptan encoding y errors.
with fileinput.input(
files=archivos,
encoding="utf-8",
errors="strict",
) as entrada:
for linea in entrada:
procesar(linea)Usa strict cuando texto inválido debe detener el proceso. Políticas como replace o surrogateescape deben documentarse porque alteran o preservan bytes problemáticos de forma distinta.
Modo binario
FileInput permite mode="r" o mode="rb".
with fileinput.FileInput(files=archivos, mode="rb") as entrada:
for linea in entrada:
procesar_bytes(linea)En modo binario las líneas son bytes. No proporciones encoding.
Errores de lectura
Fileinput lanza OSError cuando no puede abrir o leer.
try:
with fileinput.input(files=archivos, encoding="utf-8") as entrada:
for linea in entrada:
procesar(linea)
except OSError as error:
print(f"fallo de lectura: {error}")Un lote debe decidir si un archivo defectuoso interrumpe todo o se registra para tratamiento posterior.
Archivos vacíos
Un archivo vacío se abre y cierra sin producir líneas. Si eso es inválido, comprueba el tamaño o registra explícitamente las rutas procesadas.
Leer gzip y bzip2
hook_compressed() abre archivos .gz y .bz2 de forma transparente.
with fileinput.FileInput(
files=["app.log", "app.log.gz", "viejo.log.bz2"],
openhook=fileinput.hook_compressed,
encoding="utf-8",
) as entrada:
for linea in entrada:
procesar(linea)Otras extensiones se abren normalmente. La detección depende del sufijo, no de una firma validada.
Hooks personalizados
Un openhook recibe nombre y modo y devuelve un objeto abierto. Encoding y errors se pasan como keywords cuando existen.
from pathlib import Path
def abrir_validado(filename, mode, *, encoding=None, errors=None):
ruta = Path(filename).resolve()
if ruta.suffix not in {".txt", ".log"}:
raise ValueError("extensión no soportada")
return open(ruta, mode, encoding=encoding, errors=errors)
with fileinput.FileInput(
files=archivos,
openhook=abrir_validado,
encoding="utf-8",
) as entrada:
for linea in entrada:
procesar(linea)No se puede combinar un hook con edición in-place.
Filtrado in-place
Con inplace=True, el archivo original se mueve a un backup y sys.stdout apunta a la ruta original. Todo lo impreso se convierte en el nuevo contenido.
for linea in fileinput.input(
files=["config.txt"],
inplace=True,
backup=".bak",
encoding="utf-8",
):
print(linea.replace("antiguo", "nuevo"), end="")Al finalizar, config.txt contiene el texto transformado y config.txt.bak conserva el original.
Riesgos de la edición in-place
La operación es destructiva. Un fallo, una ruta equivocada, salida truncada o un print() accidental pueden corromper datos. Prueba la transformación sin inplace, opera sobre copias y conserva backups.
La documentación oficial de fileinput indica que un backup existente con el mismo nombre puede reemplazarse silenciosamente. Los flujos críticos deben generar nombres únicos.
Backup por defecto
Sin un sufijo permanente, fileinput usa un backup temporal y lo elimina al cerrar. Los datos importantes deberían usar una extensión explícita o una política de archivado separada.
Logs durante la reescritura
Stdout está redirigido al archivo. Envía progreso y diagnósticos a stderr.
import sys
print("procesando", fileinput.filename(), file=sys.stderr)Separa estrictamente salida transformada y mensajes operativos.
In-place y stdin
La edición in-place está desactivada para stdin porque no existe un archivo original. Un filtro por pipe debe escribir a stdout y dejar que el llamador elija el destino.
Reescritura transaccional
Para archivos críticos, escribe en un temporal, realiza flush(), valida el resultado y sustituye el original con os.replace(). Este diseño ofrece más control sobre errores, permisos, backups y recuperación.
Acceso estrictamente secuencial
FileInput no ofrece acceso aleatorio ni índices. Evita mezclar iteración y readline() de forma confusa. Para recuperar una línea arbitraria usa linecache o una estructura propia.
Seguridad de rutas
Valida nombres externos contra una raíz permitida y resuelve symlinks.
BASE = Path("/srv/importaciones").resolve()
def ruta_permitida(nombre):
ruta = (BASE / nombre).resolve()
if ruta != BASE and BASE not in ruta.parents:
raise ValueError("ruta fuera del directorio permitido")
return rutaLimita también cantidad, tamaño y tipos aceptados.
Entradas comprimidas hostiles
Un archivo pequeño puede expandirse a muchos gigabytes. Aplica límites de bytes, tiempo y líneas. hook_compressed() facilita la apertura, pero no evita bombas de descompresión.
Pruebas
def test_varios_archivos(tmp_path):
primero = tmp_path / "a.txt"
segundo = tmp_path / "b.txt"
primero.write_text("uno\ndos\n", encoding="utf-8")
segundo.write_text("tres\n", encoding="utf-8")
with fileinput.FileInput(
files=[primero, segundo],
encoding="utf-8",
) as entrada:
lineas = list(entrada)
assert lineas == ["uno\n", "dos\n", "tres\n"]Prueba archivos vacíos, última línea sin salto, encoding inválido, stdin, compresión y fallos durante reescritura.
fileinput frente a open()
Usa open() para un solo archivo y control explícito. Usa fileinput cuando la abstracción principal es una secuencia continua de líneas provenientes de varias fuentes.
Rendimiento
Fileinput procesa una línea a la vez y no carga todos los archivos en memoria. Los costes dominantes suelen ser almacenamiento, decodificación, descompresión y la transformación. Compila expresiones regulares fuera del bucle y mide cargas reales.
Errores frecuentes
- Depender accidentalmente de
sys.argv. - Omitir encoding.
- Abrir secuencias globales superpuestas.
- Usar
strip()y eliminar espacios válidos. - Imprimir logs a stdout durante in-place.
- Reescribir datos sin backup permanente.
- Suponer que stdin puede consumirse varias veces.
- Aceptar compresión sin límites.
Buenas prácticas
- Usa un contexto
with. - Prefiere objetos
FileInputen bibliotecas. - Declara encoding y política de errores.
- Envía diagnósticos a stderr.
- Conserva backups para cambios destructivos.
- Valida rutas y tamaños externos.
- Prueba archivos vacíos y última línea.
- Usa sustitución atómica para datos críticos.
Conclusión
El módulo fileinput en Python simplifica filtros que leen varios archivos o stdin, manteniendo información útil de origen y numeración. También ofrece hooks para compresión y una reescritura in-place conveniente.
Esa comodidad exige disciplina. Evita estado global en aplicaciones complejas, define las reglas de texto, trata OSError, limita entradas desconocidas y considera cada reescritura como destructiva. Con validación, backups y pruebas, fileinput es una base compacta para utilidades de terminal confiables.






