Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Eingabe und Ausgabe



Auf den ersten Blick stellt Ruby zwei getrennte Sätze von I/O Routinen zur Verfügung. Der erste ist die simple Schnittstelle -- wir haben bis jetzt fast ausschließlich sie benutzt.

print "Geben Sie Ihren Namen ein: "
name = gets

Im Modul Kernel sind eine Menge I/O-bezogene Methoden implementiert -- gets, open, printf, putc, puts, readline, readlines und test --, mit deren Hilfe man einfach und bequem einfache Ruby-Programme schreiben kann. Diese Methoden arbeiten typischerweise mit der Standardeingabe und der Standardausgabe, womit sie nützlich zum Schreiben von Filtern sind. Sie finden Sie ab Seite 415 beschrieben.

Der zweite Weg, der Ihnen viel mehr Kontrolle gibt, ist, IO-Objekte zu benutzen.

Was ist ein IO-Objekt?

Ruby definiert eine einzige Basisklasse, IO, die Ein- und Ausgabe behandelt. Diese Klasse wird von File und BasicSocket, die spezialisierteres Verhalten bereitstellen, geerbt, die Prinzipien sind dennoch immer die gleichen. Ein IO-Objekt ist ein in beide Richtungen arbeitender Kanal zwischen einem Ruby-Programm und einer externene Ressource.[Für die, die Details der Implementierung einfach wissen müssen: Das bedeutet, dass ein einzelnes IO-Objekt manchmal mehr als einen Filedescriptor des Systems verwalten kann. Wenn Sie zum Beispiel zwei Pipes (eine zum Lesen, eine zum Schreiben) öffnen, enthält ein einziges IO-Objekt beide Pipes.] Hinter einem IO-Objekt kann mehr stecken, als Sie denken, aber schlussendlich verwenden Sie es, um in es zu schreiben und von ihm zu lesen.

In diesem Kapitel konzentrieren wir uns auf die Klasse IO und ihre am häufigsten verwendete Unterklasse, File. Für nähere Informationen, wie man die Socket-Klasse für Netzwerkaufgaben verwendet, lesen Sie den Abschnitt, der auf Seite 473 beginnt.

Dateien öffnen und schließen

Wie erwartet können Sie eine neues Dateiobjekt mit File.new erstellen.

aFile = File.new("testfile", "r")

# ... Datei verarbeiten

aFile.close

Mit dem Modus-String können Sie angeben, ob ein File-Objekt lesbar, schreibbar oder beides sein soll (hier haben wir ``testfile'' mit ``r'' zum Lesen geöffnet). Eine vollständige Liste der erlaubten Modi ist auf Seite 331 zu finden. Sie können auch optional Berechtigungen angeben, wenn Sie eine Datei erstellen; für Details lesen Sie die Beschreibung von File.new auf Seite 308. Nach dem Öffnen der Datei können wir mit ihr arbeiten und schreiben und/oder lesen, wie wir es brauchen. Als verantwortungsvolle Softwarebürger schließen wir die Datei und versichern uns, dass alle gepufferten Daten geschrieben wurden und nicht mehr benötigte Resourcen freigemacht worden sind.

Aber hier kann Ihnen Ruby das Leben etwas erleichtern. Auch die Methode File.open öffnet eine Datei. Bei normaler Verwendung verhält sie sich wie File.new. Wenn jedoch mit dem Aufruf der Methode ein Block assoziiert ist, verhält sich open anders. Anstatt ein neues File-Objekt zurückzuliefern, führt sie den Block aus und übergibt ihm die neu geöffnete Datei als Parameter. Wenn der Block verlassen wird, wird die Datei automatisch geschlossen.

File.open("testfile", "r") do |aFile|

# ... Datei verarbeiten

end

Dateien lesen und schreiben

Die gleichen Methoden, die wir für ``einfaches'' I/O verwendet haben, sind für alle Dateiobjekte verfügbar. So wie gets eine Zeile vom Standard-Input liest, liest aFile.gets eine Zeile vom Dateiobjekt aFile.

I/O-Objekte erfreuen sich jedoch an noch mehr Zugriffsmethoden, alle dazu gedacht, uns das Leben einfacher zu machen.

Lese-Iteratoren

So wie Sie eine gewöhnliche Schleife verwenden können, um Daten von einem IO-Objekt zu lesen, können Sie auch verschiedene Iteratoren benutzen. IO#each_byte ruft einen Block mit dem nächsten 8-bittigen Byte des IO-Objekts auf (in diesem Fall ein Objekt vom Typ File).

aFile = File.new("testfile")
aFile.each_byte {|ch| putc ch; putc ?. }
ergibt:
T.h.i.s. .i.s. .l.i.n.e. .o.n.e.
.T.h.i.s. .i.s. .l.i.n.e. .t.w.o.
.T.h.i.s. .i.s. .l.i.n.e. .t.h.r.e.e.
.A.n.d. .s.o. .o.n.......
.

IO#each_line ruft den Block mit der nächsten Zeile einer Datei auf. Im nächsten Beispiel machen wir mit String#dump, die ursprünglichen Newlines sichtbar, damit Sie sehen, dass wir nicht schummeln.

aFile.each_line {|line| puts "Got #{line.dump}" }
ergibt:
Got "This is line one\n"
Got "This is line two\n"
Got "This is line three\n"
Got "And so on...\n"

