Entrada y salida en Ruby: archivos, rutas y formatos comunes

Capítulo 7

Tiempo estimado de lectura: 7 minutos

+ Ejercicio

Lectura y escritura de archivos con File e IO

En Ruby, la entrada/salida (I/O) te permite leer y escribir datos fuera de tu programa: archivos de texto, reportes, exportaciones y datos para otros scripts. La clase File es la puerta de entrada más común para trabajar con archivos en disco, y se apoya en la abstracción de IO (un “flujo” del que puedes leer o al que puedes escribir).

Apertura de archivos: modos más usados

Al abrir un archivo, debes elegir un modo. Los más comunes:

  • "r": lectura (falla si no existe).
  • "w": escritura (crea o sobrescribe).
  • "a": append (crea o agrega al final).
  • "r+": lectura y escritura (no trunca).
  • "w+": lectura y escritura (trunca).
  • "a+": lectura y escritura (agrega al final).
  • "rb", "wb": modos binarios (útiles en Windows o para archivos no-texto).

En la práctica, para texto suele bastar con "r", "w" o "a".

Uso de bloques: cierre seguro del archivo

La forma recomendada es abrir con bloque: Ruby cierra el archivo automáticamente al terminar el bloque, incluso si ocurre un problema dentro.

File.open("data/notas.txt", "w") do |f|  f.puts "Primera línea"  f.puts "Segunda línea"end

Si abres sin bloque, debes cerrar manualmente (y es fácil olvidarlo):

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

f = File.open("data/notas.txt", "a")f.puts "Otra línea"f.close

Rutas y manejo de paths de forma portable

Evita “armar rutas” concatenando strings con "/". En distintos sistemas operativos el separador puede variar. Usa File.join y utilidades de File/Dir para mantener tu código portable.

Construir rutas y asegurar carpetas

base = Dir.pwd # carpeta actual del proyectoinput_path  = File.join(base, "data", "usuarios.csv")output_path = File.join(base, "out", "usuarios.json")Dir.mkdir(File.join(base, "out")) unless Dir.exist?(File.join(base, "out"))

Operaciones comunes con rutas

  • ¿Existe?: File.exist?(path)
  • ¿Es archivo?: File.file?(path)
  • ¿Es directorio?: File.directory?(path)
  • Extensión: File.extname(path)
  • Nombre base: File.basename(path)
  • Directorio padre: File.dirname(path)

Leer texto: todo de una vez vs. línea a línea

Para archivos pequeños, puedes leer todo el contenido de una vez. Para archivos medianos/grandes, es mejor procesar línea a línea para ahorrar memoria.

Leer todo el archivo

contenido = File.read("data/notas.txt")puts contenido

Procesar línea a línea

File.foreach("data/notas.txt") do |linea|  puts "Línea: #{linea.strip}"end

File.foreach es muy práctico porque abre, itera y cierra internamente.

Escritura incremental (reportes)

Para generar reportes, suele convenir ir escribiendo conforme procesas datos:

File.open("out/reporte.txt", "w") do |f|  f.puts "reporte generado"  f.puts "-" * 20  (1..3).each do |i|    f.puts "fila #{i}"  endend

Persistir datos simples

Cuando solo necesitas guardar algo sencillo (por ejemplo, un log o un listado), un archivo de texto suele ser suficiente. Algunas ideas:

  • Guardar una lista: una línea por elemento.
  • Guardar pares clave-valor: clave=valor por línea.
  • Guardar un “snapshot” de un reporte: encabezado + filas.

Ejemplo: guardar IDs procesados:

ids = ["u001", "u002", "u003"]File.open("out/ids_procesados.txt", "w") do |f|  ids.each { |id| f.puts id }end

Formatos comunes para intercambiar información: CSV y JSON

Dos formatos muy usados para mover datos entre scripts y herramientas son:

  • CSV: ideal para tablas (filas/columnas), compatible con hojas de cálculo.
  • JSON: ideal para estructuras (objetos, listas), muy común en APIs y configuración.

CSV en Ruby: leer y escribir con la librería estándar

Ruby incluye csv en su librería estándar. Para trabajar con encabezados, usa headers: true.

require "csv"CSV.foreach("data/usuarios.csv", headers: true) do |row|  # row es un CSV::Row, puedes acceder por nombre de columna  puts row["email"]end

Escritura de CSV:

require "csv"filas = [  ["id", "email", "activo"],  ["u001", "ana@example.com", true],  ["u002", "bob@example.com", false]]CSV.open("out/usuarios_export.csv", "w") do |csv|  filas.each { |fila| csv << fila }end

JSON en Ruby: leer y escribir con la librería estándar

Ruby incluye json. Para convertir estructuras Ruby a JSON, usa JSON.generate o JSON.pretty_generate (más legible). Para leer, usa JSON.parse.

require "json"data = {"app" => "mi_script", "version" => 1, "items" => [1, 2, 3]}File.write("out/data.json", JSON.pretty_generate(data))
require "json"raw = File.read("out/data.json")obj = JSON.parse(raw)puts obj["app"]

Nota: al parsear JSON, las claves suelen quedar como strings (a menos que transformes claves explícitamente).

