Ruby Weblog Archiv 2005-2

Winfried Mueller
www.reintechnisch.de

Wer Skriptsprachen und Objektorientierung mag, findet in Ruby eine ästhetische Ausdrucksmöglichkeit seines Könnens. Plattformübergreifend.


29.06.2005 :: Workshop-Skript

Permalink

Im April habe ich einen Ruby-Workshop bei der Dortmunder Linuxgruppe (http://www.lugrudo.de) gemacht.

Das Skript zum Workshop ist jetzt online, siehe hier: WSRubyForBeginner

Vielleicht regt es ja dazu an, selber auch mal einen Ruby-Workshop zu organisieren.

13.06.2005 :: FileSet

Permalink

Als Systemadmin begegnet man oft der Aufgabe, über ein Verzeichnis zu iterieren und mit bestimmten Dateien etwas anzustellen. Hierfür gibt es in Ruby Unterstützung mittels der Bibliothek find. Ich wollte es jedoch etwas komfortabler haben, und so entstand die Klasse FileSet.

 
require 'find'

# FileSet                Version: 2005/06/13
#
# Winfried Mueller, www.reintechnisch.de
#
# Example:
#   fs = FileSet.new( "e:/temp/7z" ) do |path, fn, stat|
#     # File Filter Definition     
#     stat.file? and
#     (fn =~ /css$/ or
#      fn =~ /html$/)
#   end
#
#   fs.each do |path, fn, stat|
#     puts "#{path} #{stat.size}"
#   end
#   
class FileSet
  attr_reader :basedir

  #attr_priv :filter

  def initialize( basedir, &block )
    @basedir = basedir
    if block
      @filter = block
    else
      @filter = lambda { true }
    end
  end

  def filter( &block )
    @filter = block
  end

  def each
    raise "dir not found: #{@basedir}" unless File.directory?( @basedir )
    Find.find( @basedir ) do |path|
      next if path == "." or path == ".."
      stat = File.stat(path)
      filename = File.basename( path )
      if @filter.call( path, filename, stat )
        yield path, filename, stat
      end
    end
  end
end

Zuerst legt man ein neues FileSet an, wobei man neben dem Basisverzeichnis auch gleich die Filterbedingungen übergeben kann:

 
fs = FileSet.new( "c:/winnt" ) do |path, fn, stat|
  fn =~ /\.dll$/
end

Hier wollen wir also das winnt-Verzeichnis scannen, wobei nur alle *.dll Files herausgefiltert werden sollen (genaugenommen hätten wir noch ein stat.file? mit hinzunehmen müssen). Ruft man dann ein each auf, werden alle Dateien, die diesem Filterkriterium entsprechen, durchlaufen. Dabei wird ebenfalls der komplette Pfad, der Filename und ein stat-Objekt übergeben, worüber man die Dateieigenschaften abgreifen kann.

 
fs.each do |path, fn, stat|
  puts "#{path} #{stat.mtime.strftime( '%d.%m.%Y %H:%M:%S')}"
end

Damit wird also jede DLL-Datei ausgegeben inkl. der Datums der letzten Veränderung.

Den Filter kann man mit fs.filter { block } auch separat setzen. Wird der Filter überhaupt nicht gesetzt, werden alle Dateien bei each durchlaufen.

11.06.2005 :: Unit Testing

Permalink

Unit Tests sind ein sehr leistungsfähiges Konzept, um fehlerfreie Software zu erstellen. Es erfährt gerade in den letzten Jahren wieder viel Aufmerksamkeit durch das Extreme Programming. Hierbei geht man sogar den umgedrehten Weg: Anstatt zuerst den Programmteil zu implementieren und dann die Tests zu schreiben, schreibt man zuerst die Tests. Anhand dieser Tests hat man eine Richtschnur und eine Dokumentation, was das Programm können muss und wann man das Ziel erreicht hat. Das Schreiben der Tests ist damit sozusagen eine Zielvereinbarung - bewältigt dein Programm diesen Parcours, ist es fit für den Einsatz.

Unit steht dafür, dass nicht ein Programm als Ganzheit getestet werden soll, sondern bestimmte Teile, z.B. eine Bibliothek, eine Klasse, ein Modul.

In Ruby gibt es die wunderbare Unterstützung über test/unit, geschrieben von Nathaniel Talbott. Es ist ein Testframework, was einem viel Arbeit bei der Erstellung von Tests abnimmt. Framework hört sich mächtig und nach viel Arbeit an, es zu verstehen. Für die meisten Tests ist es jedoch total simpel.

Wahrscheinlich wird man schon Code begegnet sein, der von test/unit Gebrauch macht. Weil es so einfach zu benutzen ist, schreiben viele Bibliotheksdesigner auch Tests. Und so stolpert man darüber, wenn man sich mal ein paar Ruby-Bibliotheken anschaut.

Hier ein Beispiel:

 
def Note( selector )
  raise unless selector.is_a?( Integer )
  raise unless (selector < 6 && selector > 0)
  case selector
    when 1
      "Sehr gut"
    when 2
      "Gut"
    when 3
      "Befriedigend"
    when 4
      "Ausreichend"
    when 5
      "Ungenuegend"
  end
end


require 'test/unit'

class TestNote < Test::Unit::TestCase
  def test_exceptions
    assert_raise(RuntimeError) { Note( "" ) }
    assert_raise(RuntimeError) { Note( 6 ) }
    assert_raise(RuntimeError) { Note( 0 ) }
    assert_nothing_raised()    { Note( 1 ) }
  end

  def test_output
    assert_equal( "Sehr gut", Note( 1 ) )
    assert_equal( "Gut", Note( 2 ) )
    assert_equal( "Befriedigend", Note( 3 ) )
    assert_equal( "Ausreichend", Note( 4 ) )
    assert_equal( "Ungenuegend", Note( 5 ) )    
  end
end

Wir haben also eine Methode "Note" geschrieben, die wir ausgiebig testen wollen. Ein require 'test/unit' holt uns das Testframework hinein. Die Klasse TestNote ist die Definition des Tests. Tests können in Methoden untergliedert werden, hier haben wir test_exceptions und test_output. Jede Methode, die einen Test darstellt, muss mit test_ beginnen. Das Testframework erkennt das über Reflektion und ruft diese dann fürs Testen auf.

Wir verwenden hier zum Testen assert_equal, assert_raise und assert_nothing_raised. Alle diese Methoden überprüfen, ob bestimmte Bedingungen erfüllt sind, ob z.B. der Rückgabewert von Note(1) auch "Sehr gut" ist oder ob eine Exception ausgelöst wird, wenn man Note(0) benutzt.

Beim Aufruf dieses Programms wird der definierte Test auch ausgeführt, man braucht also keinen Main-Code schreiben, der den Test startet. Wenn alles korrekt funktioniert, gibt der Test aus:

 
Loaded suite test61
Started
..
Finished in 0.01 seconds.

2 tests, 9 assertions, 0 failures, 0 errors

Sollte ein Fehler auftauchen, wird der Fehler ausgegeben.

Mitunter soll für jeden Test ein immer gleichlautender Startup und ein Endcode ausgeführt werden. Dafür gibt es die Methoden setup und teardown, die jeweils zu Beginn und Ende eines Tests aufgerufen werden (einer jeden test_* Methode).

Die Testsuite wertet übrigens auch Kommandozeilenoptionen aus. Damit kann man z.B. nur einen bestimmten Test starten:

 
ruby test61.rb --name test_exceptions

Damit wird bei obigem Test nur die Test-Methode test_exceptions ausgeführt.

Tests sollte man natürlich in eigene Dateien organisieren und gemeinsam in ein Unterverzeichnis "test" packen. So ist es Ruby-Konvention. In einen Test included man die Bibliothek oder den Programmteil, den man testen möchte. Damit der Code korrekt gefunden wird, sollte man den Bibliothekspfad erweitern:

 
# Verzeichnisstruktur
# mylib
#   lib
#     mylib.rb
#   test
#     test1.rb  -> diese Datei hier

$:.unshift File.join( File.dirname(__FILE__), "..", "lib" )

require 'test/unit'
require 'mylib.rb'

# Testcode hier...

Neben den oben aufgeführten assert-Methoden gibt es insgesamt diese:

 
assert

assert_nil
assert_not_nil

assert_equal
assert_not_equal

assert_in_delta

assert_raise
assert_nothing_raised

assert_instance_of
assert_kind_of

assert_respond_to

assert_match
asster_no_match

asster_same
assert_not_same

assert_operator

assert_throws

assert_send

flunk( message = "Flunked" )
  Wirft auf jeden Fall einen Fehler aus.

Tests sind gleichzeitig auch Dokumentation. Sie zeigen, wie sich ein Code verhalten sollte. Wer also fremden Code einsetzt, kann über Tests, sofern vorhanden, einiges an Spezifkationen herausfinden. Selber tut man gut daran, durch Tests für andere zu dokumentieren, wie sich der eigene Code verhalten sollte.

Mit dem Schreiben von Tests ist das so eine Sache: Eigentlich sind sie oft Gold wert und deshalb wichtig und notwendig. Leider ist es zusätzliche Mühe und Zeitaufwand, der nicht direkt im Produkt sichtbar wird. Ähnlich wie mit Dokumentation. Die Einspareffekte durch gute Tests sind nicht direkt erkennbar und es schreit niemand, wenn man keine Tests schreibt. Somit werden Tests oft ähnlich behandelt, wie Dokumentation oder Datensicherungen/Backups. Jeder weiß, dass man sie machen sollte, aber viele tun es trotzdem nicht. Spätestens dann, wenn mal wieder Zeitdruck ist, vergisst man sie. Und zahlt dann doppelt und dreifach drauf...

Autos funktionieren heute übrigens auch deshalb so zuverlässig, weil ein unglaublicher Aufwand für das Testen aufgebracht wird. Dort gehört das penible Testen zum festen Teil der Entwicklung.

Weblinks:

11.06.2005 :: WWW::mechanize - Web Scraping

Permalink

Webseiten sind eigentlich dazu gedacht, von einem menschlichen Benutzer bedient zu werden. Manchmal möchte man jedoch auch ein Webangebot über ein Skript automatisiert bedienen. Ich hatte letztens ein Anwendungsfall, wo dies sehr praktisch war: 1000 Datensätze (Spam) in einem Gästebuch mussten gelöscht werden, zu dem ich keinen direkten Datenbank-Zugriff hatte. Es funktionierte also nur löschen über das Webinterface.

Eine andere Anwendung wäre der Test von Webseiten. Oder die automatisierte Suche in Ebay. Ja, man könnte technisch gesehen sogar skriptgesteuert bei Ebay automatisch ersteigern.

In Ruby gibt es seit kurzem hierfür das WWW::mechanize aus dem Wee-Projekt. Es basiert auf den Idee der mechanize Perl-Bibliothek.

Das Konzept ist einfach: Mit get holt man sich eine Webseite, die dann geparst wird. Hier werden dann z.B. alle Links oder alle Forms erkannt. Auf diese kann man nun zugreifen, entweder über die Nummer des Links, oder noch besser, über die Bezeichnung des Links. Selbst wenn sich die Anordung der Links auf der Seite ändert, funktioniert es dann noch. So könnte man also nun eine Abfolge von Befehlen absetzen: Hole Seite x, Folge dem Link "Go", Trage in der Antwortseite im ersten Formular im Feld Login "xy" ein, im Feld Passwort "geheim", schicke das Formular ab usw...

WWW::mechanize ist ein Quickhack, wie es Michael Neumann schreibt, sicherlich noch nicht so gut getestet und leistungsfähig, wie das Perl-Modul. Ein paar erste Gehversuche meinerseits zeigten aber, dass es funktionierte. Der Autor nutzt es hauptsächlich als Testwerkzeug für Webprojekte.

Der Quellcode ist überschaubar, so dass man schnell eigene Erweiterungen oder Verbesserungen einbauen kann.

Wer also Webseiten fernbedienen muss, sollte sich WWW::mechanize mal anschauen. Ausprobieren geht übrigens wunderbar über irb.

Weblinks:

01.06.2005 :: to_s Selektor

Permalink

Manchmal baut man eine Klasse, die auf to_s mindestens in zwei Varianten antworten sollte, weil beide Formate sinnvoll sind. Man könnte sicherlich anfangen, und to_s1..to_sn als extra Methoden anlegen. Schöner finde ich dieses Vorgehen mit Symbolen:

 
class Time
  alias old_to_s to_s
  def to_s( select = :default )
    case select
      when :default
        old_to_s
      when :short_de
        strftime( "%d.%m.%Y %H:%M:%S" )
      when :short_en
        strftime( "%m/%d/%Y %I:%M:%S %p")
    end
  end
end

a = Time.new
puts a.to_s #>> Wed Jun 01 14:04:58 Westeuropäische Sommerzeit 2005
puts a.to_s( :short_de ) #>> 01.06.2005 14:10:24
puts a.to_s( :short_en ) #>> 06/01/2005 02:10:24 PM

30.05.2005 :: Integer Arithmetik

Permalink

Jetzt bin ich auf die Nase gefallen:

 
puts 10 / 9  #>> 1
puts -10 / 9 #>> Ups: -2
puts -10 / 3 #>> Ups: -4

Bei vielen Sprachen wird Integer-Division so durchgeführt, dass einfach die Nachkommastellen abgeschnitten werden (fractional-Teil). Dann wäre -10 / 9 = -1.111 = -1. So bin ich das gewöhnt und so hätte ich das auch erwartet.

Dagegen verhalten sich jedoch Python, TCL und Ruby anders, mathematisch korrekter. Genaueres lässt sich in den angebenen Threads nachlesen.

Zurückgeben wird bei der Division der größte Integerwert der aber kleiner als das Float-Ergebnis ist. Bei positiven Zahlen bedeutet das bei 10 / 9 = 1.111 = 1. Bei -10 / 9 = -1.1111 kann nicht -1 herauskommen, weil -1 nicht kleiner als -1.1111 ist. Folglich muss es -2 sein.

Auch floor gibt übrigens den größten Integerwert zurück, der kleiner als die Float-Zahl ist. So ist 1.2.floor = 1, jedoch -1.2.floor = -2.

Modulo verhält sich bei negativen Zahlen ebenso, für C-Programmierer sehr merkwürdig. 10 % 9 ergibt 1, wie erwartet. -10 % 9 ergibt dagegen nicht -1, sondern 8! Dagegen verhält sich -10.remainder(9) so, wie modulo bei C: Es ergibt -1.

Kurzum: Bei Integer-Arithmetik kann man in Ruby Überraschungen erleben, wenn man von Sprachen wie C/C++, Java oder PHP kommt.

Weblinks:

28.05.2005 :: Idee Erweiterung Time-Class: Distanzen als String

Permalink

Wenn man die Time-Klasse benutzt, muss man mitunter mit Zeitdistanzen rechnen. Man möchte z.B. 20 Tage und 3 Stunden hinzuaddieren. Die Time-Klasse kennt aber nur die Addition von Sekunden. Hat man dagegen Tage oder Stunden, müsste man erst umrechnen.

Hierfür dachte ich mir, könnte man die Time-Klasse erweitern, so dass folgendes geht:

 
  a = Time.new
  a = a + "5D7H5M3S"  # Add 5 Tage + 7 Stunden + 5 Minuten + 3 Sekunden
  puts a
  a = a + "1Y"        # Add 1 Jahr
  a = a + "250H"      # Add 250 Stunden
  a = a - "2Y"        # Sub 2 Jahre

Eine Implementierung könnte so aussehen. Ein Jahr wird hier der Einfachheit halber mit 365 Tagen angesehen, was bei Schaltjahren nicht korrekt ist. Alles, was im String nicht interpretiert werden kann, wirft hier keine Exception ab, sondern wird ignoriert.

 
class Time
  alias old_plus +
  alias old_minus -

  def string2seconds( str )
    t = str.scan( /(\d+)(Y|D|H|M|S)/ )
    seconds = 0
    loop do
      break if t.length == 0
      match = t.shift
      val = match[0].to_i
      type = match[1]
      case type
        when "Y"
          seconds += val * 365 * 24 * 60 * 60
        when "D"
          seconds += val * 24 * 60 * 60
        when "H"
          seconds += val * 60 * 60
        when "M"
          seconds += val * 60
        when "S"
          seconds += val
        else
          # should not happen
          raise "assert"
      end
    end
    seconds
  end
  private :string2seconds

  def +(other)
    if other.is_a?(String)
      old_plus( string2seconds(other) )
    else
      old_plus(other)
    end
  end

  def -(other)
    if other.is_a?(String)
      old_minus( string2seconds(other) )
    else
      old_minus(other)
    end
  end
end

a=Time.local( 2005, 1, 1, 0, 0, 0)
puts a

loop do
  print "Distanz: "
  d = gets.chomp
  puts a + d
end

# Beispiele
# 5H5S      >> 5 Stunden + 5 Sekunden
# 1Y5D10M   >> 1 Jahr + 5 Tage + 10 Minuten
# 10H5M7S   >> 10 Stunden + 5 Minuten + 7 Sekunden

Vielleicht ist es auch sinnvoller eine TimeDistance Klasse einzuführen, mit der man dann rechnen kann.

 
a = Time.new
a = a + TimeDistance.new( "5H7M" )  # 5 Stunden + 7 Minuten
a = a + TimeDistance.new( 600 )     # 600 Sekunden

28.05.2005 :: Sag es symbolisch

Permalink

Wenn man das Verhalten von Objekten festlegt, braucht man öfters mal eine Möglichkeit, zwischen mehreren Alternativen wählen zu können. Ein typisches Beispiel:

 
class Foo
  SPEED_FAST = 2
  SPEED_MEDIUM = 1
  SPEED_SLOW = 0
  def initialize( speed )
    @speed = speed
  end
  def puts_speed
    case @speed
      when SPEED_FAST
        puts "Fast Speed"
      when SPEED_MEDIUM
        puts "Medium Speed"
      when SPEED_SLOW
        puts "Slow Speed"
    end
  end
end

a = Foo.new( Foo::SPEED_MEDIUM )
a.puts_speed

In dieser Art macht man es auch in vielen anderen Sprachen. Anstatt Werte wie 0,1 oder 2 zu übergeben, was kryptisch wäre, benutzt man Konstanten. Diese Konstanten dürfen nicht global sein, weil sie sich sonst mit anderem Code überschneiden könnten. Kapselt man sie hier in der Klasse, gibt es keine Probleme mit anderen Namensräumen.

In Ruby gibt es nun eine für viele Fälle eine elegantere Lösung. Es gibt sogenannte Symbole. Symbole sind global. Das Literal für ein Symbol ist :symbolname, also z.B.

 
a = :Foo
b = :Bar
x = :NochEinSymbol

Nun verweist a auf ein Symbolobjekt mit dem Wert ":Foo". Es wird neu angelegt, wenn es noch nicht existiert. Ist es schon da, z.B. durch anderen Code, wird lediglich eine Referenz darauf gelegt.

Symbole haben eine eindeutige Identität, die über eine Fixnum definiert wird. Jedes Symbol hat also eine eindeutige Nummer, die von Ruby beim Anlegen des Symbols vergeben wird.

 
a = :Foo
puts a.to_i

Bei Konstanten hat man ja das Problem, dass bei Namensüberschneidungen diese mit unterschiedlichen Werten initialisiert würden. Bei Symbolen hat man das Problem nicht, weshalb Namensüberschneidungen kein Problem sind.

Obigen Code könnte man nun so umschreiben:

 
class Foo
  def initialize( speed )
    @speed = speed
  end
  def puts_speed
    case @speed
      when :fast
        puts "Fast Speed"
      when :medium
        puts "Medium Speed"
      when :slow
        puts "Slow Speed"
    end
  end
end

a = Foo.new( :medium )
a.puts_speed

Ein Symbol wie :medium ist eleganter als z.B. MeinModul::MeineKlasse::MeineKonstante, wie es im Extremfall sein könnte. Auch braucht man keine Konstanten mehr zu definieren. Natürlich sollte man den Code so erweitern, dass er eine Prüfung vornimmt, ob @speed mit einem gültigen Wert initialisiert wird. Das könnte man in initialize z.B. so tun:

 
  def initialize( speed )
    raise unless [:fast, :medium, :slow].include? speed 
    @speed = speed
  end
 

Selbst wenn ein anderes Modul nun auch :fast benutzt, gibt es keine Überschneidungsprobleme. Ruby erkennt, dass das Objekt mit eindeutiger Nummer schon angelegt ist und benutzt es.

Die :symbol Literale kennt man übrigens auch aus den attr, attr_reader, attr_writer Definitionen. Auch dort sind es nichts weiter als Symbole, die diesen Methoden übergeben werden.

Von einem Symbol kann man sich sowohl den Integerwert wie auch den String-Wert zurückgeben lassen, :medium wäre mit :medium.to_s dann "medium". Symbole können sich aus beliebigen Zeichen zusammensetzen, jedoch muss man diese dann mitunter mit Anführungsstrichen umgeben:

 
a = :Foo
puts a.to_i #>> 18661
puts a.to_s #>> "Foo"
a = :"Foo"
puts a.to_i #>> 18661 -> das selbe, Anführungstriche aber nicht nötig
puts a.to_s #>> "Foo"
a = :"Das ist ein langes Symbol"
puts a.to_i #>> 18879
puts a.to_s #>> "Das ist ein langes Symbol"
 

Genauso, wie man Symbole gut für Alternativen bei der Intitialisierung verwenden kann, kann man sie auch als Rückgabewerte für Methoden verwenden, z.B. für Fehlercodes.

Symbole eignen sich auch für Keys eines Hashes.

 
h = { :name => "Meier", :vorname => "Franz" }
puts h[:name] #>> "Meier"

Symbole sind bei Zugriffen auf Hash-Werte auch schneller, als z.B. Strings, wie dieser Benchmark zeigt:

 
require "Benchmark"

n = 50000
Benchmark.bm do |x|
  x.report { 
    h = {:name => "Meier", 
         :vorname => "Heinz", 
         :mail => "sonstwie@domain.tld"}
    n.times do 
      a = h[:vorname] 
      b = h[:name] 
      c = h[:mail]
    end 
  }
  x.report { 
    h = {"name" => "Meier", 
         "vorname" => "Heinz", 
         "mail" => "sonstwie@domain.tld"}
    n.times do 
      a = h["vorname"] 
      b = h["name"] 
      c = h["mail"]
    end
  }
end

Ergibt auf meinem Rechner:

 
      user     system      total        real
  0.491000   0.000000   0.491000 (  0.491000)
  0.981000   0.000000   0.981000 (  0.981000)

Abschließend lässt sich sagen: Immer dann, wenn man mit Konstanten oder Literalen etwas benennbares definieren will, sollte man darüber nachdenken, ob Symbole ein besseres Mittel sind.

24.05.2005 :: Präventive Programmierung

Permalink

Ich bin erstaunt - da habe ich bei einer Diskussion in den Rubyforen ein Wortschöpfung gewählt, die es bis zu diesem Zeitpunkt Google unbekannt war: "präventive Programmierung". Auch präventive Softwareentwicklung gibt es noch nicht.

Nun, dann bin ich auch in der Verantwortung, näher zu erklären, was ich damit meine: Präventive Programmierung in Ruby

22.05.2005 :: Quellcode browsen

Permalink

Es ist einfach praktisch: In so manchem Quellcode mal eben im Webbrowser hineinzublicken, anstatt sich erstmal ein Paket herunterzuladen.

Sowohl Rubyforge wie auch das raa-Archiv bieten diese Möglichkeit inzwischen. Bei Rubyforge geht man beim Projekt auf den Reiter SCM (SCM steht wahrscheinlich für Source-Code-Managment). Dort kann man dann "Durchsuche CVS Repository" wählen. So landet man in einem Apache ViewCVS, wo man jede Quelldatei anschauen kann. Das klappt natürlich nur, wenn das Projekt auch das CVS von rubyforge nutzt - nicht alle Projekte tun dies.

Im raa-Archiv ist die Funktionalität recht neu. Schön dort: Man kann auch im Quellcode suchen. Auf jeder Projektseite gibt es den Link "Source Code Browser", worüber man den Quellcode browsen kann. Dort ist auch die Suchmöglichkeit untergebracht. Suchen kann man auch generell im RAA-Archiv unter Advanced Search.

Weblinks:

17.05.2005 :: AES-Encryption mit openssl

Permalink

Unter Linux kann man mit aespipe aus dem loop-aes Paket wunderbar z.B. Backups verschlüsseln. Vor ein paar Monaten versuchte ich, ob damit verschlüsselte Dateien mit einem Ruby-Skript zu entschlüsseln sind. Dazu diente mir damals eine AES-Bibliothek, die komplett in Ruby geschrieben war. Die Ergebnisse waren ernüchternd - Ruby ist keine Low-Level-Sprache und für Bitschiebereien nicht geeignet, wenn man Geschwindigkeit braucht. Die Sache war einfach unakzeptabel langsam. Aber immerhin, ich konnte mit Ruby ver- und entschlüsseln.

Die beste Alternative für Verschlüsselung unter Ruby ist die Nutzung der openssl-Bibliothek. Die gehört mittlerweile zur stdlib, ist also auf allen neueren Systemen verfügbar. Leider ist sie bisher kaum dokumentiert.

Stück für Stück fand ich aber heraus, wie sie funktioniert, teilweise durch tiefe Blicke in den Quellcode. Und dann war die Sache ein Klacks, ein Programm zum ver- und entschlüsseln zu schreiben, was nach dem gleichen Verfahren wie loop-aes bzw. aespipe arbeitet.

Hier ist das Ergebnis: RSAESEncryption#openssl

14.05.2005 :: Kommandozeilen Mailer für Windows

Permalink

Nachdem nun das Passwort-Eingabe-Problem unter Windows gelöst ist, konnte ich meinen Kommandozeilenmailer fertigstellen. Ruby bringt ja smtp Unterstützung schon mit, auch smtp-auth, ohne das man ja heutzutage keine Mail mehr an einen öffentlichen Server versenden kann. Es braucht allerdings noch ein wenig Code rundrum, um ein paar Aufbereitungen zu machen.

Das Programm ist einfach gehalten, es werden nur wenig Syntaxüberprüfungen durchgeführt. Mailadressen auf syntaktische Korrektheit zu checken usw. kann eine Menge Aufwand bedeuten. Auch eine Datums-Zeile für den Mailheader wird nicht erzeugt, weil in aller Regel der erste Mailserver das für einen nachholt. Und der hat meist eine genauere Uhrzeit als die des eigenen Rechners.

siehe: rmail - Kommandozeilen Mailer für Windows

13.05.2005 :: mehrzeilige Strings

Permalink

Unter Ruby kann man mehrzeilige Stringliterale nutzen:

 
s= "Das ist ein Text
der aus mehreren Zeilen
besteht und als ein
Stringliteral intpretiert wird."

puts s

13.05.2005 :: ensure wird bei int-Trap abgearbeitet

Permalink

Ein Programm, welches mit strg+c, kill -2 (INT) oder kill -1 (HUP) abgebrochen wird, durchläuft noch den aktuellen ensure Pfad. Muss man also auf jeden Fall Aufräumarbeiten machen, kann man das dort tun und braucht nicht zwangsläufig eine trap-Funktiion zu schreiben.

Bei kill -15 (TERM) klappt das allerdings nicht. Bei kill -9 (KILL) sowieso nicht, weil dies keine Applikation abfangen kann.

13.05.2005 :: Passworteingabe in Shellskripts

Permalink

Wenn man den Benutzer auffordert, ein Passwort einzugeben, sollte das nicht auf der Konsole ausgegeben werden. Unter Linux lässt sich dies einfach über stty erledigen, in dem man das Echo ausschaltet:

 
# getting Password
system "stty -echo"
print "Passwort: "
passwd = $stdin.gets.chomp
print "\n"
system "stty echo"
puts "Passwort: #{passwd}"

Ein Problem damit gibt es aber. Wenn jemand das Skript mit strg-c während der Passwort-Eingabe abbricht, fehlt ihm das echo. Dies lässt sich verhindern, in dem man zuvor die tty-Umgebung sichert und im Falle eines INT-Traps diese wieder restauriert. Man würde also zuvor dies erledigen:

 
stty_save = `stty -g`.chomp
trap("INT") { system "stty", stty_save; exit }

Gleiches lässt sich über einen Ensure-Pfad erreichen. Wenn eine Veränderung von stty nur an dieser Stelle im Programm vorgenommen wird, ist diese Variante sicherlich die Bessere.

 
# getting Password
begin
  system "stty -echo"
  print "Passwort: "
  passwd = $stdin.gets.chomp
  print "\n"
ensure
  system "stty echo"
end
puts "Passwort: #{passwd}"

Eine weitere Möglichkeit der Passworteingabe findet man im Ruby-password Modul hier...

Unter Windows funktionieren diese Lösungen natürlich nicht. Hier kann man jedoch die Win32::Console Bibliothek verwenden.

 
require "Win32/Console"
a = Win32::Console.new(STD_INPUT_HANDLE)
a.Mode(ENABLE_PROCESSED_INPUT)

passwd = ""
loop do
  s = a.InputChar(1)
  if s == "\r"
    break
  end
  passwd << s
end

puts passwd

Win32::Console lässt sich auch von Hand installieren und man braucht nicht unbedingt die C-Bibliothek. Es reicht, wenn man unter lib die Verzeichnisse Term und Win32 des tarballs in ein Ruby Bibliotheksverzeichnis kopiert. Win32::Console hat nämlich 2 Versionen implementiert, eine in pure Ruby und die andere über eine C-Bibliothek.

Meine Win32::Console Release hatte allerdings einen Bug (v1.0/19.01.2004). In der Methode ReadConsole wurde "return lpNumberOfCharsRead.unpack('L')" zurückgegeben, was in "return lpNumberOfCharsRead.unpack('L')[0]" geändert werden musste. Sonst funktioniert die Methode InputChar nicht korrekt und gibt immer nil zurück. Wahrscheinlich wurde nur die C-Bibliothek vernünftig getestet und es könnte sein, dass die pure Ruby Version noch in anderen Methoden den gleichen Fehler beinhaltet (Array Rückgabe anstatt das erste Element des Arrays).

Aus der Diskussion in den Rubyforen habe ich nun eine Windows Version erstellt, die ohne externe Bibliotheken auskommt und die ich deshalb präferiere:

 
require 'Win32API' 

def get_passwd( char=nil, prompt=nil, char_range=0x20..0x7F )
  getch = Win32API.new("msvcrt", "_getch", [], 'L')
  prompt ||= "Password: "
  print prompt
  passwd = ""

  loop do
    c = getch.Call
    case c
      when 0x0d
        break
      when char_range
        passwd << c.chr
        if char
          print char
        end
      else
        STDERR.print "\x7" # BEL if not allowed char
    end
  end
  print "\n"

  passwd
end

passwd = get_passwd( "*" )

puts passwd

Hier kann auch ein Charakter angegeben werden, welches bei der Eingabe von Zeichen ausgegeben wird, typischerweise der "*". Ebenso kann man den Prompt auswählen und auch den Zeichenbereich. Alles über 0x7F ist gefährlich, weil je nach Codepage die Zeichen anders gemappt sind. Das wäre dann wenig portierbar und Passwörter würden bei anderer Konfiguration des Betriebssystemes schon nicht mehr funktionieren. Wer auf Nummer sicher gehen will, belässt es bei der Standardeinstellung von 0x20..0x7F.

Wird ein falsches Zeichen eingegeben, wird BEL ausgegeben, wobei der Rechner dann piepst. Das ist ein gutes Feedback für den Benutzer.

Weblinks:

12.05.2005 :: Ruby Kultobjekte

Permalink

Seit ein paar Wochen laufe ich mit Ruby-Augen durch die Welt. Alles was rubinrot ist, fällt mir auf. Und natürlich muss ich manchmal einfach zuschlagen, wenn mir ein paar Ruby-Kultobjekte über den Weg laufen.

Diese Woche war der Discounter Plus gleich mit zwei Ruby-Artikeln am Markt. Echt cool...

Ruby Uhr für 7,99 Euro bei Plus

Ruby Sonnenbrille 2,99 Euro

11.05.2005 :: Tutorial Reguläre Ausdrücke

Permalink

Reguläre Ausdrücke sind etwas ganz Essenzielles in der Programmierung, besonders in Sprachen wie Ruby oder Perl. Für Programmier-Anfänger sind sie jedoch oft eine große Hürde und es gibt nur wenige Tutorials, die dieses Thema auf eine einfache Weise aufgreifen und erklären.

Als ich die Einladung in Ruby vor anderthalb Jahren schrieb, merkte ich, ich muss mich diesem Thema nochmal separat zuwenden. Die Thematik ist einfach zu kompliziert, um sie in einem kurzen Abschnitt dieses Dokumentes zu beschreiben.

Im März kam dann mal wieder, angeregt durch ein paar Diskussionen auf http://www.rubyforen.de, die Energie in mir auf, mich ans Werk zu machen. Ein erster Anlauf ging ziemlich schief. Nach 5 Seiten Beschreibung hatte ich mich in eine zu komplizierte Sackgasse geschrieben. Ich unterbrach das Unternehmen erstmal. Es ist gar nicht so ohne, diese komplizierte Materie auf einfache Weise zu erklären.

Die letzten Tage machte ich dann einen zweiten Versuch. Im Grunde spielte dabei ein unglücklicher Umstand rein, der sich später als Glück herausstellte: Ich fand das alte Dokument nicht mehr, an dem ich im März arbeitete. Eigentlich wollte ich dort dran weiterschreiben. So musste ich ganz neu anfangen und das war dann auch der Segen. Es schrieb sich recht flüssig runter und der neue Ansatz war irgendwie besser. Es war gut, nochmal gänzlich neu zu beginnen. Das alte Dokument fand ich dann aber doch ein paar Stunden später wieder, hab es aber zu dieser Zeit nicht mehr gebraucht.

Jetzt ist es jedenfalls in einer ersten Form fertig:

Tutorial: Reguläre Ausdrücke in Ruby einfach erklärt

08.05.2005 :: Wozu noch Shell-Skripte?

Permalink

Wenn man erstmal Ruby in der Hand gehabt hat, kommt schnell die Frage auf: Wozu eigentlich noch Shell-Skripte. Die Frage war ja auch schon da, als Perl die Massen eroberte. Im Vergleich zu solchen Skriptsprachen ist die Bash oder Kornshell ziemlich primitiv und angestaubt. Trotzdem, das muss man sagen, lässt sich vieles damit bewerkstelligen.

Ich habe gerade in einem neu erschienenen Buch gelesen: "Scriptprogrammierung für Solaris & Linux; Wolfgang & Jörg Schorn; Addison-Wesley 2004, ISBN: 3-8273-2115-8" Dort vertreten die Autoren die Meinung, man solle alles, was man in Shellskript machen kann, auch damit tun. Nur wenn das nicht geht solle man auf Perl o.ä. ausweichen. Ich dagegen halte es genau umgedreht, ich setze nahezu immer Ruby Skripte ein und vermeide Shellskripte. Damit fahre ich sehr gut. Wer hat denn nun recht?

Die Argumente, die sie vorbringen, kommen mir teilweise wie der verzweifelte Kampf vor, etwas sinnvolles zu finden, was für Shellskripte spricht. Da kommt das Argument, dass Perl keine Standardkomponente des Systems ist, Shell jedoch schon. Dieses Argument ist sicherlich dann gültig, wenn man auf vielen Systemen arbeiten muss, die sowas nicht installiert haben und wo man auch keine Rechte hat, dies nachzuinstallieren. In der Tat gibt es in der Praxis öfters mal solche Konstellationen. Und dann ist es wichtig, nur das zu nehmen, was man überall vorfindet. Andererseits ist das schon fast so, als würde ein Autoschlosser für alles nur eine Zange verwenden, weil er die gewiss immer und überall findet. Keine Schraubenschlüssel, keine Spezialwerkzeuge - alles mit der guten Wasserpumpenzange...

Dieses Argument stimmt also in einigen wenigen Umfeldern, oft ist es aber mittelfristig sinnvoller, einfach die Werkzeuge zu installieren, die man benötigt. Skripte in mächtigeren Sprachen wie Perl oder Ruby werden auf Dauer kostengünstiger sein, weil leichter wartbar, leichter skalierbar und wiederverwendbar. Übrigens: Nahezu alle Linux-Distributionen haben Perl standardmäßig an Board, weil viele Teile des Grundsystems schon von Perl Gebrauch machen. Ruby dagegen muss fast immer nachinstalliert werden.

Ein weiteres Argument ist, dass Shellskripte immer laufen, Perl-Skripte dagegen nicht mehr unbedingt, wenn die Perl-Version auf dem Zielsystem eine andere ist. Auch das ist ein sehr schwaches Argument für Shellskripte, Sprachänderungen sind in Perl und Ruby eher marginal, ältere Interpreterversionen gleichzeitig installierbar. Und man kann natürlich auch Code so schreiben, dass er mit ziemlicher Sicherheit lange unverändert lauffähig sein wird bzw. auf unterschiedlichsten Versionen funktioniert. Und ein Update des Interpreters ist meist schnell erledigt, damit dann auch neuere Skripte laufen. Ich denke, in den meisten Fällen ist die Fortentwicklung der Sprache selbst handhabbar.

Dann kommt das Argument, dass Shellskripte wesentlich weniger Plattenplatz als z.B. C-Programme einnehmen. Was für ein Argument in einer Zeit, wo die kleinsten Festplatten 80GB groß sind. Ich denke, heutzutage interessiert es nur noch in absoluten Ausnahmefällen (z.B. embedded Systeme), ob ein Programm 100 Byte oder 6 KB Platz einnimmt.

All das sind Argumente, die den Einsatz von Shell-Skripts oft nicht rechtfertigen. Will man aber verstehen, warum etwas immer noch so häufig eingesetzt wird, muss man sich die Historie anschauen. Die bedingt immer ganz stark, warum heute etwas ist, wie es ist.

Betrachten wir dazu die Welt, wenn wir heutzutage entscheiden müssten. Heute würde man festlegen, welches Sriptingsystem unixoide Systeme benutzen sollten. 4 Entwickler würden sich treffen und ihre Systeme vorstellen, die zuvor noch nie jemand zu Gesicht bekommen hätte: Perl, Ruby, Python, Shell-Skript.

Aus dieser Perspektive wäre Shell-Skript sicherlich ziemlich lächerlich und es gäbe keinen Grund, auf so eine Sprache zu setzen. Bei den anderen Kandidaten würde man sicherlich lange streiten können.

Nun gibt es aber die Vergangenheit und die legt in großem Maße fest, was Menschen heutzutage tun. Da gibt es viele Menschen, die mit Shellskript aufgewachsen sind. Die fühlen sich dort zu Hause. Die Trägheit ist oft groß, etwas neues zu lernen, wenn doch das Alte prinzipiell funktioniert. Millionen von Shellskripten existieren überall und manifestieren diese Form der Programmierung. Überall müssen Shellskripte gepflegt werden, man verfügt also sowieso über dieses Know-How. Warum dann noch was weiteres lernen?

Kurzum, etwas was durch die lange Geschichte so lange sich manifestiert hat, bleibt mittelfristig erhalten, auch wenn es sich unabhängig von der Geschichte heute betrachtet recht angestaubt anfühlt.

Umgedreht nun: Was sind die Vorteile von modernen Skriptsprachen wie Perl, Python oder Ruby?

  • Sie sklarieren wesentlich besser. Man kann kleinste Wegwerfskripte schreiben und größere Anwendungen. Shellskripte werden dagegen recht schnell unübersichtlich, wenn sie wachsen.
  • Es gibt mächtige Bibliotheken, die einen in der Programmierung unterstützen. Das macht viele Dinge möglich, die mit Shellskripten nicht ohne weiteres funktionieren.
  • Moderne Skriptsprachen bieten Mechanismen zur Fehlerbehandlung (Exception Handling) und zum Datenaustausch/Kommunkation unterschiedlicher Komponenten des Gesamtsystems. Sie können auf moderne Technologien aufsetzen, wie z.B. SOAP (Webservices). Auch Zugriff auf Datenbanken ist leicht möglich. Für nahezu alle Technologien gibt es Schnittstellen zu modernen Skriptsprachen. Ordentliche Fehlerbehandlung führt zur robustem Code. Und zu einer besseren Trennung zwischen Programm-Algorithmen und Fehlerbehandlung.
  • Sie bieten starke Mechanismen zur Code-Wiederverwendung. So kann man für seinen Wirkbereich starke Bibliotheken aufbauen, die in Projekten immer wieder verwendet werden. Das schafft zudem stabilen Code. Bei Shell-Skripten dagegen schreibt man meist alles immer wieder neu.
  • Sie sind meist objektorientiert oder unterstützen die Objektorientierung. Diese schafft viele Vorteile, um gerade größer werdende Skripte besser überschauen und warten zu können. Und es ist die Basis für eine vernünftige Wiederverwbarkeit von Code.
  • Mit einer Sprache lassen sich sowohl kleinere Adminskripte wie auch größere Webapplikationen oder Anwendungssoftware schreiben. Moderne Skriptsprachen sind also wesentlich umfassender einsetzbar und flexibler, als Shell-Skripte.
  • Ruby ist mit seiner Syntax sehr gut wartbar und leserlich. Shell-Skripte wirken oft recht kryptisch. Ähnliches gilt allerdings auch für Perl, die man auch schonmal als Write Only Language (WOL) bezeichnet.
  • Sie orientieren sich mehr am Menschen als an der Maschine. Man muss oft nicht die Gehirnakrobatik bewältigen, wie es bei Shellskripten nötig ist. Man programmiert so einfacher mit mehr Überblick. Oder andersherum: Man kann die Herausforderungen in komplexer Software anstatt in komplexer Syntax suchen.
  • Sie sind mitunter besser portierbar.
  • Schweizer Taschenmesser: Innerhalb von 2 Minuten hat man ein Ruby auf einer Windows-, Mac- oder Linuxplattform installiert. Durch die reichhaltigen Bibliotheken hat man nun Zugriff auf File-IO, Regexp, ssh, ftp, http, SOAP, XMLRPC, pop3, smtp, Logfiles, XML, HTML, IMAP, ntp, telnet, sockets, SSL, GUI, webserver, Multithreading, Win32API, Win32OLE, zip/gzip-Archive, Distributed Programming, md5sum, csv-Reader, CGI-Bibliotheken.
  • leistungsfähige Datenstrukturen und Persistenz (Strings, Hash, Arrays, Collections, Structs, Datenserialisierung z.B. über YAML)

Das Versöhnliche zwischen allen Sprachen zum Schluss: Perl, Ruby und Python wären heute nicht das, was sie sind, wenn es Shell-Skript und andere Werkzeuge wie awk, grep, sed, m4 usw. nicht gegeben hätte. Viele Erfahrungen, die man damit gemacht hatte, konnten mit übernommen werden. In Perl spürt man das noch am deutlichsten - Shell, sed und awk werden darin vereint. Perl ist jedoch viel mehr, als nur diese Vereinigung. Ruby wiederum hat viel von Perl gelernt und übernommen, versucht aber wiederum, einiges besser zu machen, legt vor allem seine Aufmerksamkeit auf gute Objektorientierung und intuitiv verständlichen Code.

Die Zeit ist nicht stehengeblieben und moderne Skriptsprachen sind die Fortsetzung der Ur-Shell-Skript Sprache (daneben und davor gab es natürlich auch einiges). Vieles wurde verbessert und erweitert. So stark, dass heutzutage die Grenzen zwischen Skriptsprachen und "echten Sprachen" verschwinden, es schwer fällt, überhaupt noch so eine Trennung vorzunehmen. Man spricht deshalb auch immer öfter von dynamischen Sprachen.

Eins haben sie allerdings alle gemeinsam: Man braucht sich nicht in tonnenschwere Frameworks einzuarbeiten, um eine einfache Aufgabe zu bewältigen. Bei allen kann man mit Einzeilern anfangen zu programmieren. Man kann schnell mal eben etwas programmieren, dass macht den Einsatz gerade für kleine Adhoc-Lösungen interessant. Und das erinnert mich auch an das geniale der Heimcomputerzeit. Meinen Schneider Z80 Computer konnte ich damals einschalten und sofort ein paar Zeilen Basic einhacken. Es war unkompliziert und durchschaubar. Das mag ich auch heute an Skriptsprachen.

08.05.2005 :: Sonderstellung Konstanten

Permalink

Konstanten haben in Ruby eine gewisse Sonderstellung. Sie sind neben globalen Variablen etwas, was man von überall aus erreichen kann. Konstanten, die im Kontext außerhalb jeder Modul- und Klassendefinition angelegt werden, sind direkt über ihren Namen überall erreichbar. Ansonsten gibt es den, auch aus anderen Sprachen bekannten Scope Operator "::". Über Classname::Constantname kann man dann von überall auf die Konstante zugreifen. Konstanten sind damit immer public, man kann sie nicht verstecken.

Befindet man sich in der Klasse, dem Modul, wo die Konstante definiert wurde, kann man direkt ohne Scope Operator drauf zugreifen.

Hat man eine Konstante in einer Klasse/Module definiert, die gleich heißt, wie eine, die außerhalb existiert, kann man mit ::Constantname auf die äußere zugreifen. Bsp:

 
MYCONSTANT="Aussen"
class MyClass
  MYCONSTANT="Innen"
  def get_innen
    MYCONSTANT
  end
  def get_aussen
    ::MYCONSTANT
  end
end

a = MyClass.new
puts a.get_innen  # >> "Innen"
puts a.get_aussen # >> "Aussen"

Konstanten beginnen mit einem Großbuchstaben, das ist verpflichtend. Man hält sich jedoch normal an die Konvention, wie man es aus anderen Sprachen gewöhnt ist - alle Buchstaben groß zu schreiben.

Konstanten sind übrigens die einzige Anwendungsform des Scope Resolution Operators, soweit ich das überblicke. Für alles andere braucht es diesen auch nicht, weil es z.B. keine globalen Methoden gibt und Instanz-Methoden in Modulen generell privat sind. Siehe auch Weblog-Eintrag vom 07.05.2005.

Übrigens lässt es Ruby zu, Konstanten zu verändern, wirft dabei jedoch eine Warnung aus. Man sollte das nie in normalen Programmabläufen ausnutzen, sonst hätte sich ja der Sinn von Konstanten erübrigt. Es ist vielmehr ein interessantes Hilfsmittel für's Debugging.

Wie konstant sind denn eigentlich Konstanten? Eine Konstante ist ja, wie eine Variable, eine Referenz auf ein Objekt. Das konstant bezieht sich nur darauf, dass die Konstante immer auf dieses Objekt referenzieren wird. Das Objekt dagegen, worauf es referenziert, kann beliebig verändert werden.

 
MYNAME = "Peter"
puts MYNAME  #>> "Peter"

MYNAME.replace( "Joerg" )
puts MYNAME  #>> "Joerg"

MYNAME = "Stefan"  # >> warning
puts MYNAME #>> "Stefan"



Durch Replace wird nur der Inhalt des aktuellen Objekts geändert und das ist völlig in Ordnung. Es ist jedoch verwunderlich, wenn man Konstanten aus anderen Sprachen kennt. Wie oben geschrieben, eine Konstante zeigt auf immer das selbe, aber veränderliche Objekt.

Versucht man jedoch eine Zuweisung, wird MYNAME mit einem anderen Objekt verbunden und das ist "strafbar". Deshalb gibt's hier die gelbe Karte mit einer Warnung.

Wo können Konstanten überall angelegt werden? Nur im globalen Kontext oder in einer Klassen/Moduldefinition. Nicht jedoch innerhalb von Methoden.

07.05.2005 :: Es gibt keine globalen Methoden

Permalink

Methoden außerhalb aller Klassen sind in vielen anderen Programmiersprachen globale Methoden oder Funktionen. Sie sind von überall aus erreichbar und man kann z.B. mit dem Scope-Operator ::methode darauf referenzieren, falls man eine gleiche Methode in der Klasse definiert hat, in der man sich gerade befindet.

Auch wenn der Schein trügt, in Ruby gibt es keine globalen Methoden. Deshalb auch keinen Scope-Operator für Methoden. In Wirklichkeit sind alle Methoden außerhalb von Klassendeklarationen private Methoden der Klasse Object. Definiert man also in diesem Kontext eine Methode, ist das das selbe wie dies hier:

 
class Object
  def meineMethode
    puts "Hello World."
  end
  private :meineMethode
end


meineMethode  # >> "Hello World."

Object ist die Mutter aller Objekte. Methoden, die Object enthält, sind auch in allen Objekten erreichbar. Damit wird klar, warum wir eine solche Methode in sämtlichen Objektmethoden erreichen können. Und auch in Klassenmethoden ist sie erreichbar, weil die Basis jeder Klasse, die Klasse Class ebenfalls als Basis Object hat. Kurzum, immer und überall sind private Methoden von Object erreichbar (public Methoden von Object sowieso).

Der Kontext außerhalb jeder Klasse ist so, als würde man sich in der Klasse Object befinden, wobei jedoch Methoden, abweichend vom Standardverhalten, dort generell privat angelegt werden.

Der Grund, warum alle Methoden im Module Kernel von überall erreichbar sind, ist schlicht die Tatsache, dass dieses Modul in Object included wurde. Damit ist ebenfalls jede Methode aus dem Module Kernel eine Instanz-Methode der Klasse Object.

Dadurch, dass es keine globalen Methoden gibt, sondern alles Instanzmethoden der Klasse Object sind, überlädt eine gleichnamige Methode die zuvor Vorhandene. In anderen Sprachen (z.B. Perl) würde man dann auf beide zugreifen können, einmal mit ::methode und direkt mit methode. In Ruby geht das z.B. über die super Methode, die ja die gleiche Methode in der Basisklasse aufruft.

 
class Foo
  def puts( str )
    super str
  end
end

a = Foo.new
a.puts( "Hello World" )

Übrigens kann man auch Klassenvariablen im "globalen Kontext" anlegen, der dann eine Klassenvariable jedes Objektes ist.

 
@@test = "Hello"

class Foo
  def get_test
    @@test
  end
end

a = Foo.new
puts a.get_test #>> Hello

Instanzvariablen dagegen funktionieren nicht. Wer den Grund weiß - ich bin neugierig auf eine Antwort.

Nachtrag 30.05.2005: Dank an WoNaDo (rubyforen). Er hat mir erklärt, warum Instanzvariablen hier nicht funktionieren. Jetzt wird es mir auch klar. Eine Instanzvariable ist in diesem Kontext ja eine KlassenInstanzvariable. Und diese wird nicht mitvererbt sondern ist privat für die Klasse, in der sie angelegt wird, hier also für Object. Somit kommt man über Foo nicht mehr heran, auch wenn diese Klasse von Objekt erbt. Hier ein Beispiel, was das nochmal verdeutlicht:

 
class Foo
  @name = "Ich bin Foo"
  def self.getname
    @name
  end
end

class FooSohn < Foo
 #@name = "Ich bin FooSohn"
end

puts Foo.getname       # >> "Ich bin Foo"
puts FooSohn.getname   # >> nil

FooSohn.getname findet keine Klasseninstanzvariable @name und gibt deshalb nil zurück. Ein Zugriff auf Foo->@name geschieht nicht. Legt man dagegen in FooSohn ein @name an, wird dort der Inhalt dieser Variablen zurückgegeben.

05.05.2005 :: Reichlich asserts benutzen

Permalink

Beim coden kommt man immer wieder an Punkte, wo man genau weiß, hier muss diese oder jene Bedingung auf jeden Fall erfüllt sein oder hier darf auf keinen Fall dieses passieren. Man weiß es in dem Moment, wo man den Code schreibt, man weiß auch, dass dieser Fall nicht auftreten dürfte. Doch wir überblicken nicht alles und so passieren eben doch Dinge, die wir nicht vorausschauen.

Unter C/C++ habe ich gelernt, viel mit asserts zu arbeiten, Überprüfungen im Code, dass ein erwarteter Zustand tatsächlich erfüllt ist. Natürlich kann man dieses Konzept in jede Sprache übernehmen.

Hier ein Beispiel in Ruby:

 
DEBUG = true
def x_assert
  if DEBUG
    raise "assertion error"
  end
end

begin
  file = File.open( "meinfile", "w" )
  # ... some code
rescue
  #...
end

x_assert if file

Ein typischer Fall, wo ein close vergessen wurde. Einerseits sollte man sich angewöhnen, immer gleich ein close zu schreiben, wenn man ein open anlegt. Oder die Blockform benutzen, was aber nicht immer geht.

Andererseits ist es geschickt, ein x_assert einzufügen. Man weiß ja: An diesem Punkt muss file == nil sein. Alles andere ist ein Fehler.

Der Einsatz von x_assert ist sozusagen Prophylaxe. Überall, wo wir ein Übel erahnen, bauen wir sicherheitshalber eine assert-Überprüfung ein. Wenn man einmal anfängt, sein Aufmerksamkeit auf mögliche Übel zu richten, geht es bald schon automatisch. Man gewöhnt sich an, auf problematische Konstellationen zu achten und füttert sein Programm mit reichlich asserts.

Und dann wird man beim programmieren staunen, wie oft doch asserts einschlagen, die man so nicht vermutet hätte. Und das macht Debugging recht schnell möglich. Und es fallen Fehler auf, die vielleicht sonst gar nicht sichtbar geworden wären.

Asserts sind auch sehr hilfreich, wenn jemand den Code liest und verstehen will. So findet man Stellen im Programm, die explizit zeigen, dass an einer Stelle, mit jenem Verhalten gerechnet wird. Es ist damit gleichzeitig ein Stück Dokumentation.

Asserts schaltet man in der Regel im Produktivcode ab, deshalb auch die DEBUG-Variablenprüfung. Wenn es nicht auf Laufzeitgeschwindigkeit ankommt, kann es auch Sinn machen, sie drinzulassen. Oder man schreibt für die Produktivvariante einen anderen Assert-Code, z.B. nur eine Warnung in einem Logfile.

Nachtrag 08.07.2005: Florian hat mir geschrieben und auf seine ruby-breakpoint Bibliothek hingewiesen, die auch asserts implementiert. Ich werde mir das die Tage mal anschauen. Projekthomepage hier...

05.05.2005 :: Variablen immer Referenzen auf Objekte

Permalink

Variablen sind immer Referenzen auf Objekte. Damit können sehr unschöne Seiteneffekte auftreten, wenn man nicht dran denkt.

 
class MyTest
  def initialize( name )
    @name = name
  end
  def to_s
    @name
  end
end

name = "Winfried"
s = MyTest.new( name )

puts s.to_s # >> Winfried

name.replace( "Heiner" )

puts s.to_s # >> Heiner!!!


Frechheit! Da verändert mir doch jemand von außen Member meines Objektes s! Obwohl da kein attr_writer existiert.

Und so funktoniert es: Das Objekt s wird angelegt und mit name initialisiert. Dabei wird das Stringobjekt aber nicht kopiert sondern es wird nur eine Referenz auf dieses Objekt in s abgelegt. Jetzt existieren 2 Referenzen auf das selbe Objekt, einerseits @name im Objekt s und die lokale Variable name. Wenn später über name.replace dieses Objekt verändert wird, verändert sich auch das Objekt s. Genaugenommen nur das String-Objekt, welches ein Member von Objekt s ist.

Aus diesem Grund sagt man auch: Methoden, die ein Objekt selber verändern, sind immer mit etwas Vorsicht anzuwenden. Einige dieser Methoden haben deshalb auch das Ausrufezeichen im Namen.

Anders ist es, wenn wir statt name.replace eine Zuweisung gemacht hätten:

 name = "Heiner"

Dann nämlich wird die Referenz name mit einem gänzlich neuen String-Objekt verbunden. Die Bindung zum alten Stringobjekt ist aufgehoben und nur noch über s.to_s erreichbar.

Wer ganz sicher gehen will, kann natürlich auch über clone die Objekte duplizieren. Das kostet aber auf jeden Fall zusätzlich Laufzeit und Speicher.

 
class MyTest
  def initialize( name )
    @name = name.clone
  end
  def to_s
    @name
  end
end

name = "Winfried"
s = MyTest.new( name )

puts s.to_s # >> Winfried

name.replace( "Heiner" )

puts s.to_s # >> Winfried


05.05.2005 :: Platformabhängigkeiten

Permalink

Die meisten Dinge in Ruby sind plattformunabhängig implementiert. Es gibt jedoch einige Bibliotheken, die das nicht leisten. Und spätestens dann, wenn man externe Programme aufruft, ist es meist vorbei mit Plattformunabhängigkeit. Ein Aufruf von `chown root:root file` funktioniert auf allen Unix-Plattformen prächtig, nicht jedoch auf einer Windows-Plattform. Ein Grund, warum man auf externe Programmaufrufe verzichten sollte, wenn Ruby eigene Implementierungen anbietet.

Oft geht es um die Unterscheidung zwischen Windows und unixoiden Systemen. Zwei Welten, die in vielerlei Hinsicht unterschiedlich sind. Und es sind die beiden Hauptsysteme, auf denen Ruby läuft. Hierfür eignet sich dieser Code:

 
def platform
  case RUBY_PLATFORM
    when /linux/i, /freebsd/i, /bsd/i, /solaris/i, 
         /hpux/i, /powerpc-darwin/i
      :unix
    when /mswin32/i, /mingw32/i, /cygwin/i, /bccwin32/i
      :windows
    when /java/i
      :java
    else
      :other
  end
end

puts platform

03.05.2005 :: Dir['**/*'] findet keine Hidden-Files

Permalink

Muss man einfach wissen: Mittels Dir['**/*'] findet man ja alle Dateien in einem Verzeichnis rekursiv. Allerdings findet man keine Dateien, die mit einem Punkt beginnen, also solche, die unter Linux normal versteckt werden. Möchte man auch diese finden, muss man zusätzlich Dir['**/.*'] verwenden, welches nur die versteckten Dateien und auch die "." und ".." Verzeichnisse findet. Ein Ausdruck, welcher alles findet, ist mir nicht bekannt. Wem was einfällt, bitte melden.

Hier ein Codebeispiel, welches alle Dateien unterhalb eines bestimmten Pfades findet. Dabei wird der base_path später weggeschnitten. Zurückgegeben wird also nicht <base_path>/meinpfad/meinedatei sondern einfach nur meinpfad/meinedatei, wie man es ja oft braucht:

 
# Get all regular files under base_path
def all_files( base_path )
  gdir = File.join(base_path,"**/*")
  gdir_hiddenfiles = File.join(base_path,"**/.*")
  f = Dir[gdir]
  f.concat( Dir[gdir_hiddenfiles] )
  f = f.select { |x| File.file?( x ) } 

  # convert base_path/path/file to path/file
  base_path_end = gdir.length - 4
  f.each do |file|
    file.replace( file[base_path_end..-1] )
  end
  f
end

01.05.2005 :: Distributed Ruby (dRuby, SOAP, WSDL, CORBA, XMLRPC)

Permalink

Hab mal einen Artikel dazu im Rubywiki angelegt:

http://rubywiki.de/wiki/ThemaDistributedRuby

30.04.2005 :: Kalenderwoche ermitteln

Permalink

Logfiles werden gerne so geschrieben, dass man die Kalenderwoche in den Dateinamen hängt, z.B. access.log.17.gz. Möchte man sowas von einem ftp-Server abholen, braucht man die aktuelle Kalenderwoche. In Ruby bekommt man die so:

 
require 'date'

t = Time.new
d = Date.new( t.year, t.month, t.day )

week = d.cweek
filename = "access.log.#{week}.gz"
puts "Filename: #{filename}"

30.04.2005 :: ftp Download

Permalink

Sich eine Datei via ftp zu holen, ist mit Ruby einfach:

 
require 'net/ftp'

FTP_SERVER = "ftp.ruby-lang.org"
FILE       = "/pub/ruby/1.8/changes.1.8.0"
USER       = "anonymous"
PASS       = "guest@home"

Net::FTP.open( FTP_SERVER ) do |ftp|
 ftp.login( USER, PASS )
 ftp.getbinaryfile( FILE, File.basename( FILE ) ) 
end

Hier wird die Datei changes.1.8.0 vom Server geholt und ins aktuelle Verzeichnis abgelegt.

In Ruby 1.6.x war open ohne Blockaufruf implementiert. Eine Version, die sowohl unter Version 1.6 wie 1.8 funktioniert, ist diese:

 
require 'net/ftp'

FTP_SERVER = "ftp.ruby-lang.org"
FILE       = "/pub/ruby/1.8/changes.1.8.0"
USER       = "anonymous"
PASS       = "guest@home"

ftp = Net::FTP.open( FTP_SERVER )
  ftp.login( USER, PASS )
  ftp.getbinaryfile( FILE, File.basename( FILE ) ) 
ftp.close

Was manchmal störend sein kann, sind die langen Timeout Zeiten. Bis die ftp-Bibliothek aufgibt, können schonmal 10 Minuten vergehen. Besser ist es dann, eigene Timeouts zu setzen, sowohl für's Login wie auch für die Übertragung. Ruby hat hierfür die timeout-Bibliothek.

 
require 'net/ftp'
require 'timeout'


FTP_SERVER = "ftp.ruby-lang.org"
FILE       = "/pub/ruby/1.8/changes.1.8.0"
USER       = "anonymous"
PASS       = "guest@home"

CON_TIMEOUT      = 30
TRANSFER_TIMEOUT = 600

ftp = nil
begin
  timeout( CON_TIMEOUT ) do
    ftp = Net::FTP.new( FTP_SERVER )
    ftp.login( USER, PASS )
  end

  timeout( TRANSFER_TIMEOUT ) do
    ftp.getbinaryfile( FILE, File.basename( FILE ) )
  end

rescue
  STDERR.puts "Error ftp-transfer server: #{FTP_SERVER}"
  raise
ensure
  ftp.close if ftp
  GC.start
  sleep 5
end

Im ensure Pfad wird auch im Fehlerfall der Socket geschlossen. Bei Tests in Ruby 1.6.x habe ich herausgefunden, dass im Fehlerfall mitunter Sockets nicht korrekt geschlossen wurden. Erst wenn ich den Garbage Collector mit GC.start aufgeräumt habe, war das Schließen erfolgreich. Danach muss noch eine kurze Zeitspanne gewartet werden, damit die Sockets auch im Fehlerfall sauber abgebaut werden. 5 Sekunden ist dabei ein Daumenwert, den man sicherlich noch besser ausloten kann.

27.04.2005 :: Netter Einzeiler - Uhrzeit bitte

Permalink

 
# ruby -e "loop { puts Time.now; sleep 1 }"

Quelle: http://www.rubyforen.de/ltopic,658,0,asc,0.html

Verbesserte Version:

 
# ruby -e "loop {print Time.now; STDOUT.flush; sleep 1; print 8.chr*80}"

27.04.2005 :: Eigene Range-Erweiterungen

Permalink

Die Range-Klasse funktioniert nicht nur für eingebaute Klassen, wie Integer oder String, sondern auch für beliebige andere Klassen. Einzige Voraussetzung ist, dass zwei Methoden in dieser Klasse verfügbar sind: <=> und succ.

 
class Strich
  include Comparable
  def initialize( length)
    @length = length
  end
  attr_reader :length

  def succ
    Strich.new( @length + 1 )
  end

  def <=>( other )
    @length <=> other.length
  end

  def to_s
    "-" * @length
  end
end

a = Strich.new(1)
b = Strich.new(10)

(a..b).each do |strich|
  puts strich.to_s
end


Ergebnis:

 
-
--
---
----
-----
------
-------
--------
---------
----------

27.04.2005 :: Kommaseparierte LValues, RValues

Permalink

Wenn ein Operator mit zwei Ausdrücken was tut, dann steht der eine normal links, der andere rechts vom Operator. Daher kommen die Begriffe rvalue (right value) und lvalue (left value). Soviel zur Begriffsbestimmung.

Der Zuweisungs-Operator = wird ja in der Form lvalue = rvalue benutzt. In Ruby gibt es nun sogenannte Parallele Zuweisung, es dürfen dabei mehrere lvalues und rvalues existieren, die kommasepariert werden.

 
vorname, name = "Heinz", "Meier"
a = "Heinz", "Meier"
vorname, name = ["Heinz", "Meier"]

Wenn rechts mehrere Variablen stehen, wird daraus ein Array gemacht. Alle 3 Beispiele ergeben auf der rechten Seite ein Array mit 2 Elementen, welches dann der linken Seite zugewiesen wird. Die linke Seite nimmt das erste Element des Array und weist dies der linkesten Variablen zu, das zweite Element der zweiten von links usw. Wenn rechts weniger Elemente vorhanden sind, wie links Variablen vorhanden, werden die restlichen linken Variablen mit "nil" belegt, was man auch so erwartet. Und umgedreht kann links nur soviel übernommen werden, wie Variablen da sind.

Mit diesem Mittel lässt sich nun einiges anstellen:

Elemente aus einem Array übernehmen:

 
meinArray = [1,2,3,4]
first, second = MeinArray # >> first = 1, second = 2

a,b,c,d = (0..3).to_a     # >> a=0, b=1, c=2, d=3

dummy, dummy, first, second = MeinArray #> first = 3, second = 4

first, second, *rest = MeinArray # >> rest = [3,4] 

Letztere Form ist eine Besonderheit. Wenn dem letzten lvalue ein Stern vorangestellt wird, verhält es sich ähnlich wie bei Methodenaufrufen. Diese Variable übernimmt dann alle restlichen Werte, hier also ein Array mit [3,4]. Wichtig ist hier, dass eine *variable immer vom Typ Array ist, auch wenn nur noch ein Wert übriggeblieben ist, den diese übernimmt.

Könnte ein Stern auf der rechten Seite Sinn machen? Ja, es expandiert ein Array in einzelne Werte:

 
a,b,c,d = 1,2,[3,4]  # >> a = 1, b = 2, c = [3,4], d = nil
a,b,c,d = 1,2,*[3,4] # >> a = 1, b = 2, c = 3, d = 4
a,b,c,d = 1,2,3,4    # >> identisch zum vorherigen

Bildlich gesprochen, wird das Array ausgeschüttet und die einzelne Werte ordentlich kommasepariert in die Zeile gehängt.

Parallelzuweisung:

 
a = 10
b = 20
c, d = a, b # >> c = a, d = b 

a, b = b, a # >> a und b vertauschen ohne Hilfsvariable

a = 10
c, d = a, a + 10 # >> c = 10, d = 20 

Zu allem Überfluß gibt es nun noch die Möglichkeit, die linke Seite zu klammern. Ich denke, in der Praxis wird man das kaum brauchen. Wer darüber mehr wissen will, schaue mal im PickAxe II Buch auf Seite 93 nach.

26.04.2005 :: Projekt Text-Template-Engine (rtemple)

Permalink

Hab mal ein Konzeptpapier verfasst: RubyProjektRtemple

Diskussion unter: http://www.rubyforen.de/topic,713.html

26.04.2005 :: Ruby Change Requests ist Online

Permalink

http://rcrchive.net

26.04.2005 :: Design-Grundsätze

Permalink

Ich bin gerade über eine Mail von Patrick Michaud gestolpert, der der Hauptentwickler des PmWiki ist. Er hat sich zu Anfang seines Projektes einige Design-Grundsätze definiert, an denen er sich immer wieder ausrichtet. Das ist eine sehr gute Idee.

Ein Grundsatz für PmWiki hat er in der gestrigen Mail beschrieben: "simple things should be simple and complex things possible". Es ging dabei um die Diskussion, die Wiki-Sprache immer mehr aufzublähen mit allen möglichen Spezialfeatures. Der Grundsatz sorgt jetzt dafür, dass man im Eifer des Gefechts nicht alle möglichen Dinge implementiert, die die Benutzung für Anfänger komplizierter machen. Einfache Dinge, die Anfänger tun, sollen einfach bleiben. Wer kompliziertes machen will, kann das auch tun, jedoch muss er dann auch mit komplizierteren Ausdrücken rechnen. Lieber wird versucht, einfache Dinge einfach zu halten, auch wenn das dazu führt, das komplizierte Dinge etwas komplizierter werden. Er bevorzugt also die Leute, die Anfänger sind. Das ist eine sinnvolle Designentscheidung, braucht es doch bei einem Wiki besonders eine Einladung an Anfänger, damit die Einstiegsschwelle nicht so hoch ist. Ein Wiki braucht ja vor allem Schreiber und daran mangelt es oft. Also muss man denen einen roten Teppich ausrollen.

Das ist übrigens ein weiterer Designgrundsatz von PmWiki - "Mach es den Schreibern möglichst einfach."

Im Eifer des Gefechts verliert man bei Software-Entwicklung das Wesentliche aus den Augen. Wollte man zuerst eine einfach benutzbare Software schreiben, bemerkt man beim Fortschreiten des Produktes gar nicht mehr, dass alles viel zu kompliziert wird, weil man es selber gut kennt und die Komplexität nicht mehr wahrnimmt.

Von daher ist es für jedes Softwareprojekt gut, sich Design-Grundsätze zu suchen und diese schriftlich zu fixieren. Einfache, prägnante kurze Sätze. Der Prozess des Schreibens solcher Grundsätze ist wichtig, weil sich da erstmal herauskristallisiert, was man eigentlich will. Das, was zuvor nur so nebulös im Raum rumschwirrte, wird jetzt klar und prägnant, wird eindeutig und be-greifbar.

Solche Grundsätze sind auch wichtig für ein Team von Entwicklern, damit man sich auf eine gleiche Marschrichtung einschwören kann. Es müssen natürlich Grundsätze sein, die für alle Entwickler annehmbar sind. Nur so kann man sich von ganzem Herzen auf etwas einlassen. Und gute Software entsteht ja nicht daraus, dass Leute ihren Job machen, sondern eher, dass Menschen von ganzem Herzen an etwas arbeiten.

Im Laufe der Entwicklung kann man immer wieder die Grundsätze zur Hand nehmen und sich neu ein-norden. Der Entwicklung wieder zur ursprünglichen Richtung verhelfen. Am besten auch im Team.

Softwareprojekten merkt man es schnell an, ob sie von ein paar Grundsätzen geführt werden oder ob sie sich chaotisch entwickeln. Gute Software braucht Grundsätze, woran sie sich ausrichtet, weil nur so beständig bestimmte Konzepte weiter und weiter wachsen. Wenige starke Konzepte sind viel besser, als ein wilder Haufen schwacher Konzepte, die auch noch permanent wechseln. Der meisten Software mangelt es an gut durchdachten, leistungsfähigen und flexiblen Konzepten. Meist deshalb, weil sich um Design, Design-Grundsätze und Architektur zu wenig Gedanken gemacht wird.

Neben den Design-Grundsätzen für ein konkretes Produkt gibt es natürlich auch noch generelle Grundsätze für gute Software. Jeder wird mit der Zeit durch Erfahrung einiges herausfinden, was grundsätzlich wichtig ist. Und da lohnt es sich, dass mal schriftlich zu fixieren. Um daraus vielleicht ein paar eigene goldene Programmiergrundsätze zu extrahieren.

Grundsätze sind Anker, an denen man sich festhalten kann. Etwas, was man sonst im Alltag aus den Augen verliert.

26.04.2005 :: Dynamische Variablen

Permalink

In einem aktuellen Projekt brauchte ich Variablen, deren Werte sich aus einem Stück Ruby-Code erzeugen. Das ist dabei herausgekommen und hat mir mal wieder gezeigt, wie genial dynamisch Ruby ist.

 
$local = {}

def localvar( varname, &block )
  $local[varname] = block
end

localvar( "Test" ) {
  Time.new.to_s
}

localvar( "Test1" ) {
  "Deine Glueckszahl: #{rand(99).to_s}"
}

puts "Test:  #{$local['Test'].call}"
puts "Test1: #{$local['Test1'].call}"


Durch Aufruf von localvar() können beliebige Variablen definiert werden, deren Wert durch beliebigen Ruby-Code gebildet wird. Und zwar erst in dem Moment, wo man darauf über $local[var].call darauf zugreift. Erst dann wird der Codeblock ausgeführt und der Wert ermittelt.

26.04.2005 :: InPlace Array.each

Permalink

Man stelle sich vor, in einem Array sind eine Reihe von Strings, denen alle die ersten beiden Buchstaben weggeschnitten werden sollen. Dafür braucht man kein neues Ziel-Array zu erzeugen sondern kann das InPlace machen. Der Trick ist, string#replace zu verwenden.

 
a = [ "xxEins", "xxZwei", "xxDrei" ]

a.each do |s|
  s.replace( s[2..-1] )
end

p a  # >> ["Eins", "Zwei", "Drei"] 

Würde man es mit "s = s[2..-1]" versuchen, so weist man s ein neues String-Objekt zu. Was man aber will: Das Objekt verändern, worauf s gerade zeigt. Denn das ist genau das Objekt, worauf auch im Array referenziert wird. Man muss dieses Objekt verändern und dafür gibt es nur recht wenige Möglichkeiten. Eine davon ist replace, eine andere ist "<<".

Andersherum muss man aufpassen, dass man nicht Elemente des Array verändert, obwohl man das gar nicht wollte. Hier ein Beispiel:

 
a = [ "Eins", "Zwei", "Drei" ]

a.each do |s|
  puts s << " ist es!"
end

p a  # Ups: >> ["Eins ist es!", "Zwei ist es!", "Drei ist es!"] 

 

12.04.2005 :: Neue integrierte Doku

Permalink

Ist zwar keine neue Doku, aber eine gute Integration unterschiedlicher Quellen. Die Ruby Docbar:

http://www.ruby-doc.org/docbar/toc_ref.html

10.04.2005 :: openssl die Zweite

Permalink

Nachdem ich über die Schwierigkeiten mit openssl hier geschrieben hatte, machte ich noch einige Versuche. Und keine Stunde später fiel mir die Merkwürdigkeit auf, dass der übergebene Key irgendwie intern gehasht wurde. Denn es waren Schlüssel länger als 16 Byte zulässig, die jeweils auch andere Ergebnisse lieferten. Allerdings fand ich auch heraus, dass man nach der Initialisierung den key neu setzen kann. Und diese Methode hasht den Key nicht, sondern nimmt ihn direkt als Key zum verschlüsseln. Und damit funktionierte es dann tatsächlich. So muss das in etwa aussehen:

 
require 'openssl'

aes = OpenSSL::Cipher::Cipher.new("AES-128-CBC")

iv  = "\x00"*16
key = "\x00"*16
text = "\x00"*32

aes.encrypt(key, iv)      # gehashter Key, den wir so nicht brauchen
aes.key = key             # Key direkt gesetzt.
ctext = aes.update( text )
ctext << aes.final

Testweise habe ich mal ein 70MB großes File entschlüsselt, was ich zuvor mit aespipe verschlüsselt hatte. Und siehe da, alles funktioniert. Der Rest ist jetzt eine reine Fleißaufgabe. Ich möchte ein Werkzeug zusammenbasteln, welches wie aespipe Dateien nach dem AES-Algorithmus ver- und entschlüsselt.

Interessant ist auch, dass openssl viele weitere Cipher/Verschlüsselungsalgorithmen kennt, die alle über die selbe Schnittstelle bedient werden. Es ist also ein Einfaches, auf einen anderen Cipher zu wechseln oder mit unterschiedlichen Ciphern mehrfach zu verschlüsseln.

Openssl und ruby-openssl ist also rehabilitiert. Auch wenn die Dokumentation mitunter grauenhaft oder schlicht nicht vorhanden ist, so scheint es doch zu funktionieren.

09.04.2005 :: openssl - ein großer Sumpf

Permalink

Spaß macht es nicht, sich mit openssl auseinanderzusetzen. Hab mich gerade 5 Stunden durch diverse Quellen durchgeackert. Anfangs sah alles ganz einfach aus. Ich wollte mein AES-Encryption Werkzeug auf openssl umstellen, weil die bisherige Pure-Ruby Bibliothek viel zu langsam ist. Das Ruby Binding ruby-openssl bietet dafür tatsächlich den AES-128-CBC Cipher an und wie es mir schien, wäre der Einsatz total simpel.

Im Grunde scheint es das auch. Hier ein Stück Code, was eigentlich funktionieren sollte:

 
require 'openssl'

def bin2hex( bin_value )
  hex_value = ""
  bin_value.each_byte do |b|
    hex_value << sprintf( "%2.2x", b)
  end
  hex_value
end

key = "\x00"*16
iv  = "\x00"*16
text = "\x00"*32

aes_ssl = OpenSSL::Cipher::Cipher.new("AES-128-CBC")

aes_ssl.encrypt(key,iv)
ctextB = aes_ssl.update( text )
ctextB << aes_ssl.final

puts bin2hex( ctextB ) # >> 53ca7e686f065...

Bei diesem Standard-Testfall sollte aber dies herauskommen:

 
Key  : 00000000 00000000 00000000 00000000
IV   : 00000000 00000000 00000000 00000000
Plaintext: 00000000 00000000 00000000 00000000
Ciphertext: 66e94bd4ef8a2c3b884cfa59ca342b2e00

Soviel kann man bei dieser Sache ja eigentlich gar nicht falsch machen. Weil aber nichts funktionierte, habe ich mir erstmal die cvs-Quellen von ruby-openssl besorgt. War gar nicht so einfach, weil die Download-Angaben im raa-Archiv nicht funktionierten. Irgendwann bin ich aber fündig geworden. Die Quellen sind schlecht dokumentiert, sehr schwer, da durchzusteigen.

Als nächstes versuchte ich mein Glück mal direkt bei openssl.org. Auch dort ist alles saumäßig dokumentiert. Quellcode-Beispiele findet man im Netz kaum, vor allem nicht zum Einsatz der Cipher. Vielleicht liegt das daran, dass openssl wg. der schlechten Doku alles andere als einladend ist. Da macht man lieber einen großen Bogen herum. Zum Einsatz in Ruby fand ich so gut wie nichts, was für den konkreten Fall relevant gewesen wäre. Hat überhaupt schonmal jemand in Ruby mittels openssl AES verschlüsselt?

Die Tests, die bei ruby-openssl dabei sind, entschlüsseln und verschlüsseln lediglich hintereinander und das funktioniert auch bei mir. Nur ist das verschlüsselte Resultat eben nicht das, was es lt. Standard sein soll. Zu allem Überfluß gibt es in openssl Testfälle für AES-128-CBC, deren Ergebnis aber von der Norm abweicht (evptests.txt, AES-128-CBC Encrypt Testfall). Sehr merkwürdig.

Ich versuchte auch mal den rc4 Cypher, weil AES noch recht neu ist, man es kaum in der Doku findet. Mittels openssl hab ich erstmal einen Testfall ausprobiert:

 
openssl rc4 -in test.bin \
-K 00000000000000000000000000000000 \
-iv 0000000000000000 > test.bin.rc4

In test.bin stehen 8 Null-Bytes. Das Ergebnis ist: de 18 89 41 a3 37 5d 3a. Das stimmt mit den Angaben überein, was man auch in evptests.txt der openssl-Distribution findet. Das scheint also ein gültiger Wert zu sein. Mittels openssl in Ruby bekomme ich für diesen Testfall jedoch 98 88 d1 72 b1 18 6b b3.

Es könnte natürlich sein, dass ich noch Fehler in der Anwendung mache. Allerdings beschleicht mich immer mehr das Gefühl, dass openssl ein großer Sumpf ist und die ruby-openssl auch nur partiell getestet. Unterstützung, um mit solchen Problemen weiterzukommen, findet man kaum. Schön wäre, wenn man Standard-Testfälle finden könnte, die zeigen, dass ruby-openssl korrekte Ergebnisse liefert. Die gibt es aber nicht.

Für heute reicht es erstmal und ich gehe etwas frustriert zu Bett...

08.04.2005 :: rsplit - Dateien Splitprogramm

Permalink

Ich hab mal ein kleines Skript geschrieben, mit dem man Dateien splitten kann, um z.B. ein großes Image auf mehrere CD's zu verteilen. Sowas ist ja ein rudimentäres Werkzeug und Windows bietet da standardmäßig nichts.

Programm und Doku siehe hier: RSRsplitRb

07.04.2005 :: Artikel zum Modul optparse

Permalink

Fast immer, wenn man ein Kommandozeilen-Werkzeug schreibt, braucht man auch einen Option-Parser. In Ruby wird das alte getoptlong durch optparse abgelöst, weil es eine intelligentere Schnittstelle bietet.

Ich habe in den letzten Tagen damit experimentiert und eine ausführliche Dokumentation geschrieben: RubyOptParse

05.04.2005 :: Konfigurationsmanagment mit Gnome gconf

Permalink

Gnome gconf ist ein modernes Konfigurations-Managmentwerkzeug, welches ähnlich wie die Windows Registry hierarchische Konfigurations-Informationen verwaltet. Es stellt ein zentrales Konfigurationssystem für Gnome dar, kann aber auch völlig unabhängig von Gnome verwendet werden. Interessanterweise gibt es hierfür eine Ruby-Bindung.

Weblink:

04.04.2005 :: *arg - Der Stern bei Parameterübergabe

Permalink

Es gibt zwei Formen, bei denen der Stern bei Parametern eine besondere Bedeutung hat:

 
def myMethode( *arg )
  p arg
end

a = [1,2,3]
myMethode( a )         # >> [[1, 2, 3]]
myMethode( a, a, a )   # >> [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
myMethode( *a )        # >> [1, 2, 3]

Bei der Definition einer Methode, kann ein *Parameter beim Aufruf mehrere Argumente entgegen nehmen. Er wird also für variable Parameterübergaben verwendet. Jedes durch Komma separierte Argument-Objekt wird in ein Array abgelegt. In der Methode ist dann arg also ein Array.

Weil wir im ersten Aufruf ein Array übergeben, haben wir ein Array in einem Array. Der zweite Aufruf verdeutlicht das nochmal, 3 Arrays im arg-Array.

Beim letzten Aufruf hat der Stern die Bedeutung, das übergebene Array in getrennte Argumente aufzulösen. Also nicht das Array als ein Argument zu übergeben, sondern jedes Element als ein Argument. Es wäre also gleichbedeutend mit myMethode( 1, 2, 3 ) bzw. myMethode( a[0], a[1], a[2]).

Die Argumente werden übrigens immer per Reference übergeben, nicht per Value. Das bedeutet, dass man übergebene Argumente nicht verändern sollte, weil man sonst böse Überraschungen erlebt. Es sei denn, es ist explizit vereinbart, dass ein Argument verändert wird. Es gilt jedoch, dies möglichst zu vermeiden, weil diese Form der Kommunikation zwischen Methode und dem Aufrufkontext wenig offensichtlich ist und oft zu unerwarteten Programmverhalten bzw. Fehlern führt. Besser eine Methode gibt nur einen Return-Wert zurück.

Hier ein Beispiel:

 
def myMethode( s )
  s << "World"
end

s = "Hello "

t = myMethode( s ) 


puts t      # >> OK: "Hello World"
puts s      # >> Ups, war nicht gewollt... "Hello World"

04.04.2005 :: undefined ist nicht nil

Permalink

Eine Variable, die nicht definiert ist, ist nicht das gleiche, wie eine Variable, die den Inhalt nil hat.

 
a = nil
puts a.to_s  # >> ""
puts b.to_s  # >> Exception: NameError undefined local variable

04.04.2005 :: Rescue Statement Modifier

Permalink

Eine Neuerung in Ruby 1.8 ist der rescue Modifier. Rescue wird ja eigentlich im Zusammenhang mit einem Begin/End Block gebraucht:

 
begin
  # Code
rescue
  # Code wird ausgeführt, wenn Exception aufgetreten ist
ensure
  # Code wird in jedem Fall ausgeführt, ob Exception oder nicht
end

In Ruby 1.8 gibt es nun eine weitere Bedeutung:

 
s = a.to_s rescue "a nicht definiert"
puts s

Wenn der Ausdruck links eine Exception abwerfen würde, wird der Ausdruck nach rescue ausgeführt. Wenn also in diesem Beispiel a nicht definiert ist, wird der String s mit "a nicht definiert" gefüllt.

Möchte man mehrere Befehle hinter rescue ausführen, muss man diese klammern, damit rescue es als ein Ausdruck sieht:

 
f = File.open( "MyFile" ) rescue (STDERR.puts("Datei nicht vorhanden.");
                                  exit 1)

04.04.2005 :: Methoden Codeblock übergeben

Permalink

Methoden kann man neben Argumenten auch einen Codeblock übergeben. Das geht auch für initialize:

 
class MyClass
  def initialize
    yield self if block_given?
  end
  attr_accessor :name, :color
end

m = MyClass.new do |o|
  o.name  = "Mein Objekt"
  o.color = "#00FF00"
end


p m

Codeblöcke werden wie gewohnt, mit yield ausgeführt, wenn denn auch ein Block übergeben wird (... if block_given?). Manche meinen, diese Form der Initialisierung wäre übersichtlicher, als diese:

 
class MyClass
  def initialize
    yield self if block_given?
  end
  attr_accessor :name, :color
end

m = MyClass.new 
m.name  = "Mein Objekt"
m.color = "#00FF00"

p m

Einen gewissen Charme hat erstere Version schon, zeigt es doch durch die Blockschreibweise, dass die Zuweisungen noch zur Erstellung des Objektes gehören. Ein funktionaler Zusammenhang wird besser deutlich.

Ich halt's ja gerne mit der Prämisse: "Führe keine neuen Konstrukte ein, wenn sie keinen bedeutenden Fortschritt bringen." Denn das verhindert wieder die einfache Lesbarkeit von Code, Anfänger wird es erstmal verwirren.

Dazu sind solche Dinger manchmal gedacht - denke ich. Mancher Code soll nur beeindrucken, wie die übermäßige Verwendung von Fremdwörtern, und dem Schreiber bescheinigen, dass er was kann. Es ist cool, den anderen ins nachdenken zu bringen. Ein Spiel.

Wie es sich hier konkret verhält, weiß ich noch nicht einzuschätzen. Mir ist diese Schreibweise nur eben übern Weg gelaufen und ich wollte es mal festhalten.

Grundsätzlich sind Codeblöcke ein starkes Design-Element in Ruby. Sie machen den Code an vielen Stellen elegant, übersichtlich und robust. Man sollte sich also nicht scheuen, sie auch in eigene Klassen/Methoden einzubauen.

02.04.2005 :: Codesuche im RAA und über koders.com

Permalink

Quellcode-Suchmaschinen sind schon eine geniale Hilfe. Ich wollte z.B. gerade mal schauen, wie andere das Modul optparse verwenden. Also eben mal auf http://www.koders.com nach suchen lassen. Und schon hatte ich jede Menge Quellcode, den ich inspizieren konnte. Sogar mit Syntax-Highlighting.

Neben Koders.com gibt es jetzt auch im RAA-Archiv eine experimentelle Suchfunktion.

Weblinks:

01.04.2005 :: Jetzt deutsches Rubywiki

Permalink

Seit wenigen Tagen gibt es nun das deutsche Rubywiki. Nachdem wir auf rubyforen.de in den letzten Monaten immer mal wieder darüber diskutiert haben, hat iGEL (Initiator von rubyforen.de) es jetzt eingerichtet. Erreichbar ist es unter http://www.rubywiki.de.

Ich finde, das ist eine große Sache für die Ruby Community. Ein Forum eignet sich gut, um über Dinge zu diskutieren. Das, was dabei rauskommt, ist es oft wert, zusammengefasst dokumentiert zu werden. Dafür eignet sich wiederum ein Wiki sehr gut.

Als Wiki-Software kommt übrigens Mediawiki zum Einsatz. Es scheint einen Trend zu geben, dass immer mehr diese Software einsetzen. Die Wikipedia hat sie zu einem Quasi-Standard für Wikis gemacht. Ich denke, es ist eine gute Wahl, ist die Software doch sehr komfortabel.

Eigentlich wollten wir ja ein Wiki einsetzen, was in Ruby geschrieben ist. Leider gibt es da noch nichts wirklich fertiges, was so einige Mindestanforderungen abdeckt. Instiki und Ruwiki machen beide einen guten Eindruck und es könnte viel daraus werden. Das, was den beiden noch fehlt, ist eine vernünftige Seiten-Änderungs-Historie, so dass man beliebige alte Stände wieder herstellen kann. Ohne solch ein Feature kann man kein öffentliches Wiki betreiben.

Die Webentwicklung fürs Internet bekommt dieses Jahr durch Ruby on Rails einen großen Aufschwung. Bleibt zu hoffen, dass uns viele gute Webwerkzeuge beschert werden.

Weblinks:


Copyright dieser Seite

Permalink

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-3 | RubyWeblog | Archiv 2005-1 >>