Programando en Ruby

La Guía de los "Programadores Pragmáticos"

Basic Input and Output



Ruby tiene, a primera vista, dos grupos separados de rutinas de E/S. El primero es la interfaz simple (por ahora es la única que hemos usado):

print "Dime tu nombre: "
nombre = gets

Hay todo un conjunto de métodos relacionados con la E/S implementados en el módulo Kernel (gets, open, print, printf, putc, puts, readline, readlines, y test) que hacen fácil y conveniente escribir programas claros en Ruby. Normalmente estos métodos hacen la E/S por la entrada y salida estándares, lo que resulta útil para escribir filtros. Están documentados a partir de la página 411.

La segunda manera, que te da mucho más control, es usar objetos IO.

¿Qué es un objeto IO?

Ruby define una sola clase base, IO, para gestionar toda la entrada y salida. De esta clase base heredan File y BasicSocket, para dar un comportamiento más especializado, pero los principìos se mantienen iguales para todos. Un objeto IO es un canal bidireccional entre un programa en Ruby y un recurso externo[Para aquellos que tienen que saber por fuerza los detalles de implementación, significa que un solo objeto IO puede estar gestionando más de un descriptor del sistema operativo. Por ejemplo, cuando abres una pareja de tuberías, un solo objeto IO contiene tanto la de lectura como la de escritura]. Puede que haya más de lo que parece en los objetos IO, pero al final simplemente leerás de y escribirás en él.

En este capítulo nos concentraremos en la clase IO y en su clase heredera más común, File. Para más detalles sobre las clases relacionadas con la red, vea el apartado que empieza en la página 469.

Abrir y cerrar ficheros

Como era de esperar, se puede crear un nuevo objeto de fichero con File.new .

unFichero = File.new("ficheroprueba", "r")

# ... procesar el fichero

unFichero.close

Puedes crear un objeto de clase File preparado para leer, escribir o ambas, según los caracteres de modo que elijas (aquí abrimos ``ficheroprueba'' para leer con el ``r''). La lista completa de modos permitidos está en la página 326. También puedes, optativamente, elegir los permisos del fichero a la hora de crearlo; mira la descripción de File.new en la página 303 para más detalles. Después de abrir el fichero podemos trabajar con él, leyendo o escribiendo lo que queramos. Por último, como responsables programadores, cerramos el fichero para asegurarnos de que todos los datos que hay en memoria se escriben y que todos los recursos relacionados se liberan.

Pero aquí Ruby puede hacerte la vida un poco más fácil. El método File.open también abre un fichero. En su uso normal, hace lo mismo que File.new . Sin embargo, si hay un bloque asociado a la llamada, open se comporta de manera diferente: en vez de devolver un nuevo objeto File, invoca al bloque, pasándole el fichero acabado de abrir como parámetro. Cuando el bloque termina, el fichero se cierra automáticamente.

File.open("ficheroprueba", "r") do |unFich|

# ... procesar el fichero

end

Lectura y escritura de ficheros

Los mismos métodos que has estado usando para la E/S ``simple'' están disponibles para todos los objetos de tipo fichero. Así, gets lee una línea de la entrada de la terminal, y unFich.gets lee una línea del objeto fichero unFich.

Sin embargo, los objetos de E/S tienen una serie de métodos de acceso adicionales, pensados para hacer nuestra vida más fácil.

Iteradores para leer

Además de los bucles comunes para leer datos de un flujo IO, puedes usar varios iteradores de Ruby. IO#each_byte invoca un bloque para el siguiente byte de 8 bits del objeto IO (en este caso, del objeto File).

unFich = File.new("ficheroprueba")
unFich.each_byte {|c| putc c; putc ?. }
produce:
É.s.t.a. .e.s. .l.a. .l.í.n.e.a. .u.n.o.
.É.s.t.a. .e.s. .l.a. .l.í.n.e.a. .d.o.s.
.É.s.t.a. .e.s. .l.a. .l.í.n.e.a. .t.r.e.s.
.Y. .a.s.í. .s.i.e.m.p.r.e.......
.

El método IO#each_line llama al bloque con la siguiente línea del fichero. En el siguiente ejemplo haremos visibles los saltos de línea usando String#dump , para que puedas ver que no estamos haciendo trampas.

unFich.each_line {|linea| puts "Obtengo #{linea.dump}" }
produce:
Obtengo "\311sta es la l\355nea uno\n"
Obtengo "\311sta es la l\355nea dos\n"
Obtengo "\311sta es la l\355nea tres\n"
Obtengo "Y as\355 siempre...\n"

Puedes pasar a each_line cualquier serie de caracteres como separador de línea, y partirá la entrada adecuadamente, devolviendo el separador de líneas al final de cada línea de datos. De ahí que veas los ``\n'' en la salida del anterior ejemplo. En el siguiente usaremos ``e'' como separador de líneas.

unFich.each_line("e") do |linea|
  puts "Obtengo #{ linea.dump }"
