Ruby Weblog Archiv 2005 Quartal 1

Winfried Mueller, www.reintechnisch.de


30.03.2005 :: Deep Copy und Shallow Copy

Permalink

Wenn man ein Objekt kopiert oder einer Referenz ein anderes Objekt zuweist, wird man früher oder später über merkwürdige Effekte stolpern. Genaugenommen darüber, dass man in einem Objekt was ändert und in einem anderen ändert sich auf einmal auch was.

Will man der Sache auf den Grund gehen, muss man wissen, wie Ruby Objekte handhabt, wenn man sie kopiert oder wenn man Zuweisungen macht.

Generell beinhaltet jede Variable in Ruby nicht das Objekt selber sondern es ist eine Referenz (ein Zeiger) auf eine Objekt. Objekte schweben also salopp gesagt im freien Raum und Variablen zeigen auf sie. Zeigt nichts mehr auf ein Objekt, wird es früher oder später vom Garbage Collector (GC) gelöscht. Das macht Ruby nicht sofort, sondern dann, wenn der Speicher mal wieder knapp wird und aufgeräumt werden muss.

Bei einer Zuweisung wird generell die Referenz kopiert, nicht aber das Objekt.

 
s = "Hello"
t = s              # t und s zeigen auf das gleiche Stringobjekt 
                   # mit dem Inhalt "Hello"

puts t.object_id   # beide sind identisch
puts s.object_id

t << " World"      # verändert das Objekt. Weil s ebenso auf das selbe
                   # Objekt zeigt, ist der Zugriff über s ebenso verändert

puts s             # >> "Hello World"
puts t             # >> "Hello World"

Möchte man eine Kopie eines Objektes haben, nutzt man die Methoden dup oder clone. Beide machen sogenannte Shallow Copys, also flache Kopien. Das bedeutet, dass zwar das Objekt selber kopiert wird, nicht aber die Objekte, auf die in dem Objekt referenziert werden.

 
s = "Hello"
t = s.clone    # jetzt existieren 2 Objekte mit dem Inhalt "Hello"
t << " World"  # Das Objekt, worauf t zeigt, wird nur erweitert


puts "s:"
puts s            # Objekt 1 beinhaltet immer noch "Hello"
puts s.object_id  # und hat seine eigene ID

puts "t:"
puts t            # Objekt 2 beinhaltet "Hello World"
puts t.object_id


Hiermit haben wir also eine echte Kopie des String-Objektes gemacht. 2 identische Objekte existieren im Speicher und wenn man das eine verändert, verändert sich nicht das andere. Das es sich um eine flache Kopie handelt, fällt hier nicht auf, weil ein String auf kein anderes Objekt referenziert. Anders sieht es hier aus:

 
class MyClass
  def initialize
    @arr = [1,2,3]
    @s = "Hello"
  end
  attr_accessor :arr, :s
end

a = MyClass.new
b = a.clone

a.arr << 4
a.s << " World"

puts a.arr.join   # '1234' wie erwartet
puts a.s          # 'Hello World' wie erwartet

puts b.arr.join   # ups, auch '1234'
puts b.s          # und auch hier 'Hello World'

Obwohl wir hier also eine Kopie von a der Objekt-Referenz b zugewiesen haben, beeinflussen sich die beiden Objekte gegenseitig. Und genau das ist der Punkt, wo viele erstmal verblüfft sind.

Eine flache Kopie heißt, dass wir tatsächlich 2 Objekte im Speicher liegen haben. Jedoch zeigen beide Objekte auf ein und das selbe Array-Objekt und auf eine und dasselbe String Objekt. Das teilen sie sich sozusagen miteinander. Denn diese wurden nicht auch kopiert. Ruby kann von Haus aus generell nur flache Kopien erstellen. Um alles andere muss man sich selber kümmern. Oder man braucht eine entsprechende Bibliothek.

Hier ein Beispiel, wie eine tiefe Kopie für diese Klasse aussehen könnte:

 
class MyClass
  def initialize
    @arr = [1,2,3]
    @s = "Hello"
  end
  attr_accessor :arr, :s

  def deep_copy
    d = self.clone
    d.arr = @arr.clone
    d.s = s.clone
    d
  end
end

a = MyClass.new
b = a.deep_copy

a.arr << 4
a.s << " World"

puts a.arr.join
puts a.s

puts b.arr.join
puts b.s

Das klappt hier so einfach, weil unsere Klasse nur Basisobjekte enthält, die ihrerseits nicht wieder auf andere Objekte verweisen. Also brauchen wir nur alle Members ebenfalls zu clonen. Andernfalls müsste man auch diese anregen, sich tief zu kopieren.

Shallow Copy und Deep Copy sind Konzepte, die sich in jedem Grundlagenbuch über Objektorientierung finden lassen sollten. Wer also tiefer in das Thema einsteigen will, kann dort weiterlesen.

Manchmal kommt der Trugschluß auf, eine Zuweisung würde das Objekt doch kopieren.

 
s = "Hello"
t = s
t = "Hello World"

puts s  # s immer noch "Hello"
puts t  # t "Hello World"

Also doch eine Kopie? Sonst stände doch in s und t der gleiche String. Nein. Zuerst wird "Hello" angelegt und s zugewiesen. Die Referenz s zeigt also jetzt auf Objekt 1 mit dem Inhalt "Hello". Dann wird mit t = s der Referenz t ebenfalls dieses Objekt zugewiesen. Demnach zeigt jetzt t, genau wie s auf Objekt 1 mit dem Inhalt "Hello". Dann aber wird t ein neues Objekt 2 zugewiesen mit dem Inhalt "Hello World". Die Referenz s hingegen zeigt noch auf Objekt 1.

Übrigens sind dup und clone nicht völlig identisch. Beide machen sie flache Kopien, das ist definitiv so. Das PickAxe-Buch zeigt nebulös die Unterschiede auf. Viel Klarheit hat mir das aber nicht beschert. Matz persönlich schreibt dazu:

%div `clone' copies everything; internal state, singleton methods, etc.
`dup' copies object contents only (plus taintness status). apply=div%

Weblinks:

28.03.2005 :: YAML für Konfigurationsdateien

Permalink

Was gibt es nicht alles für Formate für Konfigurationsdateien. Unter Windows findet man hauptsächlich das Ini-Format. Unter Linux/Unix hat fast jedes Programm eine eigene Syntax. Oft wird eine Variable = Value Form benutzt. Doch Programme wie cron haben ihre ganz spezielle Syntax.

Der Trend geht auf jeden Fall hin zu hierarchisch strukturierten Konfigurationsdateien. Nur so kann man die reale Welt vernünftig abbilden und mit Komplexität umgehen.

Schaut man sich an, was es für hierarchische Config-Formate gibt, so sind die nur sparsam gesät. Die Ini-Files haben eine fixe 2-Ebenen Hierarchie über Abschnitte. Das hilft zwar schon, ist aber zu unflexibel für viele Probleme.

Das Apache Config Format kann Hierarchie und ist dem HTML angelehnt, welches ja auch hierarchisch strukturiert ist. Hier mal ein Beispiel:

 
ServerType standalone
LockFile   /var/lock/apache.lock