Actividad aplicada: importar CSV, limpiar/transformar y exportar JSON

Objetivo: tomar un CSV de “usuarios” o “productos”, normalizar campos (limpieza), transformar tipos y exportar un JSON con una estructura clara para que otro script lo consuma.

1) Preparar el CSV de entrada

Ejemplo de archivo data/usuarios.csv (con encabezados):

id,nombre,email,edad,activo,plan,último_loginu001, Ana Pérez , ANA@EXAMPLE.COM , 29 , si , pro , 2025-01-10u002,Bob, bob@example.com, , no, free, 2024-12-01u003,Carla, carla@example.com, 41, SI, pro,

Observa problemas típicos: espacios extra, mayúsculas en email, edad vacía, booleanos como “si/no”, fechas vacías.

2) Definir funciones de limpieza y transformación

Centraliza reglas para que el procesamiento sea consistente.

def clean_str(value)  return nil if value.nil?  s = value.to_s.strip  s.empty? ? nil : senddef clean_email(value)  s = clean_str(value)  s&.downcaseenddef to_int(value)  s = clean_str(value)  s ? s.to_i : nilenddef to_bool(value)  s = clean_str(value)&.downcase  return true  if ["si", "sí", "true", "1", "yes", "y"].include?(s)  return false if ["no", "false", "0", "n"].include?(s)  nilend

3) Leer el CSV con encabezados y construir una estructura Ruby

Generaremos un JSON con metadatos y una lista de usuarios normalizados.

require "csv"require "json"input_path  = File.join(Dir.pwd, "data", "usuarios.csv")output_dir  = File.join(Dir.pwd, "out")output_path = File.join(output_dir, "usuarios.json")Dir.mkdir(output_dir) unless Dir.exist?(output_dir)usuarios = []errores = []CSV.foreach(input_path, headers: true) do |row|  id     = clean_str(row["id"])  nombre = clean_str(row["nombre"])  email  = clean_email(row["email"])  edad   = to_int(row["edad"])  activo = to_bool(row["activo"])  plan   = clean_str(row["plan"])&.downcase  ultimo_login = clean_str(row["último_login"]) || clean_str(row["último_login"])  # Validaciones mínimas para el ejemplo  if id.nil? || email.nil?    errores << {"id" => id, "motivo" => "Falta id o email", "fila" => row.to_h}    next  end  usuarios << {    "id" => id,    "nombre" => nombre,    "email" => email,    "edad" => edad,    "activo" => activo,    "plan" => plan || "free",    "ultimo_login" => ultimo_login  }endpayload = {  "fuente" => File.basename(input_path),  "total" => usuarios.size,  "errores" => errores.size,  "usuarios" => usuarios,  "rechazados" => errores}File.write(output_path, JSON.pretty_generate(payload))puts "Exportado: #{output_path}"puts "Usuarios: #{usuarios.size} | Rechazados: #{errores.size}"

4) Procesar línea a línea y generar un reporte adicional

Además del JSON, es común crear un reporte de auditoría (por ejemplo, cuántos usuarios por plan). Esto refuerza el flujo: leer → transformar → escribir.

conteo_por_plan = Hash.new(0)usuarios.each do |u|  conteo_por_plan[u["plan"]] += 1endreport_path = File.join(output_dir, "reporte_planes.txt")File.open(report_path, "w") do |f|  f.puts "reporte de planes"  f.puts "archivo: #{File.basename(input_path)}"  f.puts "-" * 30  conteo_por_plan.sort_by { |plan, n| [-n, plan] }.each do |plan, n|    f.puts "#{plan}: #{n}"  endend

5) Verificar resultados rápidamente

Comprueba que el JSON se generó y que tiene la estructura esperada:

require "json"obj = JSON.parse(File.read(File.join(Dir.pwd, "out", "usuarios.json")))puts obj["total"]puts obj["usuarios"].first

Tabla de referencia rápida

TareaHerramientaEjemplo
Leer archivo completoFile.readFile.read(path)
Escribir archivo completoFile.writeFile.write(path, str)
Iterar líneasFile.foreachFile.foreach(path) { |l| ... }
Abrir con cierre seguroFile.open + bloqueFile.open(path, "w") { |f| ... }
Construir rutasFile.joinFile.join(a, b, c)
Leer CSVCSV.foreachCSV.foreach(path, headers: true)
Escribir JSONJSON.pretty_generateFile.write(path, JSON.pretty_generate(obj))

Ahora responde el ejercicio sobre el contenido:

Para procesar un archivo de texto grande sin consumir demasiada memoria, ¿qué enfoque es el más adecuado en Ruby?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Para archivos medianos o grandes conviene procesar línea a línea para ahorrar memoria. File.foreach itera por líneas y además gestiona la apertura y cierre internamente.

Siguiente capítulo

Código limpio en Ruby: estilo, legibilidad y refactorización

Arrow Right Icon
Portada de libro electrónico gratuitaRuby desde Cero para principiantes: Programación Moderna, Clara y Eficiente
70%

Ruby desde Cero para principiantes: Programación Moderna, Clara y Eficiente

Nuevo curso

10 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.