end
produce:
Obtengo "\311sta e"
Obtengo "s la l\355ne"
Obtengo "a uno\n\311sta e"
Obtengo "s la l\355ne"
Obtengo "a dos\n\311sta e"
Obtengo "s la l\355ne"
Obtengo "a tre"
Obtengo "s\nY as\355 sie"
Obtengo "mpre"
Obtengo "...\n"

Si combinas la idea del iterador con la característica del bloque que cierra automáticamente el fichero, obtienes IO.foreach . Este método recibe el nombre de una fuente de E/S, la abre en modo lectura, llama al iterador por cada línea del fichero, y entonces lo cierra automáticamente.

IO.foreach("ficheroprueba") { |line| puts line }
produces:
Ésta es la línea uno
Ésta es la línea dos
Ésta es la línea tres
Y así siempre...

O, si lo prefieres, puedes obtener un fichero entero en una lista de líneas:

lista = IO.readlines("ficheroprueba")
lista.length » 4
lista[0] » "\311sta es la l\355nea uno\n"

No olvides que la E/S nunca es segura en un mundo incierto---se lanzarán excepciones en la mayoría de los errores, así que deberías estar atento para cazarlas y tomar medidas apropiadas.

Escritura a ficheros

Por ahora, hemos estado llamando felizmente a puts y print, pasándoles cualquier objeto y confiando en que Ruby hará lo correcto (que, por supuesto, hace). ¿Pero qué está haciendo exactamente?

La respuesta es bastante simple. Con un par de excepciones, cada objeto que pasas a puts y print se convierte a caracteres llamando al método to_s del propio objeto. Si por cualquier razón el método no devuelve una ristra de caracteres válida, se crea una, incluyendo el nombre de la clase y el id del objeto, algo como <NombreClase:0x123456>.

Las excepciones también son simples. El objeto nil se imprime como la ristra ``nil'', y una lista pasada a puts se escribirá como si se llamara al método con cada uno de sus elementos por separado.

¿Y qué pasa si quieres escribir datos binarios y no quieres que Ruby esté cambiando nada? Normalmente puedes usar IO#print y pasar una ristras con los caracteres a escribir. Sin embargo, puedes llegar a las rutinas de E/S de nivel bajo si de verdad quieres---mira la documentación de IO#sysread y IO#syswrite en la página 335.

¿Y cómo metes tus datos binarios en una ristra, para empezar? Las dos maneras más comunes son tratarlo byte a byte o usar Array#pack .

str = "" » ""
str << 1 << 2 << 3 » "\001\002\003"
[ 4, 5, 6 ].pack("c*") » "\004\005\006"

Hecho de menos mi Iostream de C++

Sobre gustos no hay nada escrito... Sin embargo, igual que puedes añadir un objeto a un Array con el operador <<, también puedes añadir un objeto a un flujo IO de salida:

endl = "\n"
$stdout << 99 << " globos rojos" << endl
produces:
99 globos rojos

De nuevo, el método << usa el to_s para convertir sus argumentos en caracteres antes de enviarlos por su camino.

Comunicarse con redes

Ruby habla on fluidez la mayoría de los protocolos de Internet, tanto de bajo como de alto nivel.

Para aquéllos que disfrutan manipulándolo todo a nivel de red, Ruby viene con un conjunto de clases en el módulo ``socket'' (documentado a partir de la página 469). Estas clases dan acceso a TCP, UDP, SOCKS y a canales de comunicación locales de Unix (``Unix domain sockets''), así como a cualquier otro tipo adicional de canal propio de tu arquitectura. El módulo también tiene clases de conveniencia para hacer más fácil el escribir servidores. Aquí presentamos un programa simple que obtiene información sobre el usuario ``oracle'' de la máquina local, medianete el protocolo ``finger''.

require 'socket'
cliente = TCPSocket.open('localhost', 'finger')
cliente.send("oracle\n", 0)    # 0 means standard packet
puts cliente.readlines
cliente.close
produce:
Login: oracle         			Name: Oracle installation
Directory: /home/oracle             	Shell: /bin/bash
Never logged in.
No Mail.
No Plan.

A nivel más alto, el conjunto de módulos lib/net dan forma de manejar una serie de protocolos de red a nivel de aplicación (actualmente FTP, HTTP, POP, SMTP y telnet). Todo esto está documentado a partir de la página 482. Por ejemplo, el siguiente programa lista las imágenes mostradas en la página principal de los Programadores Pragmáticos.

require 'net/http'

h = Net::HTTP.new('www.pragmaticprogrammer.com', 80) resp, datos = h.get('/index.html', nil) if resp.message == "OK"   datos.scan(/<img src="(.*?)"/) { |x| puts x } end
produce:
images/title_main.gif
images/dot.gif
images/dot.gif
images/dot.gif
images/aafounders_70.jpg
images/pp_cover_thumb.png
images/ruby_cover_thumb.png
images/dot.gif
images/dot.gif


Extraído del libro "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2000 Addison Wesley Longman, Inc. liberado bajo los términos de la Open Publication License V1.0.
La referencia está disponible en: download.