Sie können each_line jede Zeichensequenz als Zeilentrenner übergeben. Die Methode wird die Eingabe dementsprechend umbrechen und das Zeilenende am Ende jeder Datenzeilen zurückgeben. Das ist der Grund, warum Sie das Zeichen ``\n'' im Output des vorigen Beispiels sehen. Im nächsten Beispiel verwenden wir ``e'' als Zeilentrenner.

aFile.each_line("e") do |line|
  puts "Got #{ line.dump }"
end
ergibt:
Got "This is line"
Got " one"
Got "\nThis is line"
Got " two\nThis is line"
Got " thre"
Got "e"
Got "\nAnd so on...\n"

Wenn Sie die den Iterator und den sich automatisch schließenden Block kombinieren, kommt IO.foreach heraus. Diese Methode nimmt den Namen einer I/O-Quelle, öffnet diese zum Lesen, ruft den Iterator für jede Zeile in der Datei auf und schließt die Datei dann automatisch.

IO.foreach("testfile") { |line| puts line }
ergibt:
This is line one
This is line two
This is line three
And so on...

Oder Sie können, wenn Sie das lieber haben, ein Array mit allen Zeilen einer Datei erhalten:

arr = IO.readlines("testfile")
arr.length » 4
arr[0] » "This is line one\n"

Vergessen Sie nicht, dass Ein- und Ausgabe in einer unsicheren Welt niemals sicher sein kann -- bei den meistern Fehlern werden Ausnahmen ausgelöst und Sie sollten sich darauf vorbereiten sie abzufangen und angemessen darauf zu reagieren.

In Dateien schreiben

Bis jetzt haben wir fröhlich puts und print aufgerufen, ihnen irgendwelche alten Objekten übergeben und darauf vertraut, dass Ruby das richtige macht (was es natürlich tut). Aber was genau macht Ruby?

Die Antwort ist ziemlich einfach. Mit einigen Ausnahmen wird jedes Objekt, dass Sie puts und print übergeben, in einen String umgewandelt, indem die Methde to_s des Objekts aufgerufen wird. Wenn aus irgendeinem Grund to_s keinen gültigen String zurückgibt, wird ein String, der den Klassennamen und die ID des Objekts enthält, erstellt, etwas wie <ClassName:0x123456>.

Auch die Ausnahmen sind einfach. Das Objekt nil wird als der String ``nil'' ausgegeben und ein Array, das puts übergeben wird, wird so ausgegeben, als ob jedes Element einzeln puts übergeben worden wäre.

Was, wenn Sie binäre Daten schreiben wollen und Ruby nicht damit verwirren wollen? Nun, normalerweise können Sie einfach IO#print benutzen und den String, der die Bytes enhält, die geschrieben werden sollen, übergeben. Sie können jedoch auch die low-level Eingabe- und Ausgaberoutinen verwenden, wenn Sie das wirklich wollen -- schauen Sie sich auf Seite 339 die Dokumentation zu IO#sysread und IO#syswrite an.

Und wie bekommt man für die erste Möglichkeit die binären Daten in einen String? Die beiden gebräuchlichen Wege sind, sie Byte für Byte hineinzustecken oder Array#pack zu verwenden.

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

Aber ich vermisse meinen Iostream aus C++!

Manchmal wird Geschmack einfach nicht gewürdigt...Aber so wie Sie mit << ein Objekt einem Array hinzufügen können, so können Sie auch ein Objekt an einen Ausgabe-IO-Stream anhängen:

endl = "\n"
$stdout << 99 << " red balloons" << endl
ergibt:
99 red balloons

Wieder benutzt die Methode << to_s, um ihre Argumente in Strings umzuwandeln, bevor sie sie fröhlich auf den Weg schickt.

Mit Netzwerken reden

Ruby spricht die meisten Internetprotokolle fließend, sowohl low-level als auch high-level Protokolle.

Für die, denen es Spaß macht, über die Netzwerkschicht zu kriechen, kommt Ruby mit einer Menge Klassen in der Socket-Library (am Seite 473 dokumentiert) daher. Diese Klassen geben Ihnen Zugriff auf TCP, UDP, SOCKS und Unix Domain Sockets, sowie über jeden zusätzlichen Socket, den Ihre Plattform unterstützt. Die Bibliothek stellt auch Hilfsklassen zur Verfügung, die es einfacher machen, Server zu schreiben. Hier ist ein einfaches Programm, dass das finger-Protokoll benutzt, um Informationen über den Benutzer ``oracle'' zu erhalten:

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

Auf einem höheren Level bieten die lib/net Bibliotheksmodule Zugriff auf eine Reihe von Protokollen der Anwedungsschicht (zur Zeit FTP, HTTP, POP, SMTP und telnet). Diese sind ab Seite 486 dokumentiert. Das folgende Programm zum Beispiel listet die Bilder, die auf der Homepage der Pragmatic Programmers gezeigt werden, auf:

require 'net/http'

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


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Übersetzung: Johannes Tanzler
Für das englische Original:
© 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0. That reference is available for download.
Diese Lizenz sowie das Original vom Herbst 2001 bilden die Grundlage der Übersetzung
Es wird darauf hingewiesen, dass sich die Lizenz des englischen Originals inzwischen geändert hat.
Für die deutsche Übersetzung:
© 2002 Jürgen Katins
Der Copyright-Eigner stellt folgende Lizenzen zur Verfügung:
Nicht-freie Lizenz:
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Freie Lizenz:
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".