<Directory /home/*/public_html>
  AllowOverride FileInfo
  <Limit GET POST>
    Order allow,deny
  </Limit>
</Directory>

Wir können hier also beliebig tief schachteln und haben damit eine universelle hierarchische Konfigurationssprache, die man für alles mögliche einsetzen könnte. So ganz optimal empfinde ich sie von der Lesbarkeit nicht, man kann sich aber dran gewöhnen. Hier zeigt sich auch, dass Entwickler gerne auf das zurückgreifen, was sie gut kennen. Wer einen Webserver schreibt, entlehnt also seine Ideen dem HTML.

Eine weitere Möglichkeit ist die Verwendung von XML. Technisch sicherlich eine saubere Lösung, wenn man aber direkt mit einem Texteditor solche Dateien betrachtet und editiert, empfinde ich die auch nicht übersichtlich.

Manche gehen den Weg, und bauen die Config-Dateien in der Syntax der verwendeten Sprache auf, also z.B. so:

 
Module Config
  TmplPath = './tmpl'
  Files    = Array.new
  Files    << { Name => "test1.txt, 
                Perm => "0644" } 
end

So eine Config-Datei kann dann mit require eingebunden werden oder aber mit eval geparst. Diese Vorgehensweise ist einfach, elegant und flexibel, wirft aber auch einige Probleme auf.

Syntaxfehler lassen sich mitunter schwer lokalisieren. Auch kann man hier beliebig bösen Code mit einschmuggeln, was dann problematisch ist, wenn man den Menschen, die konfigurieren, nicht immer hundertprozentig vertrauen kann. Man gibt ihnen so viel Macht an die Hand. Manchmal kann so auch versehentlich falscher Code ins Programm kommen.

Ein weiteres Problem kommt auf, wenn die Konfigurierer keine Kenner der Sprache sind. Dieses Problem tritt häufig auf. PmWiki ist z.B. in php geschrieben und die Konfiguration geschieht auch über php-Statements. Viele, die PmWiki einsetzen, haben jedoch überhaupt keine Ahnung von php und machen entsprechend viele Konfigurationsfehler. Und diese lassen sich manchmal nur schwer lokalisieren oder deuten. Auch beginnen dann Freaks, die kompliziertesten Ausdrücke in die Konfiguration einzubauen, die dann normale Admins überhaupt nicht mehr überblicken können. Die Grenze zwischen Programm und Konfiguration verschwimmt.

Man sollte Menschen nicht Ruby, PHP oder sonstwas zumuten, die keine Programmierer sind. Es ist besser, eine einfache und übersichtliche Konfigurationssprache zu haben.

Ein schöne Lösung, die auch gut unter Ruby unterstützt wird, ist der Einsatz von YAML. YAML wurde entwickelt, um eine einfachere Sprache als XML zu haben. Gerade für den Einsatz in Konfigurationsdateien eignet sie sich gut.

Beispiel:

 
#
# Yaml Config File test1.yaml
#

---
TmplPath:      './tmpl'
DstPath:       './dst'
TmplPreString: '__wm__'

Files:

  - Name: 'test1.txt'
    Perm: '0640'
    Vars:
      bgcolor: '#ffffff'
      color:   '#000000'
      dst:     'http://www.wikidorf.de/reintechnisch'

  - Name: 'test/test.txt'
    Perm: '0640'
    Vars:
      text:    'Hello World'
      search:  'http://www.google.de'
      path:    '/var/www/mytest'

Für YAML ist das richtige Einrücken wichtig, ähnlich wie bei der Programmiersprache Python. Jede tiefere Ebene muss um typisch 2 Spaces eingerückt werden. Wichtig ist, dass alle Zeilen der gleichen Ebene gleich tief eingerückt sind. Tabs sind nicht erlaubt.

Übersichtlich ist so eine Konfig-Datei auf jeden Fall. Das obige Beispiel zeigt nur einen kleinen Bereich der Möglichkeiten. YAML ist sehr mächtig und so gut wie alles ist möglich, was man für Config-Dateien braucht, z.B. auch mehrzeilige Texte.

Das Ruby Programm, was diese Config Datei liest, ist ebenfalls sehr einfach:

 
require 'yaml'

# einlesen und parsen, Exception, wenn Fehler enthalten sind
h = YAML.load_file( 'test1.yaml' )

# Hash wieder als yaml rausschreiben
puts h.to_yaml


# Zugriff auf Hash
puts "--------------------------------"
puts "TmplPath: #{h['TmplPath']}"
puts "DstPath : #{h['DstPath']}"
puts "TmplStr : #{h['TmplPreString']}"

h['Files'].each do |f|
  puts "FName: #{f['Name']} Perm: #{f['Perm']}"
end


Wer also nicht immer wieder anfangen will, für seine Programme sich neue Konfigurations-Formate auszudenken (und andere damit zu ärgern), sollte das universell einsetzbare YAML verwenden. Ich denke, es ist auch leicht genug zu überblicken, damit auch nicht YAML-Eingeweihte die Syntax verstehen. Eine wunderbare Referenz gibt es übrigens auf der YAML-Homepage.

Author der Ruby-Bibliotheken ist Why the Lucky stiff, der uns auch mit dem köstlichen Ruby-Buch Why's Guide to Ruby beglückt hat. Die sprachübergreifende Yaml-Bibliothek heißt übrigens syck. Sie ist für Perl, Python und PHP verfügbar.

Neben YAML gibt es eine Reihe weiterer Versuche, einfacher, gut lesbarer Datenbeschreibungssprachen. Dazu gehört OGDL und JSON. Wenn man mal nach der Trefferquote von google geht, scheint YAML die meiste Verbreitung zu haben (YAML: 236.000, OGDL: 2001, JSON: 36300 (Stand: 30.03.2005)). Trotzdem ist mir YAML bisher in der starken Verbreitung nur im Zusammenhang mit Ruby bekannt. Es wäre schön, wenn sich irgendwas mal in großen Stil durchsetzen würde. YAML ist sicherlich ein guter Kandidat. Jetzt kommt es vielleicht darauf an, dass auch andere Sprachen vorzügliche YAML-Unterstützung bekommen. Einiges ist ja bereits realisiert.

Dank an Felix für die Infos über JSON.

Weblinks:

25.03.2005 :: gsub Backreference Falle

Permalink

Wenn man mit gsub Muster austauscht, kann man auf Teilmuster mit \1..\n referenzieren. Ein Beispiel:

 
puts "Das Spiel stand 5:2".gsub( /([0-9]):([0-9])/, '\2:\1' )

Diese Substitution dreht den Spielstand auf 2:5 um.

Die Falle ist nun: Wenn man für den Substitutions-String "\2:\1" schreibt, also doppelte Anführungsstriche, dann wird \2 und \1 bereits zuvor durch den Binärwert 0x01 und 0x02 ausgetauscht. Das liegt einfach daran, dass wir es hier mit einer Substitution auf mehreren Ebenen zu tun haben.

Auf der normalen String-Substitutionsebene von Ruby soll nichts substituiert werden, folglich muss man die einfachen Anführungsstriche benutzen. Oder aber "\\2:\\1".

Der methode gsub wird dann wirklich der String '\2:\1' übergeben. Und dort wird dann der String nochmals interpretiert und alle \x Befehle substituiert. Diese Substitution hat nichts mit der Sprache Ruby zu tun, es ist die Implementierung der Methode gsub. Genauso könnte man eine eigenen Methode schreiben, die das Muster '\1' als einen bestimmten Befehl interpretiert.

24.03.2005 :: Interessanter Ruby Artikel auf DevSource.com

Permalink

Five Things You Didn't Know You Could Do with Ruby

22.03.2005 :: Interview zu Skriptsprachen

Permalink

Ein Interview mit ein paar VIP's verschiedener Skriptsprachen (PHP, TCL, Perl, Python, Ruby) über deren Bedeutung im Moment und in den nächsten Jahren.

The State of the Scripting Universe

21.03.2005 :: AES Verschlüsselungs Experimente

Permalink

Die letzten Tage habe ich viel mit AES Verschlüsselung unter Ruby experimentiert. Hier sind die Ergebnisse: RSAESEncryption

21.03.2005 :: IO-Puffer leeren mit flush

Permalink

Ruby puffert Ausgaben auf ein IO-Objekt. Das hat den Sinn, dass nicht wegen jedem Byte auf die Platte zugegriffen wird, sondern ein Block in einem Rutsch effizienter weggeschrieben werden kann.

Pufferung gilt auch für die IO-Objekte $stdout und $stderr. Und da ist es mitunter störend. Ein print "String" wird nämlich erst dann ausgegeben, wenn eine abschließende Zeilenschaltung erfolgt. Zumindest ist das auf Linux-Systemen so, bei Windows scheint keine Pufferung stattzufinden.

Ein Beispiel:

 
10.times do
  print "."
  sleep 1
end

Jede Sekunde soll ein Punkt ausgeben werden, der z.B. den Fortschritt eines Skriptes anzeigt. Jedoch wird hier 10 Sekunden lang nichts ausgegeben, weil die Ausgabe gepuffert ist. Erst, wenn das Programm verlassen wird, wird der Puffer geleert. Man muss in diesem Beispiel also den Puffer von Hand leeren.

 
10.times do
  print "."
  $stdout.flush
  sleep 1
end


19.03.2005 :: (1..7).each { |i| puts ' '*i << 'Schleifen in Ruby' }

Permalink

In Sachen Schleifen unterscheidet sich Ruby stark von anderen Sprachen. In vielen Sprachen finden sich ein paar wenige Standard-Möglichkeiten dafür: while, for oder until-Schleifen.

Ruby dagegen macht vieles über Iteratoren. Die Idee dabei ist, dass ein Objekt bzw. eine Klasse schon sinnvolle Schleifenmöglichkeiten mitbringt. Das erspart oft Tippaufwand und macht den Code leichter lesbar. Ein typisches Beispiel ist ein Array, wo man über alle Elemente iterieren will:

 
# Array anlegen
a = [ 10, 20, 30 ]

# Array ausgeben
a.each do |i|
  puts i
end

Die Methode each bzw. foreach oder Abwandlungen wie each_byte, each_index usw. findet man sehr häufig. Überall, wo eine Menge von Dingen da ist, ob Elemente eines Arrays oder Zeilen einer Datei, macht es auch oft Sinn, darüber zu interieren.

Weil sich über solche Methoden vieles erschlagen kann, findet man in Ruby-Programmen seltener Konstrukte wie while oder for, die es jedoch auch gibt.

 

# -- while Schleife
i = 1
while i < 7
  puts "Hello World"
  i += 1
end

# -- until Schleife
i = 1
until i >= 7
  puts "Hello World"
  i += 1
end

# -- for Schleife
for i in 1..7
  puts "Hello World"
end


Letzteres ist im Grunde eine oft überflüssige Form, eigentlich wird nämlich nur über die Expression nach "in" iteriert. In diesem Fall haben wir ein Range-Objekt mit der Ausprägung "1..7" gewählt. Damit ist diese Schleife fast identisch hiermit:

 
(1..7).each do
  puts "Hello World"
end

Die Ausnahme ist, dass im do-end Block definierte Variablen außerhalb nicht mehr gültig sind, dagegen sind Variablen, die innerhalb eines for-end Blockes definiert werden, auch nach Verlassen der Schleife gültig.

(1..7) mag für Umsteiger etwas komisch aussehen. Im Grunde ist es nichts weiter, als ein Range-Objekt als Literal geschrieben. Zusätzlich wurde es in Klammern verpackt, weil der folgende .each mehr bindet.

Genauso hätten wir also auch dies schreiben können:

 
r = Range.new( 1, 7 )
r.each do
  puts "Hello World"
end

# oder 
r.each do |i|
  print "#{i} "    # >> 1 2 3 4 5 6 7
end

# oder auch
for i in r
  puts "Hello World"
end

# sowas geht auch
a = [10, 20, 30]
for i in a
  puts i
end

# oder so
for i in [10,20,30]
  puts i
end

Range-Objekte sind in Ruby etwas ganz Essentielles. Sozusagen eine Basis-Klasse, von denen in vielen anderen Klassen Gebrauch gemacht wird.

Integer besitzen die Methode upto und downto. Damit lassen sich ganz ähnliche Schleifen aufbauen, wie mit Range-Objekten:

 
1.upto(7) do |i|
  print "#{i} " # >> 0 1 2 3 4 5 6 7
end

#funktioniert genauso wie
(1..7).each do |i|
  print "#{i} " # >> 0 1 2 3 4 5 6 7
end

Welche Form man verwendet, ist hier Geschmackssache.

Eine weitere Schleifenmethode ist die Methode times, ebenfalls eine Iterator-Methode, die z.B. bei Integer existiert. Sie läuft immer mit dem Wert 0 los und kommt bei x-1 als letzten Wert an.

 
7.times do
  puts "Hello World"
end

#oder
7.times do |i|
  print "#{i} " # >> 0 1 2 3 4 5 6
end

Mit Integer#step kann man Schleifen mit anderer Schrittweite erstellen:

 
# num.step( Endwert, Step ) { ... }

1.step( 10, 3 ) do |i|
  print "#{i} " # >> 1 4 7 10
end

Iteratormethoden kann man natürlich auch selber implementieren. Wenn man also eigene Klassen schreibt und irgendwo macht ein Iterator Sinn, dann sollte man so eine Schnittstelle anbieten.

18.03.2005 :: Kleiner Fehler in irb unter Windows

Permalink

Die Zeicheneingabe im irb unter Windows ist etwas gestört, betrifft derzeit Ruby 1.8.2-14. Eckige und geschweifte Klammern gehen z.B. nicht. Das Problem ist schon länger bekannt, es traut sich aber irgendwie niemand dran, dass man ordentlich zu bereinigen. Es lässt sich aber händisch zurechtbiegen: In sein Homeverzeichnis (c:/Dokumente und Einstellungen/<user>) legt man eine Text-Datei namens .inputrc. Leider ist Windows dämlich und lässt so einen Dateinamen mit Punkt am Anfang im Explorer nicht zu. Auf der Kommandozeile geht es aber:

 
c:\Dokumente und Einstellungen\rubyist> echo . >.inputrc

In diese Datei fügt man nun folgenden Inhalt ein:

 
"\M-[": "["
"\M-]": "]"
"\M-{": "{"
"\M-}": "}"
"\M-\\": "\\"
"\M-|": "|"
"\M-@": "@"
"\M-~": "~"

Dann braucht es noch die Variable Home im Environment, mir ist nichts besseres eingefallen, als die in die autoexec.bat zu schreiben (eigentlich nicht so günstig, wenn mehrere Accounts auf dem Rechner arbeiten.)

 
SET HOME=C:\Dokumente und Einstellungen\rubyist

Nach neuem Booten sollte HOME existieren, was sich mit "set" abfragen lässt. Dann sollte auch der irb korrekt funktionieren.

Weblinks:

11.03.2005 :: md5sum erzeugen

Permalink

Das Erzeugen von md5sum Prüfsummen ist mit Ruby über 'digest/md5' recht einfach. Folgendes Programm gibt die md5sum des übergebenen Files im gleichen Format zurück, wie es das Linux-Tool md5sum tut.

 
# rmd5sum.rb         Version: 2005/03/11
#
# Winfried Mueller, www.reintechnisch.de
#
# use: rmd5sum file


require "digest/md5"

fn = ARGV[0]

d = Digest::MD5.new

File.open( fn, "rb" ) do |f|
  while !f.eof?
    d << f.read(102400)  
  end
end

printf "%s *%s", d.hexdigest, fn 

Das Programm liest 100KB Blockweise ein und übergibt dies dann dem MD5 Objekt. Über << kann man diesem einen Strom von Strings übergeben, aus der die MD5 errechnet wird.

Wie schnell ist hierbei Ruby? Auf einem 500 MHz Windows System habe ich hierzu eine etwa 1 GB große Datei getestet. Einmal mit rmd5sum.rb und einmal mit dem Gnu Tool md5sum. Ruby hat dafür 120 s gebraucht, md5sum brauchte 50 s. Das ist ganz akzeptabel. MD5sum von Ruby ist ein externes C-Modul, deshalb ist es recht schnell. Evtl. kann man es noch schneller machen, wenn man die Blockgröße höher wählt. Das braucht dann natürlich mehr Hauptspeicher.

11.03.2005 :: Mysterium SCRIPT_LINES__

Permalink

Ein echter Insidertipp: Wenn man am Anfang eines Skripts der Konstanten SCRIPT_LINES__ ein Hash zuweist, werden in diesem alle mit Require eingebundenen Skripte abgelegt. Der Key ist der Dateiname, der Value ist das komplette Skript. Folgendes Skript gibt die Dateinamen aller eingebunden Dateien zurück:

 
SCRIPT_LINES__ = {}

require 'csv'
require 'find'

print SCRIPT_LINES__.keys.join( "\n" )


Weblinks:

11.03.2005 :: Rekursives Durchwandern von Verzeichnissen

Permalink

Gib mir alle Dateien im aktuellen Verzeichnis und allen Unterverzeichnissen - eine oft gebrauchte Sache. In Ruby hab ich das schonmal selber programmiert, mit einer rekursiv aufgerufenen Methode. Da wusste ich noch nicht, dass Ruby sowas schon eingebaut hat. Dafür gibt es eine ziemlich obskure Möglichkeit, die man erstmal entdecken muss.

 
a = Dir['**/*']
puts a.join('\n')

Dies schreibt alle Verzeichnisse vom aktuellen Verzeichnis aus ins Array a und gibt es dann aus. Was ist denn dieses **/*? Der Stern steht ja normal für alle Dateien in einem Verzeichnis. Doppelstern gibt es normal nicht. In Ruby steht es für "0-n Verzeichnisse".

Davor kann man natürlich auch einen anderen Pfad hängen:

 
a = Dir['/etc/**/*']
puts a.join('\n')

Dies listet dann alle Dateien im Verzeichnis /etc inkl. aller Unterverzeichnisse und deren Dateien. Wenn man nur Files ohne Verzeichnisse möchte, muss man aus dieser Ergebnismenge noch alles herausfiltern, was keine reguläre Datei ist.

 
a = Dir["/etc/**/*"]

a.each do |file|
  puts file if File.file?( file )
end

Das war eine Übung in Sachen: Schreiben sie ein Ruby Programm mit 5 Zeilen wo 5 mal das Wort file drin vorkommt :-)

Natürlich kann man ein ** auch irgendwo dazwischen verwenden:

 
a = Dir["/var/**/test/test.txt"]

# -> findet z.B.:
#    /var/lib/test/test.txt
#    /var/test/test.txt
#    /var/spool/samba/test/test.txt
#    /var/log/apache/test/test.txt


Leider funktioniert dieses Special-Globbing mit '**/*' nicht bei Dir.foreach(). Mann muss sich also erstmal alles in ein Array reinpfeifen, um es dann zu bearbeiten. Bei sehr großen Verzeichisbäumen braucht das viel Hauptspeicher.

Neben dieser Methode gibt es auch noch die wunderbare Standardbibliothek find. Damit kann man im Grunde genau das erreichen, was man mit Dir.foreach() könnte, wenn es globbing beherrschen würde:

 
require 'find'

Find.find( "/etc" ) do |file|
  puts file if File.file?( file )
end

Interessant ist hier noch, dass man find auch mehrere Verzeichnisse übergeben kann, die dann alle rekursiv durchwandert werden. Funktioniert übrigens alles auch schon bei Ruby 1.6.7. Dies ist ja noch auf vielen Debian Woody Kisten der Standard.

Weblinks:

10.03.2005 :: Interessante Optionen für Einzeiler

Permalink

Einige Ruby Kommandozeilenoptionen sind speziell für Einzeiler und kurze Programme gedacht. Sie ermöglichen oft gebrauchte Funktionalität einzuschalten. Hier mal eine kleine Liste:

 
-n    while gets();...end loop. Eingabe wird Zeile für Zeile nach $_
      weitergegeben. 
      Bsp: ruby -ne 'BEGIN{$x=0};$x+=1;sprintf( "%3d %s", $x,$_ )' file

-p    ähnlich wie -n, jedoch wird standardmäßig jede eingelesene Zeile 
      ausgedruckt, jedoch kann man über die Veränderung von $_ diese
      Ausgabe abändern. Ähnlich wie sed.
      Bsp: ruby -pe '$_="Leerzeile" if /^$/' file

-a    Autosplit. Hiermit wird $_ standardmäßig bei jedem Space gesplittet
      und in $F abgelegt. Folgendes Beispiel listet alle Wörter einer
      Datei:
      ruby -ane 'puts $F'

-Fpattern Hiermit kann für Autosplit ein pattern festgelegt werden, bei
          dem gesplittet wird.

-i[ext] In Place Editing, also lese von einem File und schreibe auch dort
        wieder zurück. Dieses Beispiel liest file und ersetzt jede 
        Leerzeile durch das Wort "Leerzeile", file wird dabei verändert.
        Ein Backup wird in file~ abgelegt.
        ruby -i~ -pe '$_="Leerzeile\n" if /^$/' file

Eine Taschenrechner hat man übrigens mit dieser Form:

 
ruby -e 'p 294.0+494.0/22.0+39.0'

Aber aufpassen: Nicht mit Integer durcheinanderkommen:

 
ruby -e 'p 11/2'   # >> 5, nicht 5.5
ruby -e 'p 11.0/2' # >> 5.5

Und ein Ruby-Grep geht dann so:

 
ruby -ne 'print if /muster/' files

Wer mehr Anregung braucht, google mal nach "Ruby One Liners". Oder hier im Ruby-Garden

10.03.2005 :: Ausgabe Warnungen einschalten

Permalink

Wenn man Skripte testet, sollte man ruhig mal die Warnungen einschalten.

 
#!/usr/bin/ruby -w
...

10.03.2005 Gültigkeit Variablen im Block

Permalink

Ein heute gebasteltes Programm stellte mich vor die Frage, ob eine Variable, die in einem Block definiert wird, auch außerhalb des Blockes gültig ist. Es scheint so zu sein, wie dieses Beispiel zeigt:

 
begin
  testvar = "Hello World"
rescue
  puts "Nur so, um ein rescue Pfad zu haben"
  raise
end
GC.start
sleep 3

puts testvar # >> Hello World

Die Variable testvar wird also erst im begin/end Block definiert, ist aber trotzdem von außerhalb erreichbar/vorhanden. Sicherheitshalber habe ich mit GC.start den Garbage Collector gestartet, damit wirklich alle Objekte im Speicher entfernt werden, die nicht mehr referenziert sind.

Anders sieht es natürlich bei Codeblöcken aus, hier ist die Variable außen nicht gültig:

 
10.times do
  a = "Ich bin a"
end

puts a # >> Error

08.03.2005 :: Ruby on Rails

Permalink

Das ist schon manchmal unglaublich, wie etwas zu einem totalen Hype wird. Überall lese ich von Ruby on Rails, ein Framework zur Entwicklung von Webanwendungen in Ruby. Nicht nur in der Ruby-Szene ist das derzeit ein ganz heißes Stichwort. Viele kommen über dieses Framework überhaupt das erste mal in Kontakt mit Ruby. Google wirft heute unter "ruby on rails" 144.000 Treffer aus.

Wie geht das, dass auf einmal Dinge so ganz groß rauskommen, was keiner geahnt hätte? Natürlich muss das Produkt erstmal irgendwie cool sein. Es muss Emotionen wecken, es muss irgendwas auf geschickte Weise lösen, etwas tun, worauf die Welt gewartet hat.

Daneben gehört aber noch einiges anderes dazu. Wenn die Sache erstmal gezündet hat, muss genügend Power da sein, die entstehende Begeisterungs-Welle auch zu bedienen. Ordentliche Webseite, ständig Neuigkeiten, schnelle Weiterentwicklung, Leute, die neue Dokumentationen und Zusatzprogramme schreiben. Auch braucht es Leute in machtvollen Positionen, die sich ebenfalls für dieses Produkt begeistern, genügend Potenzial sehen und durch ihre Stellung viel in Bewegung setzen können. Artikel in Zeitschriften, Vorstellung auf Konferenzen, Präsentationen...

Es braucht vieles, damit so ein Hype entsteht und ich denke, man kann das auch gar nicht so planen. Es passiert einfach und keiner weiß so richtig, warum. Denn es gibt auch viele Software, die ebenfalls cool ist, die aber nicht so zündet. Manchmal sind die Fehler, die gemacht werden offensichtlich, oft aber auch nicht.

Wie auch immer, Ruby on Rails gibt der Sprache Ruby einen ordentlichen Aufwind. Gerade im Bereich Webentwicklung. Die Auswirkungen könnten sein, dass Ruby bald zur Standardausrüstung der Webspace-Provider gehört. Das ist ja heute noch ein großes Problem. Es gibt nur sehr wenige Provider, die auch Ruby als Skriptsprache anbieten. Wenn dem erstmal so ist, dann könnte sich Ruby enorm verbreiten.

Ich bin gespannt.

01.03.2005 :: Atomzeituhr bitte...

Permalink

Das war ja mal wieder ein netter Fund. Ein sntp-Ruby Module, mit dem man sich die hochgenaue Zeit von einer Atomuhr übers Internet holen kann. Das Modul lädt man sich über das RAA-Archiv hier runter. Es sind nur knapp 4KB reiner Ruby-Code. Entweder installiert man es mit dem mitgelieferten Setup oder packt sntp.rb irgendwo händisch ins rechte Verzeichnis. Dann kann es mit einem einfachen Test losgehen. Wir benutzen den Zeitserver der Physikalisch-Technischen Bundesanstalt in Braunschweig. Das ist ein sehr gängiger Server, wenn es um Atomzeit geht.

 
# -- test1.rb

require 'sntp'

ntp = Net::SNTP.new
puts ntp.get('ptbtime2.ptb.de')

Gestartet mit ruby test1.rb und schon haben wir die aktuelle Atomzeit auf dem Bildschirm. So einfach geht das. Vorausgesetzt, der Internetzugang funktioniert und die Firewall lässt Ruby per UDP auch passieren.

Jetzt gefällt das ausgegebene Datumsformat nocht nicht. Also formatieren wir das mal schöner. Die Methode get gibt ja ein Time-Objekt zurück und was man damit alles anstellen kann, liest man z.B. in einem Ruby Referenzhandbuch nach.

 
require 'sntp'

ntp = Net::SNTP.new
t = ntp.get('ptbtime2.ptb.de')

puts t.strftime( "%d.%m.%Y %H:%M:%S" )

Wer C/C++ oder PHP kennt, dem dürfte strftime auch bekannt sein. Als nächstes wollen wir das Datum und die Uhrzeit eines Windows-Systems damit stellen:

 
require 'sntp'

ntp = Net::SNTP.new
t = ntp.get('ptbtime2.ptb.de')

s_date = t.strftime( "%d.%m.%Y" )
s_time = t.strftime( "%H:%M:%S" )

system( "date #{s_date}" )
system( "time #{s_time}" )

Natürlich sollte man in einem realen Programm ein wenig Fehlerbehandlung machen. Wenn keine Verbindung ins Internet ist und get nicht ausgeführt werden kann, wird übrigens eine Exception ausgelöst. Im obigen Beispiel sollte also auch im Fehlerfall nichts gravierendes schiefgehen, wie z.B. eine falsch gestellte Uhr. Wer auf Nummer Sicher gehen will, lässt Zeitverstellung hierüber nur zu, wenn die Abweichung eine bestimmte Differenz nicht überschreitet, z.B. +- 15 Minuten. Das Time-Objekt kann hierzu wunderbar mit Zeiten rechnen und Vergleiche mit Zeiten anstellen.

28.02.2005 :: Doppel chomp!

Permalink

String#chomp! isst ja den Zeilentrenner weg. Ganz ähnlich wie es auch String#chop! tut. Wenn man wirklich nur den Zeilentrenner essen will, sollte man chomp! verwenden, weil dies sicherer ist. Dieses isst nämlich nur Zeichen, die auch Zeilentrenner sind und sonst nichts.

Mit chomp! gibt es ein kleines Kompatibilitätsproblem. Bei Ruby 1.6.7 isst chomp! nämlich nur ein Zeilentrennerzeichen, auch wenn man z.B. "\r\n" bei einem DOS-File hat. Es isst genau das Zeichen, was in $/ definiert ist. Default ist das 0x0A bzw. \n. Bei Ruby 1.8.2 habe ich es getestet und da isst chomp! beide Zeichen weg, wie man es auch erwarten würde. Es hat also eine eingebaute Automatik, auch ein \r am Ende wegzuessen.

String.chop! isst seit jeher beide Zeichen weg, also auch \r, wenn nachfolgend \n da ist.

Von daher gilt: Will man chomp! unter Kompatibilitätsaspekten richtig einsetzen, dann sollte man es doppelt aufrufen, beim zweiten mal mit chomp!( "\r" ).

 
s="Hello World!\r\n"
s.chomp!
s.chomp!( "\r" ) # nötig bei Ruby < 1.8

s.each_byte do |b|
  printf "%2.2X ", b
end
puts

28.02.2005 :: Wiki Software und Weblog in Ruby

Permalink

Wir waren die letzten Tage ein wenig am recherchieren, ob es eine vernünftige Wiki-Software gibt, die in Ruby geschrieben ist. Leider sieht es da noch nicht so toll aus. Es gibt ein paar vielversprechende Ansätze, denen aber noch wichtige Merkmale fehlen, um sie öffentlich produktiv einzusetzen. Interessant sind RuWiki von Austin Ziegler und Instiki unter der Betreuung von David Heinemeier Hansson (Projektbetreuer des Senkrechtstarters RubyOnRails).

Als Maßstab für ein gutes Wiki nehme ich gerne PmWiki, MoinMoin-Wiki und das Mediawiki.

Für ein offenes Wiki ist es wichtig, dass es eine gute Seitenhistorie hat, über die man beliebige ältere Versionen zurückholen oder miteinander vergleichen kann. Bei den Ruby-Wikis sieht es da schlecht aus. Diese Funktionalität fehlt. Das ist eigentlich das größte Killer-Kriterium.

Aber auch sonst erscheinen mir die Lösungen in vielerlei Hinsicht noch nicht rund, noch in einem sehr frühen Stadium. Wahrscheinlich liegt das auch daran, dass die Verbreitung gar nicht so einfach möglich ist, wie bspw. bei einem PHP-Wiki. Die meisten Webhoster unterstützen bspw. die Sprache Ruby überhaupt nicht. Erst wenn die Möglichkeit in der Breite da ist, auch Ruby-Skripte laufen lassen zu können, werden wohl auch die Ruby Web-Applikationen sich stärker entwickeln.

Das sich da die Zeiten ziemlich schnell ändern können, zeigt ja die Sprache Python. Ich kann mich noch an die Zeit vor ein paar Jahren erinnern, da kannten Python nur ein paar Insider. Und an Unterstützung bei irgendeinem Provider war gar nicht zu denken. Mittlerweile bietet nahezu jeder Hoster auch Python an.

Ich war die letzten Tage auch ein wenig am Suchen, was ein gutes Weblog in Ruby angeht. Auch da fand ich nichts, was mich hätte überzeugen können. Rublog fehlt z.B. die Möglichkeit, es übers Web mit Inhalt zu füllen. Die letzten Tage lese ich öfters mal von Hobix, was wohl doch mehr kann, als es mir die Webseite präsentierte. Über Design lässt sich sicherlich streiten, mich hat diese ausgefallene Seite aber eher abgeschreckt. Mittlerweile habe ich aber auch aufgeräumtere Seiten gesehen, die mit Hobix umgesetzt wurden. Aber auch Hobix kann man wohl nicht übers Web mit Inhalten füllen.

Da gibt es also noch viel zu tun. An so Projekte wie z.B. Wordpress, textpattern oder b2evolution reicht das alles nicht heran.

Nachtrag 02.03.2005: Ein weiteres Ruby-Weblog-Skript ist mir über den Weg gelaufen: Blogtari. Die Oberfläche erscheint mir gut aufgeräumt. Ein Handbuch im pdf-Format (Release Notes) hab ich auch nach ein wenig Suchen gefunden. Weblogger neigen ja dazu, alles in ein Weblog zu packen, auch Dinge, die dafür nicht geeignet sind. Von einer Projekthomepage erwarte ich z.B., dass ich irgendwo ein feststehendes Menü habe, wo ich alles wichtige zum Projekt erreichen kann (Download, Documentation, Changelog, Ressourcen, ...). In Weblogs muss man sich das dagegen oft zusammensuchen. Gib mir einen Hammer und jedes Problem wird zu einem Nagel...

Zurück zum Handbuch, es gibt einen kurzen technischen Überblick, jedoch auch keine Antwort auf meine Frage, ob man denn nun webbasiert Artikel verfassen kann. Wahrscheinlich nicht.

Ein Kandidat, den man sich mal wieder anschauen könnte, ist es allemal. Momentan erfüllt es aber noch nicht das, was ich bräuchte.

25.02.2005 :: Klassenvariablen, Klasseninstanzvariablen und Konstanten

Permalink

Wer Klassenvariablen aus anderen Sprachen kennt (z.B. C++), wird erstmal verwundert sein, wie sich Ruby verhält.

 
class Form
  @@ftype = "Form"
  def self.ftype
    return @@ftype
  end
  def ftype
    return @@ftype
  end
end

class Dreieck < Form
  @@ftype = "Dreieck"
end

class Kreis < Form
  @@ftype = "Kreis"
end

d = Dreieck.new

puts Form.ftype     # >> Kreis 
puts Dreieck.ftype  # >> Kreis
puts d.ftype        # >> Kreis
puts Kreis.ftype    # >> Kreis

Alle abgeleiteten Klassen greifen in Ruby auf ein und die selbe Klassenvariable zu. In C++ hätte jede abgeleitete Klasse eine eigene Instanz dieser Variablen.

Neben diesen Klassenvariablen gibt es die Klasseninstanzvariablen, die sich in etwa so verhalten, wie man das von C++ gewöhnt ist. Und dann kommt auch das dabei heraus, was man erwartet hat.

 
class Form
  @ftype = "Form"
  def self.ftype
    return @ftype
  end
  def ftype
    return self.class.ftype
  end
end

class Dreieck < Form
  @ftype = "Dreieck"
end

class Kreis < Form
  @ftype = "Kreis"
end

d = Dreieck.new
puts Form.ftype      # >> Form
puts Dreieck.ftype   # >> Dreieck
puts d.ftype         # >> Dreieck
puts Kreis.ftype     # >> Kreis

Wo bei Klassenvariablen ein @@ vorangestellt wird, wird bei Klasseninstanzvariablen nur ein @ vorangestellt, wie bei normalen Instanzvariablen. Nur der Kontext, wo sie definiert werden, legt fest, dass es sich um eine Klasseninstanzvariable handelt.

Jetzt haben wir allerdings ein Problem. Aus einer Methode des Objekts können wir diese Klasseninstanzvariablen nicht erreichen. Denn in diesem Kontext bedeutet @ ja Instanzvariable. Wir können also nicht schreiben:

 
class Form
  @ftype = "Form"
  def self.ftype
    return @ftype
  end
  def ftype
    return @ftype    # >> Fehler, Zugriff auf Instanzvariable, 
                     #    nicht auf Klasseninstanzvariablen
  end
end

Eine Möglichkeit wäre, dass es auf jeden Fall eine Klassen-Methode gibt, über die man dann zugreifen kann. So ist das oben auch gelöst. In der Methode ftype müssen wir dann über self.class.ftype eine dynamische Bindung herstellen. Einfach nur Form.ftype würde auch bei abgeleiteten Klassen immer Form.ftype aufrufen und nicht Dreieck.ftype oder Kreis.ftype.

Hier zeigt sich auch schön das dynamische Verhalten der Sprache. Wer von C++ kommt, kann erstmal schwer verstehen, wie man dynamisch über self.class.ftype eine Funktion aufrufen kann. Eine Bindung, die erst während der Laufzeit bestimmt wird.

Es gibt noch eine weitere Möglichkeit, obiges Problem zu lösen. Mit Konstanten.

 
class Form
  FTYPE = "Form"
  def self.ftype
    self::FTYPE
  end
  def ftype
    self.class::FTYPE
  end
end

class Dreieck < Form
  FTYPE = "Dreieck"
end

class Kreis < Form
  FTYPE = "Kreis"
end

d = Dreieck.new

puts Form.ftype     # >> Form
puts Dreieck.ftype  # >> Dreieck
puts d.ftype        # >> Dreieck
puts Kreis.ftype    # >> Kreis

Auch hier müssen wir dynamisch auf die Konstante FTYPE zugreifen. Sonst würde auch in den abgeleiteten Klassen auf FTYPE der Basisklasse zugegriffen. In der Klassenmethode tut es ein self::FTYPE (self ist hier ein Klassenobjekt vom Typ Form, Dreieck oder Kreis) und in der Objektmethdoe self.class::FTYPE. Nur class::FTYPE geht wg. Namenskonflikt (Klassendefinition) nicht.

25.02.2005 :: Skript zur Assemblierung von Ruby-Code

Permalink

Ich hab mal ein Skript hier Online gestellt, was ich gerne für kleine Ruby-Skripts benutze. Einerseits lagere ich gerne Klassen in Bibliotheken aus, andererseits nervt es, wenn man bei der Software-Verteilung und Installation immer mehrere Dateien installieren muss. Eine kompakte Datei installieren und alles soll laufen, ist meine Idealvorstellung.

Dieses Skript baut mir aus mehreren verstreuten Dateien ein kompaktes Ruby-File.

Hier ist der Quellcode...

24.02.2005 :: Namensräume

Permalink

Möchte mal ein wenig Orientierung zu Namensräumen geben.

Konstanten (erster Buchstabe groß) im Toplevel definiert, also außerhalb jedes Moduls und jeder Klasse, können über ::Konstante überall erreicht werden. Auch ohne den :: Operator ist das möglich, insofern diese Variable nicht im aktuellen Module/Klasse überladen wurde.

Konstanten, die in einem Module oder einer Klasse definiert sind, können über Module::Konstante überall erreicht werden. In der Klasse/Module selber können sie direkt mit Konstante erreicht werden.

 
A = "A:Aussen"

module MyModule
  A = "A:InModule"
  puts ::A
  puts A
end

class MyClass
  A = "A:InClass"
  puts ::A
  puts A
end

puts A
puts MyModule::A
puts MyClass::A 

#Ergebnis:
#A:Aussen
#A:InModule
#A:Aussen
#A:InClass
#A:Aussen
#A:InModule
#A:InClass

Globale Variablen (mit einem $ am Anfang) sind überall direkt erreichbar.

Lokale Variablen (alle, deren erster Buchstabe klein ist), sind nur im aktuellen Kontext erreichbar. Das gilt auch für den Toplevel, also außerhalb jedes Modules oder jeder Klasse. Auf diese hat man nur im Toplevel Zugriff. Es sei denn, man geht über eval und binding (siehe Eintrag hier vom 22.02.05).

24.02.2005 :: reguläre Suche im Ruby-Wiki

Permalink

Im Ruby-Wiki auf http://rubygarden.org/ruby?HomePage kann man in der Suche mit echten regulären Ausdrücken suchen. Damit ist diese Suche sehr leistungsfähig. Im Hintergrund werkelt allerdings Perl und nicht Ruby, von daher sollte man auch Perl-Kompatible reguläre Expressions verwenden. Perl und Ruby ist da aber weitgehend kompatibel.

24.02.2005 :: Verwendung Nicht-Alphanumerischer Zeichen in Ruby

Permalink

Eine Übersicht, welche Bedeutung alles mögliche hat, was nicht alphanumerisch ist, findet man hier: http://rubygarden.org/ruby?FunnySymbolsInCode

Hauptsächlich sind das Operatoren. Eine wunderbare Hilfe, die man mal eingehender studieren sollte.

Am verwunderlichsten fand ich den Operator ?c, der den ASCII Wert des Zeichens c zurückgibt. Den kannte ich zuvor nicht.

23.02.2005 :: Anfängertipp: Codepassagen ausdokumentieren

Permalink

Beim Testen muss man öfters mal ganze Codepassagen ausdokumentieren. Hierfür nutzt man das in Ruby eingebettete RD Dokumentations-Format:

 
=begin
Quellcode,
der zwischen =begin und =end (am Zeilenanfang) steht,
wird nicht ausgewertet.
=end

23.02.2005 :: Windows Registry auslesen mit Win32/Registry.rb

Permalink

Die Welt ist einfach geworden. Ich erinnere mich noch an Ruby-Zeiten, wo der Zugriff auf die Registry einige Klimmzüge bereitete. Jetzt kann man mit require 'Win32/registry' komfortabel mit der Registry arbeiten.

Hier mal ein Beispiel für einen lesenden Zugriff:

 
require 'win32/registry'

commonFilesDir = nil
productId      = nil
programFilesDir = nil
dir = ""

key   = Win32::Registry::HKEY_LOCAL_MACHINE
subkey= 'SOFTWARE\Microsoft\Windows\CurrentVersion'
Win32::Registry.open( key, subkey ) do |reg|
  commonFilesDir = reg['CommonFilesDir']
  productId      = reg['ProductId']
  programFilesDir = reg['ProgramFilesDir']
  reg.each_value do |name, type, data|
    dir << sprintf( "%25s %10s %30s\n", name, type.to_s, data.to_s )
  end
end

puts "CommonFilesDir : " << commonFilesDir
puts "ProductId      : " << productId
puts "ProgramFilesDir: " << programFilesDir
print dir

Man kann sowohl auf einzelne Werte mit reg['Value'] zugreifen, wie auch über alle Werte einen Iterator laufen lassen. Ich denke, einfacher geht es nicht mehr.

Weblinks:

22.02.2005 :: String mit Zeilennummer: each_with_index

Permalink

 
s="Hund\n" << "Katze\n" << "Maus\n"

s.each_with_index do |line, linenr|
  print linenr, " ",  line
end

22.02.2005 Zuweisung nur wenn Variable zuvor nil

Permalink

  
s ||= "Hello World!"

22.02.2005 :: Wie funktioniert binding() ?

Permalink

Bindings sind eine Spezialität von Ruby, die man in Wald- & Wiesensprachen nicht findet. Grund genug, sich dieses Konzept mal näher anzuschauen.

Kurz gesagt, kann man sich mit einem binding die Umgebung sichern, die irgendwo im Programm vorhanden ist, um sie später zu nutzen.

 
def foo
  a = "InFoo"
  return binding()
end

a = "Global"
b = foo()
eval( "puts a" )
eval( "puts a", b )

# Ergebnis:
# Global
# InFoo
 

Die Methode eval führt ja Ruby-Code aus, der mittels String übergeben wird. Hier wird der Code einmal im normalen Kontext und dann im Kontext der Methode foo ausgeführt. Im Kontext dieser Methode ist a eine lokale Variable und hat nach dem ersten Aufruf den Wert "InFoo".

Normal werden nach Verlassen der Methode foo alle lokalen Variablen wieder freigegeben, existieren also nicht mehr. Durch binding() wird diese Umgebung allerdings konserviert. Das zweite eval erhält die Umgebung zum Zeitpunkt des Verlassens von foo, jedoch wird der Code nicht innerhalb der Methode ausgeführt.

Die Umgebung, die man durch binding() bekommt, kann man natürlich auch verändern.

Bei einer Methode einer Klasse gehört natürlich auch self zur Umgebung und man kann dann durch das Binding-Objekt auf alle Internas eines Objektes zugreifen.

 
class Foo
  def initialize( a )
    @a = a
  end

  def getBinding
    binding()
  end
end

a = Foo.new( "Hello" )
b = a.getBinding

eval( "puts @a", b )

# Ergebnis:
# Hello

Hier greife ich also von außerhalb auf die Objektvariable @a zu, weil ich mir mit getBinding eine Umgebung geholt habe, wo das möglich ist.

Parameter, die einer Methode übergeben werden, sind ja auch lokale Referenzen auf Objekte, die nach Beendigung wieder freigegeben werden. Mit binding() hat man auch Zugriff auf sie.

 
class Foo
  def initialize( a )
    @a = a
  end

  def getBinding( s )
    binding()
  end

end

a = Foo.new( "Hello" )
b1 = a.getBinding( "Binding1" )
b2 = a.getBinding( "Binding2" )

eval( "puts s", b1 )
eval( "puts s", b2 )

# Ergebnis
# Binding1
# Binding2

Beim ersten Aufruf von getBinding gehört der Parameter "Binding1" zur Umgebung, beim zweiten Aufruf "Binding2". Das spätere abrufen zeigt, dass diese Umgebung immer noch unverändert vorhanden ist.

Interessant sind Bindings fürs debuggen. Kann man sich so eine Umgebung retten, um sie zu inspizieren oder gezielt Veränderungen zu machen.

Für eval ist es interessant, weil die Ausführung in einer anderen Umgebung gemacht werden kann, als die Aktuelle. Damit lassen sich z.B. auch Zugriffsmöglichkeiten oder Namenskollissionen auf die aktuelle Umgebung über eval vermeiden.

Die Konstante TOPLEVEL_BINDING ist die Umgebung auf oberster Ebene. Folgendes Beispiel zeigt, wie man mit eval darauf Zugriff nimmt.

 
a="Hello"

def foo
  a = "foo: "
  eval( "puts a", TOPLEVEL_BINDING )
end

foo

# Ergebnis:
# foo:
# Hello

Interactive Ruby (irb) macht übrigens viel Gebrauch von Bindungen, weil ja eine aktuelle Umgebung zur Verfügung gestellt werden muss, in dem die interaktiven Eingaben laufen. Und diese Umgebung kann man auch umschalten.

In den eingebauten Klassen erscheint mir eval die einzige Möglichkeit, ein Binding-Objekt zu benutzen. Ob man binding() noch für andere Zwecke als eval gebrauchen kann, wäre noch zu erforschen.

22.02.2005 :: rescue ohne Klassenangabe

Permalink

Man könnte meinen, ein rescue ohne Angabe der Klasse, für welche rescue zuständig sein soll, würde alle Ausnahmen auffangen. Stimmt aber nicht, es fängt StandardError und alle abgeleiteten Klassen auf. Nicht aber z.B. ein Syntax Error in einem eval, denn dieser ist vom Typ Exception. Wenn man also alle erdenklichen Fehler abfangen möchte, schreibt man:

 
begin
  #...
rescue Exception => exc
  #...
end

Das => exc ist natürlich optional, ist aber schöner, das Ausnahmeobjekt mit einem griffigen Namen ansprechen zu können.

21.02.2005 :: Closures, yield, Codeblöcke

Permalink

Ein Closure ist ein mächtiges Konstrukt, welches für viele Programmierer erstmal recht fremdartig aussieht. Man findet sowas nämlich nicht in den geläufigen Sprachen wie Perl, C, C++ oder PHP. Closures sind etwas so Zentrales in Ruby, dass man sie überall in den eingebauten Klassen findet.

Sie anzuwenden ist einfach. Die geläufigste Form ist:

 
somevar.each do |a|
  # ...
end

Einer Methode kann man also einen Codeblock anhängen, der durch do..end bzw. {...} begrenzt ist. Dieser Codeblock kann auch Übergabeparameter empfangen, die zwischen die Balken geschrieben werden. Man kann beliebig viele Parameter, wie bei einem Funktionsaufruf übergeben. Der Codeblock wird hier z.B. für alle Elemente eines Arrays aufgerufen.

Mit dem übergebenen Codeblock kann eine Methode alles möglich anstellen. Ausgeführt wird er mit yield. Hier mal ein einfaches Beispiel:

 
def foo
  a = "foo: "
  print a + yield
end

foo {
  "Drucke mich aus.\n"
}

# >> foo: Drucke mich aus.

Die Methode foo() wird aufgerufen und mittels yield wird der Codeblock ausgeführt. Der letzte Ausdruck im Codeblock ist der Rückgabewert, wie man das auch von Methoden kennt. Dort ist ja auch kein explizites return notwendig. Methode foo() gibt also "foo: Drucke mich aus" zurück.

Wichtig ist hier: Auch wenn der Codeblock im Kontext der Methode foo ausgeführt wird, hat er doch keinen Zugriff auf die lokalen Variablen des Kontextes. Der Codeblock könnte also nicht auf die lokale Variable a zugreifen. Er wird nicht im selben Kontext ausgeführt.

Will man dem Codeblock Variablen übergeben, so geht das so:

 
def foo
  puts yield( "Eins", "Zwei", "Drei" )
end

foo do |a, b, c|
  [c, b, a, "Meins."].join( ", ") 
end

# >> Drei, Zwei, Eins, Meins.



Der Codeblock kann also in beide Richtungen mit der verbundenen Methode kommunizieren, er kann Parameter erhalten und einen Wert zurückgeben.

Der Codeblock kann übrigens auch über ein Proc-Objekt übergeben werden. Das sieht dann so aus:

 
def foo
  a = "foo: "
  print a + yield
end

p = proc {  "Drucke mich aus.\n" }


foo &p

Mittels der Kernel-Methode proc {...} wird ein neues Proc-Objekt angelegt und p zugewiesen. Der Methode foo wird dann p übergeben. Ein Proc-Objekt übergibt man mit einem vorangestellten &, weil Ruby sonst nicht unterscheiden könnte, ob p ein Parameter der Methode oder eine erwartete Procedure ist.

Closures eignen sich besonders für:

  • Code, der für eine Menge von Daten durchlaufen werden soll. Typisch sind hier die each- und foreach-Methoden.
  • Code, der in einer Umgebung ausgeführt werden soll, die nach Beendigung wieder abgebaut/geschlossen werden soll. Also z.B. File.open {...}, oder cgi.html {...} oder Net::HTTP.start(...) {...}.
  • Methoden, die an einem Punkt variabel gehalten werden sollen, damit die konkrete Implementierung Anpassungen vornehmen kann.

21.02.2005 :: Trace-Debug Hilfe

Permalink

Ruby kennt die kernel-Methode set_trace_func(). Hiermit kann man sich wertvolle Informationen beim Debugging holen.

Jedesmal, wenn ein bestimmter Event im Programm auftritt, wird diese Methode angesprungen. Es gibt insgesamt 8 Events:

Event-NameBeschreibung
lineAusführen einer Codezeile
c-callAufruf einer C-Routine
c-returnRücksprung aus C-Routine
callAufruf einer Methode (Rubycode)
returnRücksprung aus Methode (Rubycode)
classStart Klassen- oder Moduldefinition
endEnde Klassen- oder Moduldefinition
raiseAuslösung einer Exception

Man kann diese Methode nun so benutzen, dass sie bei bestimmten Events eine Ausgabe auf den Bildschirm macht. Man könnte das natürlich auch in eine Datei leiten. Damit kann man dann genau beobachten, was das Programm tut, wie der Programmablauf ist. Die Methode sollte gleich zu Programmbeginn definiert werden, am besten lagert man sie in eine Datei aus (z.B. trace.rb) und bindet sie mit require ein.

 
# -- trace.rb

# -- Config
TR_line       = false  # Ausführen Code auf einer Zeile
TR_c_call     = false  # Aufruf C-Routine
TR_c_return   = false  # Rückkehr C-Routine
TR_call       = false  # Aufruf Ruby Methode
TR_return     = false  # Rückkehr Ruby Methode

TR_class      = false  # Start Klassen- oder Moduldefinition
TR_end        = false  # Ende einer Klassen- oder Moduldef.
TR_raise      = false  # Auslösung Exception
# -- End Config


set_trace_func proc { |event, file, line, id, binding, classname|

  if (event == 'line'      && TR_line)     ||
     (event == 'c-call'    && TR_c_call)   ||
     (event == 'c-return'  && TR_c_return) ||
     (event == 'call'      && TR_call)   ||
     (event == 'return'    && TR_return)   ||
     (event == 'class'     && TR_class)    ||
     (event == 'end'       && TR_end)      ||
     (event == 'raise'     && TR_raise)  

    $stderr.printf "%8s %s:%-2d %10s %8s\n", event, file, 
      line, id, classname
  end
}


Im Configteil kann man definieren, bei welchem Event die Methode eine Ausgabe auf dem Bildschirm machen soll. Der Wert true schaltet die Ausgabe des entsprechenden Event ein.

Siehe auch tracer.rb in der Ruby Standard-Lib.

21.02.2005 :: sub Falle

Permalink

Ich hatte ein Webseiten-Template, in dem ich bestimmte Marker ersetzen wollte. Hierzu verwendete ich diese Methode:

 
def tmpl_sub_marker!( tmpl, mark, subst )
  tmpl.sub!( /<!-- #{mark} -->/m, subst )
end

Hierbei stellt tmpl das HTML-Template dar, mark den Namen des Markers und subst den String, durch den ersetzt werden soll. Das ging auch alles gut, bis in dem Ersatz-String ein "\1" auftauchte. Dies wurde nämlich von sub interpretiert als Teilstringersetzung.

Wie gewöhne ich nun sub!() ab, diese Ersetzung vorzunehmen? Eine Variante, die mir einfiel, ist die Block-Variante von sub!() zu benutzen:

 
def tmpl_sub_marker!( tmpl, mark, subst )
  tmpl.sub!( /<!-- #{mark} -->/m ) do |match|
    subst
  end
end

21.02.2005 :: Webhoster mit Ruby-Unterstützung

Permalink

Ist gar nicht so einfach, einen Hoster zu finden, der auch Ruby als Skriptsprache im Angebot hat. Zumindest ging es mir vor etwa einem Jahr noch so. Mittlerweile habe ich gesehen, dass einige große Provider auch Ruby unterstützen. Ich bin damals zu http://www.myhosting.de gewechselt, ein kleiner Hostingservice, den Florian Munz betreibt. Für 7 Euro hat man ssh-Zugang, cron-Jobs und eben auch Ruby als verfügbare Skriptsprache. Seit einiger Zeit kann sich Florian auch persönlich für Ruby begeistern, was der Unterstützung gut tut.

Einer der großen Provider, die Ruby unterstützen, ist Host Europe.

17.02.2005 :: Wohin mit der Konfiguration

Permalink

Fast jedes Skript braucht auch irgendwie Konfigurationen, um es für den konkreten Einsatzzweck anpassen zu können. Wohin damit und wie macht man es am besten?

Für kleine Quick&Dirty Skripts setze ich die Konfiguration an den Anfang des Skripts, eingebettet in ein Modul. Als Konstanten definiert.

 
#! /usr/bin/ruby

require "cgi"
#...

# -- Config
module Config
  SmtpHost    = 'localhost'
  MailSubject = 'TroubleTicket'
end
# -- End Config


Der Vorteil, es in ein Module zu packen, zeigt sich später in der Anwendung. Dann muss ich nämlich schreiben Config::SmtpHost oder Config::MailSubject. Der Programmcode ist so gut dokumentiert, weil jeder sofort sieht, dass hier eine Konfigurationsvariable verwendet wird. Was will man mehr...

Wenn Skripte größer werden und vor allem man auf mehreren Host's installiert, ist es besser, die Konfiguration auszulagern und dorthin zu packen, wo sie bei einem Linux-System hingehört: Ins /etc Verzeichnis, am besten in ein passendes Unterverzeichnis. Hierzu schneide ich den Config-Bereich aus der Ursprungsdatei und packe sie z.B. in /etc/checkserver/checkserver.ini. Das Module Config bleibt dabei erhalten, damit muss im Quellcode nichts geändert werden. Diese Config-Datei wird dann lediglich mit einem require '/etc/checkserver/checkserver.ini' eingebunden.

Wenn man Wegskripte auf einem Webserver installiert, dann hat man oft keine Zugriff auf das zentral /etc Verzeichnis. Hier ist es besser, dass die Anwendung ein eigenen Platz für Konfiguration definiert. Hier ist es eher typisch, dass es kein zentrales sondern anwendungsgebundene Konfigurationsverzeichnisse gibt.

Ein Nachteil hat dieses Vorgehen: Macht man in der Konfiguration einen Syntaxfehler, bricht das Skript ab. Meist mit nicht sehr aussagekräftigen Hinweisen auf den Fehler. Und gerade Admins, die mit Ruby nichts zu tun haben und dessen Syntax nicht kennen, machen bei komplizierten Konfigurationen schnell mal Fehler.

Da ist es besser, auf standardisierte Konfig-Sprachen zurückzugreifen. Die Konfigdatei wird dann geparst und Fehler beim parsen kann das Programm sogar komfortabel abfangen und Hinweise geben, wo es klemmt. Eine Möglichkeit wäre der Einsatz von Yaml als Konfigsprache. Eigentlich eine sehr schöne Sache, ich mag Yaml. Vor allem, weil man damit auch hierarchisch arbeiten kann.

Andererseits kennt kein Wald&Wiesen Admin Yaml. Und so greife ich auch gerne mal auf die Apache-Konfig-Syntax zurück, die sich gut zu verbreiten scheint. Und auch diese ist ja hierarchisch.

Hierfür habe ich mir einen Parser für Ruby geschrieben. In einem Projekt, was etwas mehr Verbreitung fand, habe ich darauf zurückgegriffen. Gerade bei der Konfig-Sprache sollte man auf was zurückgreifen, was viele kennen, womit sie sofort umgehen können. Denn nichts ist nerviger, als eine komplizierte Installation von Werkzeugen. Wahrscheinlich werden viele Lösungen deshalb nicht verwendet, weil sie einfach zu kompliziert zu installieren sind.

16.02.2005 :: Zuweisungsfalle Mehrfachzuweisung

Permalink

Ich hatte gerade 3 Variablen mit Leerstrings zu initialisieren und machte das so:

 
s  = ""
s1 = ""
s2 = ""

Soweit, so gut. Als ich den Code optimierte, dachte ich, mach ich doch aus diesen 3 Zeilen eine.

 
s = s1 = s2 = ""

Auch wenn das ähnlich aussieht, ist es nicht das gleiche. Das merkte ich dann auch sofort, weil mein Programm nicht mehr funktionierte. Im ersten Beispiel werden 3 neue Stringobjekte angelegt, die jeweils mit s, s1, s2 verbunden werden. Im zweiten Beispiel wird aber nur ein Stringobjekt angelegt, welches dann sowohl mit s, s1, s2 verbunden ist.

Das bedeutet: Ändert man s, ändert sich auch s1 und s2:

 
s = s1 = s2 = "Ruby"
s << "Perl"
puts s   # >> RubyPerl
puts s1  # Ups >> RubyPerl

14.02.2005 :: Änderungen von Ruby Version 1.8.1 auf 1.8.2

Permalink

siehe: http://www.rubygarden.org/ruby?RubyOnePointEightPointTwo

14.02.2005 :: Klassenmethoden über Singleton-Methode

Permalink

Das hinzufügen von Klassenmethoden geschieht normal so:

 
class MyClass
  def MyClass.MyMethod
    puts "Ich bin MyMethod."
  end
end

# Aufruf der Klassenmethode
MyClass.MyMethod


Genauso gut und oft verwendet geht aber auch:

 
class MyClass
  class << self
    def MyMethod
      puts "Ich bin MyMethod."
    end
  end
end

MyClass.MyMethod

Wie funktioniert sowas? Hierzu sollte man wissen, was Singleton Methoden sind. In Ruby kann man nämlich einzelnen Objekten Methoden zuordnen, die nur für dieses Objekt gelten.

 
s="Hello"
def s.beschreibung
  "Bin ein String"
end

#Was bist du?
puts s.beschreibung

Die Methode "beschreibung" existiert also nur für dieses Objekt "s". Eine andere Schreibweise, um vor allem mehrere Singletons zu definieren ist:

 
s="Hello"

class << s
  def beschreibung
    "Bin ein String"
  end
end

#Was bist du?
puts s.beschreibung

Hier werden also alle Methoden zwischen "class << s" und "end" Methoden des Objektes "s".

Zurück zum Klassenmethoden-Beispiel. Dort sind alle Methoden zwischen "class << self" und "end" Methoden des Objektes MyClass (was self entspricht). Moment mal: MyClass ist doch eine Klasse und kein Objekt! Nicht ganz. In Ruby ist alles ein Objekt, selbst eine Klasse. Das ist etwas verwirrend, weil man das so aus vielen anderen OO-Sprachen nicht kennt.

Die Mutter aller Objekte ist die Klasse Objekt, dann folgt Module und dann Class.

 
puts Class.superclass  # >> Module
puts Module.superclass # >> Object
puts Object.superclass # >> nil

Auch kann man sagen: Klassenmethoden sind nichts weiter, als Singleton-Methoden des jeweiligen Klassenobjektes. Dies lässt sich z.B. überprüfen, wenn man mal "puts File.singleton_methods" im irb eintippt. Hier werden alle Klassenmethoden der Klasse File zurückgegeben.

Warum aber nun wird die zweite Schreibweise für Klassenmethoden oft bevorzugt? Es bedeutet weniger Tippaufwand und sie ist veränderungswilliger. Ändert sich z.B. der Klassenname, müssten bei der ersten Schreibweise alle Klasssenmethoden angepasst werden. Bei der zweiten Schreibweise ändert sich dort gar nichts.

Übrigens eignet sich diese Schreibweise auch für Module. Denn auch hier gibt es Instanz- und Klassenmethoden. Klassenmethoden in Modulen verwendet man, wenn man von außerhalb darauf zugreifen will, ohne ein Modul in eine Klasse included zu haben.

 
# so
module Foo
  def Foo.methode1()
    puts "Foo.methode1"
  end
end

Foo.methode1() # >> Foo.methode1

# oder so
module Foo
  class << self
    def methode1()
      puts "Foo.methode1"
    end
  end
end

Foo.methode1() # >> Foo.methode1


13.02.2005 :: require 'tempfile'

Permalink

Oft kommt es vor, dass ein Programm temporäre Dateien anlegen muss. Man kann dabei nicht einfach immer den gleichen Dateinamen verwenden, wenn z.B. mehrere Instanzen des Programms gleichzeitig laufen könnten.

In Ruby 1.8.1 findet man hierfür die Bibliothek tempfile. Mit ihr kann man sehr einfach temporäre Dateien anlegen.

 
require 'tempfile'

tf = Tempfile.new( 'meintempfile' )

Da Tempfile alle Eigenschafen von File erbt, kann man nun damit arbeiten, als wäre es eine normale Datei. Zum Anlegen des Files sucht sich die Klasse das System-Tempverzeichnis, es sei denn, man übergibt new als zweiten Parameter ein anderes Verzeichnis. Die angelegte Datei hat das Format <basename><pid>.<count>, also z.B. "meintempfile11720.0".

Ein Programm kann also den gleichen Basenamen für mehrere Tempfiles bei new übergeben, weil es den count gibt. Über die pid hat jede Instanz einen eindeutigen Dateinamen.

Basename sollte man so wählen, dass es sich nie mit anderen Programmen überlagern kann. Man könnte z.B. den Programmnamen dort benutzen. Das hilft auch gleichzeitig bei der Zuordnung, falls sich mal noch irgendwelche Dateien im Temp-Verzeichnis befinden, die nicht gelöscht wurden. Die Tempfile-Klasse löscht übrigens auf jeden Fall bei Programmbeendigung, auch wenn man mit ctrl-c abbricht. Natürlich nicht, wenn man einen Prozess mit kill -9 abschießt.

Folgendes Beispiel erzeugt 2 Tempfiles, in die zuerst was reingeschrieben wird, die dann gesynct werden, um den Inhalt wirklich auf die Platte zu schreiben, dann zurückgespult werden, um die Inhalte auszugeben. Zum Schluß wird 60 Sekunden gewartet, damit man Zeit hat, sich von der Anlage der Tempfiles im Dateisystem zu überzeugen.

 
require 'tempfile'


tf  = Tempfile.new( 'temptest' )
tf1 = Tempfile.new( 'temptest' )

tf.puts "Erstes File Z1"
tf.puts "Erstes File Z2"
tf.puts "Erstes File Z3"
tf.puts "Erstes File Z4"

tf1.puts "Zweites File Z1"
tf1.puts "Zweites File Z2"
tf1.puts "Zweites File Z3"
tf1.puts "Zweites File Z4"

tf.sync
tf.rewind

tf1.sync
tf1.rewind

puts "---- Ausgabe Erstes File ----"
tf.each do |l|
  puts l
end


puts "---- Ausgabe Zweites File ----"
tf1.each do |l|
  puts l
end

sleep( 60 )

tf.close
tf1.close

Mit der Methode 'Tempfile#path' kann man sich den kompletten Pfad+Dateiname des Tempfiles zurückgeben lassen. 'Tempfile#size' gibt die Dateigröße zurück.

04.02.2005 Kurzform: Datei lesen

Permalink

Wer von Perl kommt, sucht vielleicht sowas wie while(<>), um schnell mal eine Datei oder stdin zu lesen. Unter Ruby geht es so:

 
while gets do
  print $_
end 

Eine Alternative ist dies:

 
ARGF.each do |line| 
  print line 
end

Hierzu ein kleiner Test. Wir wollen einen Filter schreiben, der aus einem 'ls -l' nur die Verzeichnisse herausfiltert. Zeige mir also alle Verzeichnisse, lautet die Aufgabe.

Das Programm namens dfilter, welches wir nachträglich mit chmod 700 ausführbar machen:

 
#! /usr/bin/env ruby
while gets do
  print $_ if $_ =~ /^d/
end

Und dann rufen wir es auf:

 
ls -l / | ./dfilter

Damit sollten alle Verzeichnisse vom Rootverzeichnis gelistet werden.

04.02.2005 :: CSV-Dateien bearbeiten

Permalink

Ich bin gerade dabei, Datenbestände zu konvertieren. Nahezu jede Anwendung, die Daten in Tabellen sammelt, kann zumindest im CSV-Format exportieren. Das kann z.B. Excel oder Access. Konkret brauche ich es jetzt, um meine Handbase-Daten aus dem Palm Handheld in eine Form zu bekommen, die ich in ein Wiki einspeisen kann. Ich habe da ein paar Logbücher drauf, die ich jetzt gerne in einem Wiki weiterführen möchte.

Eine CSV-Datei auseinanderzunehmen, ist nicht ganz so trivial. Normal ist jede Spalte komma-separiert und dann ist jede Zelle oft noch in Anführungsstriche gepackt. Einfach mit split da ranzugehen, funktioniert nicht.

Zum Glück gibt es aber in der Ruby 1.8er Standardbibliothek das Modul "csv". Und damit wird alles zu einem Kinderspiel.

Zuerst einmal haben wir eine Beispieldatei test.csv:

 
"00001","01.01.2005","Mein erster Eintrag"
"00002","01.01.2005","Mein zweiter Eintrag"
"00003","03.01.2005","Das ist ein etwas längerer
Eintrag
mit ein paar
Zeilenschaltungen"

Ein erstes Programm convert1.rb soll diese Datei zerlegen und mir die einzelnen Datensätze ausgeben:

 
# convert1.rb

require 'csv'

csvfile= $*[0]

CSV.open(csvfile, 'r') do |row|
  puts row
  puts "----"
end

Ein Aufruf "ruby convert1.rb test.csv" bringt dann folgendes Ergebnis:

 
00001
01.01.2005
Mein erster Eintrag
----
00002
01.01.2005
Mein zweiter Eintrag
----
00003
03.01.2005
Das ist ein etwas längerer
Eintrag
mit ein paar
Zeilenschaltungen
----

In einem zweiten Anlauf sprechen wir jede Zelle einzeln an. Jede Zelle einer Zeile wird nämlich in einem spezialisiertem Array abgelegt, womit man ganz einfach Zugriff hat.

 
# convert2.rb
csvfile= $*[0]

CSV.open(csvfile, 'r') do |row|
  printf "Nr: %s\nDatum: %s\nBeschreibung: %s\n", row[0], row[1], row[2]
  puts "----"
end

Das Ergbnis:

 
E:\temp\handbase>ruby convert2.rb test.csv
Nr: 00001
Datum: 01.01.2005
Beschreibung: Mein erster Eintrag
----
Nr: 00002
Datum: 01.01.2005
Beschreibung: Mein zweiter Eintrag
----
Nr: 00003
Datum: 03.01.2005
Beschreibung: Das ist ein etwas längerer
Eintrag
mit ein paar
Zeilenschaltungen
----

So einfach ist also der Zugriff auf csv-Daten. Diese einzelnen Zellen jetzt noch aufzubereiten und in eine Wiki-Quelltextform zu bringen, ist dann keine große Zauberei mehr. Zum Schluß habe ich dann einen Konverter, dem ich ein CSV-formatiertes Logbuch einspeise und der mir ein Wiki-formatiertes Logbuch auswirft, welches ich dann per Hand in ein Wiki einfüge und so eine sauber formatierte Logbuch-Webseite erhalte.

Achtung: Der csv-Parser nimmt es sehr genau. Wenn der letzte Wert einer Zeile in Anführungsstriche gepackt ist und danach noch Leerzeichen folgen, wirft er mit einer Exception ab. Auch Spaces vor oder nach dem Komma-Separator mag er nicht.

Übrigens: Das Trennzeichen muss nicht zwangsläufig ein Komma sein, man kann jedes beliebige Zeichen als Trenner übergeben, auch ein Space. So kann man solche Zeilen auch gut parsen:

 
20 47 "Hello World" 94 "Ein Test"

Der dritte Parameter ist der Trenner:

 
CSV.open( in_file, "r", " " ) do |line|
 ...
end

03.02.2005 :: Ruby portierbar halten: Interpreteraufruf

Permalink

Diese Zeile benutzen. Damit wird ruby gefunden, insofern es sich im Suchpfad befindet:

 
#! /usr/bin/env ruby

03.02.2005 :: Mailingliste Archivsuche

Permalink

Jede Mail, die über die Ruby-Mailingliste geht, hat im Header eine X-Mail-Count Nummer. Darüber ist jede Mail eindeutig referenzierbar. Wenn man die Nummer weiß, kommt man über die URL http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/88503 an die entsprechende Mail. Die letzte Nummer der URL dabei ersetzen. In Mails wird dann oft eine Referenz angegeben z.B. "siehe ruby-talk:88665".

Auch kann man über das Webinterface suchen: http://www.ruby-talk.org/ruby/ruby-talk/index.shtml


Copyright dieser Seite

Copyright (c) 2004 Winfried Mueller, www.reintechnisch.de

Es wird die Erlaubnis gegeben dieses Dokument zu kopieren, zu verteilen und/oder zu verändern unter den Bedingungen der GNU Free Documentation License, Version 1.1 oder einer späteren, von der Free Software Foundation veröffentlichten Version; mit keinen unveränderlichen Abschnitten, mit keinen Vorderseitentexten, und keinen Rückseitentexten.

Eine Kopie dieser Lizenz finden Sie unter GNU Free Documentation License.

Eine inoffizielle Übersetzung finden Sie unter GNU Free Documention License, deutsch.

In diesem Artikel werden evtl. eingetragene Warenzeichen, Handelsnamen und Gebrauchsnamen verwendet. Auch wenn diese nicht als solche gekennzeichnet sind, gelten die entsprechenden Schutzbestimmungen.

Alle Informationen in diesem Artikel wurden mit Sorgfalt erarbeitet. Trotzdem können Inhalte fehlerhaft oder unvollständig sein. Ich übernehme keinerlei Haftung für eventuelle Schäden oder sonstige Nachteile, die sich durch die Nutzung der hier dargebotenen Informationen ergeben.

Sollten Teile dieser Hinweise der geltenden Rechtslage nicht entsprechen, bleiben die übrigen Teile davon unberührt.


<< Archiv 2005-2 | RubyWeblog | >>