Open Source im professionellen Einsatz

Zeilenweise einlesen

Listing 1 erledigt eine typische Aufgabe im Zusamenhang mit Textdateien. Dabei geschieht die meiste Arbeit in der letzten Zeile: Sie liest eine Datei ein und zerteilt sie an den Newline-Zeichen – aber nur, falls die angegebene Datei überhaupt existiert. Der Code lässt sich zeilenweise an der interaktiven Ruby-Shell eingeben, die der Befehl »irb« aufruft.

Listing 1

Zeilen einlesen

01 people_path = "/tmp/people.txt"
02
03 people = []
04 people = File.read(people_path).split("\n") if File.exists?(people_path)

Der Code wird plattformneutral, wenn man Rubys eingebaute Variable »$/« für das Zeilenende verwendet. Diese dient auch als Trennzeichen der Methode »File.readlines()« , die eine Datei gleichzeitig liest und in Zeilen aufteilt:

File.readlines(people_path)# => ["line1\n", "line2\n", ...]

Offenbar belässt »readlines()« das Zeichen für das Zeilenende im Text, wie Abbildung 1 zeigt. So lässt sich das Zeichen entfernen:

Abbildung 1: Interaktiv eingegebener Ruby-Code verarbeitet die Zeilen einer Textdatei.

Abbildung 1: Interaktiv eingegebener Ruby-Code verarbeitet die Zeilen einer Textdatei.

File.readlines(people_path).map{ |line| line.chomp }

Der obige Code verwendet eine Map, um jede Zeile durch einen Codeblock zu schicken. Da Ruby Koroutinen kennt und Funktionen als Bürger erster Klasse behandelt, erinnern die Programme oft an funktionale Programmierung.

Der Einzeiler lässt sich noch weiter kürzen, denn Ruby kennt eine Schreibweise, um eine benannte Funktion auf jedes Element einer Menge anzuwenden:

File.readlines(people_path).map(&:chomp)

Der bisherige Ansatz birgt allerdings ein Problem: Er setzt voraus, dass mehr Speicher zu Verfügung steht, als Daten eingelesen werden. Für Ad-hoc-Skripte mag es angehen, mit kompletten Dateien zu arbeiten, für ernsthafte Admin-Aufgaben ist es zu riskant. Besser ist es, die Daten Zeile für Zeile zu lesen und zu verarbeiten. Dazu eignet sich eine Koroutine:

File.foreach(people_path) do |line|
 # Zeile verarbeiten
end

Die Methode »File.foreach()« gibt einen so genannten Enumerator zurück, aus dem sich Element für Element verarbeiten lässt. Angenommen die verarbeitete Datei enthält einige sehr lange Zeilen, deren Zeichen der Ruby-Anwender zählen möchte. Dazu schreibt er:

character_counts = File.foreach(people_path).map(&:size)

Die Map-Methode entnimmt dem von »foreach()« erzeugten Enumerator nur eine Zeile auf einmal, daher sollte sich auch nur jeweils eine Zeile im Speicher befinden. Liebhaber der funktionalen Programmierung kennen das als Lazy Evaluation. Das Programm wertet nur so viel aus, wie es gerade für den nächsten Schritt benötigt. In einer der nächsten Releases soll Ruby Unterstützung für lange Kommandoverkettungen mit mehreren Map- und Filter-Stufen bieten. Listing 2 zeigt die Verwendung von »File.open()« . Es erzeugt ein Filehandle, ein Konzept, das Programmierer aus anderen Sprachen kennen. Der Modestring »"w"« hat dabei dieselbe Bedeutung wie in C und öffnet die Datei zum Schreiben. Standardmäßig öffnet Ruby Dateien nur zum Lesen.

Listing 2

Filehandle

01 File.open(people_path, "w") do |f|
02  f.puts "john"            # eine Zeile schreiben
03  f.puts "james", "george" # zwei Zeilen schreiben
04  f.print "anna"           # 4 Zeichen ohne Newline
05  f.print "bel\n"          # explizit Newline anfügen
06 end

Filehandles, Blocks und Verzeichnisse

Die Besonderheit von Listing 2 besteht darin, dass es das Filehandle an einen Codeblock übergibt. Das heißt, alles was im Block steht, passiert nur, wenn die Datei erfolgreich zum Schreiben geöffnet wurde. Am Ende das Blocks schließt Ruby die Datei automatisch.

Das »Dir« -Modul dient dazu, Verzeichnisse zu lesen, anzulegen und so weiter. Listing 3 erstellt eine Liste aller C-Quelltextdateien im Arbeitsverzeichnis und sortiert sie nach dem »modified« -Zeitstempel. Dabei kommt das von der Bash vertraute Globbing-Muster mit Asterisk zum Einsatz.

Listing 3

Dir

01 source_files = Dir["*.c"].select{|f| File.size(f) > 1024}.sort_by{|f| File.mtime(f)}

Der beschriebene Umgang mit Dateien lässt sich in der Ruby-Dokumentation unter den Klassennamen »File« , »Dir« , »IO« und »Enumerator« nachlesen. Daneben besitzt Ruby ein eingebautes Informationssystem, das sich auf der Kommandozeile mit »ri Schlagwort« aufrufen lässt (Abbildung 2).

Abbildung 2: Rubys eingebautes Dokumentationssystem gibt Informationen zu Schlagwörtern aus.

Für die aktuelle Version 1.9.x haben die Ruby-Entwickler dem Encoding eine Runderneuerung verpasst. Dieses Thema hat mehrere Aspekte: Die Kodierung der Skriptdateien selbst darf nun auch UTF-8 sein, was der Programmierer zu Beginn der Datei angeben muss:

#!/usr/bin/env ruby -w
# encoding: UTF-8
utf8_string_literal = "André"

Außerdem geht es um den Zeichensatz der Ein- und Ausgaben. Hier ist es in der Regel empfehlenswert, die Standardkodierung des Betriebssystems zu verwenden:

ruby -e 'puts Encoding.default_external.name'

Daneben spielt noch das Encoding eine Rolle, das Ruby intern beim Verarbeiten der eingelesenen Zeichenketten einsetzt. Ein Blogeintrag des Ruby-Programmierers James Edward Gray behandelt alle Aspekte ausführlich [3].

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 5 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook