Historie von Inhalt.RubyWeblogArchiv-2006-1

Einfache Korrekturen ausblenden - Änderungen im Layout

Zeile 2 bearbeitet:
||Attach:rubinkreuz.jpg || %color=#fb203c%'''[+++Ruby Weblog+++]'''\\
geändert zu:
||Attach:rubinkreuz.jpg || %color=#fb203c%'''[+++Ruby Weblog Archiv 2006+++]'''\\
Zeilen 9-14 bearbeitet:
[[#A025]]
!! 06.01.2007 Fehler in popen3
[
--[[#A025|Permalink]]--]

Die Kommunikation zwischen externen Programmen und Ruby ist manchmal nicht so trivial. Neben den Backticks gibt es ja noch system(), popen() und popen3(). Ich habe gerade einige Tests mit popen3 gemacht. Ich wollte ein externes Programm aufrufen und mir stdout und stderr getrennt zurückgeben lassen. Das funktionierte auch schonmal recht gut:
 
geändert zu:

[[#A024]]
!! 04.12.2006 Benutzerverwaltung im Unix-System
[
--[[#A024|Permalink]]--]

Ich brauchte ein paar Werkzeuge, um Benutzeraccounts in einem Unix-System anzulegen, Passwörter skriptgesteuert zu definieren und abzufragen, ob ein Account existiert. Hier ist der grob getestete Programmcode dazu (linux_user.rb):
Zeilen 17-25 bearbeitet:
require( "open3" )

stream_error =
""
stream
_std = ""
cmd = "/bin/date"

Open3.popen3( cmd ) do |stdin, stdout, stderr|
  stream_std = stdout.readlines.join( "" )
  stream_error
   = stderr.readlines.join( "" )
geändert zu:
require 'expect'
require '
open3'
ENV[
"LANGUAGE"]="en"
LINUX
_USER_CMD_TIMEOUT = 5

class IO
  def expect_raise( rxp, timeout )
 
   if (r = expect( rxp, timeout )) == nil
     raise "command timeout"
  else
    r
  end
  end
Zeilen 32-49 bearbeitet:
puts "stream_error: "
puts stream_error
puts
puts "stream_std: "
puts stream_std
=]

Ein Problem gibt es allerdings. Der Exitstatus ist nicht korrekt, wie dieser Test zeigt:

 [=
require
( "open3" )

stream_error = ""
stream_std = ""
cmd = "/bin/false"
Open3.popen3( cmd ) do |stdin, stdout, stderr|
  stream_std = stdout.readlines.join( "" )
  stream_error  = stderr.readlines.join( "" )
geändert zu:
def passwd( user, pw )
  Open3::popen3( "/usr/bin/passwd #{user}" ) do  |stdin, stdout, stderr|

    r = stderr.expect_raise
( /(Enter.*password:)|(unknown user)/,
       LINUX_USER_CMD_TIMEOUT )
 
   if r[0] =~ /unknown/
      raise "user unknown"
    end
    stdin.print "#{pw}\n"

    stderr.expect_raise( /Retype.*password:/, LINUX_USER_CMD_TIMEOUT
)
    stdin.print "#{pw}\n"
   
    stderr.expect_raise( /updated successfully/, LINUX_USER_CMD_TIMEOUT )
  end
Zeilen 49-50 bearbeitet:
# Exitstatus zurückgeben
puts
$?
geändert zu:
def useradd( user, shell )
  r = `/usr/sbin/useradd -m -s #{shell} #{user} 2>&1`
  if
$? >> 8 != 0
    raise "#{r}"
  end
end

def user_exist?( user )
  File.foreach( "/etc/passwd" ) do |line|
    if line =~ /^#{user}/
      return true
    end
  end
  return false
end
Zeilen 66-72 bearbeitet:
Er ist immer 0. Leider ist das ein unschöner Bug, der bereits seit über 3 Jahren existiert. Leider ist dieser technisch bedingt, wie Matz schreibt: ''open3 uses the "double fork" technique, so that you cannot take the exit status of the command.''

Bei popen hingegen soll es korrekt funktionieren. Bleibt zu hoffen, dass man das technisch mal so implementiert, dass der Exitstatus erhalten bleibt. Andere Skriptsprachen können das ja auch
.

Nachtrag: Nun hab ich doch eine Lösung gefunden: Open4 kann es. Sie wurde dafür gebaut, um genau dieses Problem zu lösen. Leider gehört Open4 aber nicht zur Standardbibliothek. Mit "gem install open4" ist das Teil aber schnell nachinstalliert.

Beispiel:
geändert zu:
ENV["LANGUAGE"]="en" sorgt dafür, dass die Rückgabe aller Unix-Kommandos auch in Englisch erfolgt und nicht angepasst auf die eingestellte Sprache. Das ist wichtig, weil sonst passwd nicht korrekt funktioniert. Ich habe übrigens absichtlich auf das externe Kommando passwd aufgesetzt und ändere nicht selber die passwd/shadow-Datei, um den Code auf unterschiedlichsten Plattformen kompatibel zu halten.

LINUX_USER_CMD_TIMEOUT = 5 ist eine Festlegung in Sekunden, wie lange maximal auf eine Antwort von Kommandos gewartet wird
.

Die Klasse IO wurde um expect_raise erweitert. Es wirft eine Exception ab, falls nicht die erwartete Rückantwort kommt. Das macht expect ja nicht, sondern gibt dann lediglich nil zurück. Dann müsste ich jedesmal auf nil testen, was mich an Fehlerbehandlung aus alten C-Zeiten erinnert.

Open3 braucht es übrigens bei passwd, weil hier die Ausgabe über stderr erfolgt. Warum auch immer.

[[#A023]]
!! 01.12.2006 GemJack - Ruby Gem Online Dokumentation
[--[[#A023|Permalink]]--]

[[http
://www.gemjack.com |GemJack]] ist eine nette Homepage, auf der man alle Dokumentationen von Ruby-Gem-Paketen lesen kann. Das hilft vor allem, wenn man erstmal schnell eine Doku lesen will, ehe man sich ein Gem-Paket installiert.


[[#A022]]
!! 01.12.2006 Probleme mit open3/popen3
[--[[#A022|Permalink]]--]

Mit einem Ruby-Skript wollte ich eigentlich automatisiert neue Benutzer in einem Linux-System anlegen. Dazu verwendet man in der Regel adduser oder useradd. Leider ist adduser nur interaktiv benutzbar. Ich dachte mir jedoch, dass ich das mit popen3 und expect.rb gut hinbekomme. Das man popen3 braucht und popen nicht ausreicht, zeigte mir ein erster Test. Das Program adduser ruft nämlich passwd auf, welches seine Passwortabfrage auf stderr ausgibt und nicht auf stdout.

Nach stundenlangem rumprobieren hab ich die Sache jetzt erstmal aufgegeben. Es scheint ein Problem mit der Pufferung von Streams zu sein. Da adduser andere Programme aufruft, hat man irgendwie keinen sauberen direkten Kontakt zu deren stdin und stdout. Hier mal ein Testprogramm:

Zeilen 90-99 bearbeitet:
require( "rubygems" )
require(
"open4" )

stream_error
= ""
stream_std = ""
cmd = "/bin/false"

Open4.popen4( cmd
) do |pid, stdin, stdout, stderr|
  stream_std = stdout.readlines.join( "" )
  stream_error
  = stderr.readlines.join( "" )
geändert zu:
require "open3"

ENV[
"LANGUAGE"]="en"
CMD_TIMEOUT
= 5
user =
"ruby_test1"
pass = "Test"

`deluser #{user}`

Open3::popen3(
"/usr/sbin/adduser #{user}" ) do  |stdin, stdout, stderr|
  Thread.new {
 
  loop do
     print stderr.getc.chr
     $stdout.flush
    end
  }
  Thread.new {
    loop do
      print stdout.getc.chr
      $stdout.flush
    end
  }
  loop do
    a = gets.chomp
    stdin.print "#{a}\n"
  end 
Zeilen 116-124 gelöscht:

puts "stream_error: "
puts stream_error
puts
puts "stream_std: "
puts stream_std

print "Exitstatus: "
puts ($?.to_i >> 8).to_s
Zeilen 119-131 bearbeitet:
Weblinks:
* http://raa
.ruby-lang.org/project/open4/
* http://rubyforge
.org/tracker/index.php?func=detail&aid=1504&group_id=426&atid=1698
* http
://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/68780
* http://blade
.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/4467


[[#A024]]
!! 04.12.2006 Benutzerverwaltung im Unix-System
[--[[#A024|Permalink]]--]

Ich brauchte ein paar Werkzeuge, um Benutzeraccounts in einem Unix-System anzulegen, Passwörter skriptgesteuert zu definieren und abzufragen
, ob ein Account existiert. Hier ist der grob getestete Programmcode dazu (linux_user.rb):
geändert zu:
Damit sollt man adduser wie gewohnt ausführen können. Auf einem Ubuntu-Dapper klappt auch alles soweit. Wenn man die Fragen nach "Full Name, Room Number..." jeweils mit Enter übergeht, kommt man bis zum letzten Punkt "Other []:". Dann sollte eigentlich die Abfrage kommen "Is the information correct?". Die kommt aber nicht, dafür wartet stdin jedoch schon auf "y" oder "n" als Antwort. Wenn man die Antwort "y" gibt, erscheint nachträglich dann die Frage. Da ist also stdout irgendwo zwischengepuffert und erreicht einen zu spät.

Auf einem Debian Woody funktioniert es noch schlechter. Da kommen erst gar nicht die Fragen 5 Fragen, die kommen komplett nachträglich.

Folglich
: Interaktive Programme mit popen3 zum mitspielen anzuregen, funktioniert wohl nur, wenn die nicht weitere Programme aufrufen, die ihrerseits unabhängig arbeiten. Genau verstanden habe ich das Verhalten allerdings nicht. Adduser ist ein Perl-Skript, worin mit system(cmd) ein weiteres Kommando aufgerufen wird. Dessen stdout scheint irgendwie zeitversetzt weitergeleitet zu werden.

Auch mit Open4 ist das übrigens getestet, gleiche Problematik.

[[#A021]]
!! 01.11.2006 Vorsicht bei mitgelieferten Bibliotheken
[--[[#A021|Permalink]]--]

Auf einem Ubuntu Dapper System brauchte ich die http-access2 für ein bereits existierendes und funktionierendes Skript. Also eben mal mit apt-cache nachgeschaut, ob diese Bibliothek vom Ubuntu Paketmanagment her existiert. Die gab es auch. Also eben mit apt-get installiert. Mein Skript wollte aber einfach nicht laufen.

Nach 3 Stunden Fehlersuche hab ich dann endlich herausbekommen, dass eine völlig veraltete Bibliothek mitgeliefert wurde. Aus dem Jahre 2004. Weil die letzte Änderung an dieser Bibliothek 2005 war und Dapper erst 2006 rausgekommen ist, hätte ich nie damit gerechnet, dass die Bibliothek noch aus 2004 stammt.

Also Vorsicht: Am besten alle Ruby-Bibliotheken über rubygems installieren bzw. aus den Quellpaketen. Zumindest sollte man dann dran denken, sobald es Probleme gibt.

Die http-access2 scheint übrigens ziemlich Buggy zu sein. Zumindest läuft sie bei mir auf mehreren Rechnern nicht korrekt. Der Content der abgeholten Seite hat sehr oft die Länge 0, obwohl als Rückantwort 200 OK kam. Unter Ubuntu Dapper mit Ruby 1.8.4 funktioniert mitunter gar nichts richtig. Unter Windows und Ruby 1.8.2 bzw. Ruby 1.8.4 läuft alles problemlos.

[[#A020]]
!! 01.11.2006 Minimal-Konfiguration Apache 2 und Ruby-Skripte
[--[[#A020|Permalink]]--]

Was braucht man, um ein Ruby-Skript über den Apache ausführen zu lassen? Braucht man mod-ruby oder eruby? Nein, wenn man ein Ruby-Skript als ganz gewöhnliches cgi-skript laufen lassen möchte.

Zuerst einmal testet man, ob ein normales cgi-Bash Skript läuft. Das legt man z.B. unter /var/www/cgi-bin/test.cgi ab.

Zeilen 148-194 bearbeitet:
require 'expect'
require 'open3'
ENV["LANGUAGE"]="en"
LINUX_USER_CMD_TIMEOUT = 5

class IO
  def expect_raise( rxp, timeout )
    if (r = expect( rxp, timeout )) == nil
    raise "command timeout"
  else
    r
  end
  end
end

def passwd( user, pw )
  Open3::popen3( "/usr/bin/passwd #{user}" ) do  |stdin, stdout, stderr|

    r = stderr.expect_raise( /(Enter.*password:)|(unknown user)/,
        LINUX_USER_CMD_TIMEOUT )
    if r[0] =~ /unknown/
      raise "user unknown"
    end
    stdin.print "#{pw}\n"

    stderr.expect_raise( /Retype.*password:/, LINUX_USER_CMD_TIMEOUT )
    stdin.print "#{pw}\n"
   
    stderr.expect_raise( /updated successfully/, LINUX_USER_CMD_TIMEOUT )
  end
end

def useradd( user, shell )
  r = `/usr/sbin/useradd -m -s #{shell} #{user} 2>&1`
  if $? >> 8 != 0
    raise "#{r}"
  end
end

def user_exist?( user )
  File.foreach( "/etc/passwd" ) do |line|
    if line =~ /^#{user}/
      return true
    end
  end
  return false
end
geändert zu:
#!/bin/bash

echo "Status: 200 OK"
echo "Content-type: text/plain"
echo ""
echo ""
echo "Hello World"
Zeilen 157-179 bearbeitet:
ENV["LANGUAGE"]="en" sorgt dafür, dass die Rückgabe aller Unix-Kommandos auch in Englisch erfolgt und nicht angepasst auf die eingestellte Sprache. Das ist wichtig, weil sonst passwd nicht korrekt funktioniert. Ich habe übrigens absichtlich auf das externe Kommando passwd aufgesetzt und ändere nicht selber die passwd/shadow-Datei, um den Code auf unterschiedlichsten Plattformen kompatibel zu halten.

LINUX_USER_CMD_TIMEOUT = 5 ist eine Festlegung in Sekunden, wie lange maximal auf eine Antwort von Kommandos gewartet wird.

Die Klasse IO wurde um expect_raise erweitert. Es wirft eine Exception ab, falls nicht die erwartete Rückantwort kommt. Das macht expect ja nicht, sondern gibt dann lediglich nil zurück. Dann müsste ich jedesmal auf nil testen, was mich an Fehlerbehandlung aus alten C-Zeiten erinnert.

Open3 braucht es übrigens bei passwd, weil hier die Ausgabe über stderr erfolgt. Warum auch immer.

[[#A023]]
!! 01.12.2006 GemJack - Ruby Gem Online Dokumentation
[--[[#A023|Permalink]]--]

[[http://www.gemjack.com |GemJack]] ist eine nette Homepage, auf der man alle Dokumentationen von Ruby-Gem-Paketen lesen kann. Das hilft vor allem, wenn man erstmal schnell eine Doku lesen will, ehe man sich ein Gem-Paket installiert.


[[#A022]]
!! 01.12.2006 Probleme mit open3/popen3
[--[[#A022|Permalink]]--]

Mit einem Ruby-Skript wollte ich eigentlich automatisiert neue Benutzer in einem Linux-System anlegen. Dazu verwendet man in der Regel adduser oder useradd. Leider ist adduser nur interaktiv benutzbar. Ich dachte mir jedoch, dass ich das mit popen3 und expect.rb gut hinbekomme. Das man popen3 braucht und popen nicht ausreicht, zeigte mir ein erster Test. Das Program adduser ruft nämlich passwd auf, welches seine Passwortabfrage auf stderr ausgibt und nicht auf stdout.

Nach stundenlangem rumprobieren hab ich die Sache jetzt erstmal aufgegeben. Es scheint ein Problem mit der Pufferung von Streams zu sein. Da adduser andere Programme aufruft, hat man irgendwie keinen sauberen direkten Kontakt zu deren stdin und stdout. Hier mal ein Testprogramm:

geändert zu:
Wenn das nicht läuft, ist vermutlich der CGI-Handler nicht gesetzt. Den kann man unter Apache 2 entweder in der apache2.conf oder unter sites-available/<my-site> festlegen. In der apache2.conf sähe das so aus:
Zeilen 160-186 bearbeitet:
require "open3"

ENV["LANGUAGE"]="en"
CMD_TIMEOUT = 5
user = "ruby_test1"
pass = "Test"

`deluser #{user}`

Open3::popen3( "/usr/sbin/adduser #{user}" ) do  |stdin, stdout, stderr|
  Thread.new {
    loop do
      print stderr.getc.chr
      $stdout.flush
    end
  }
  Thread.new {
    loop do
      print stdout.getc.chr
      $stdout.flush
    end
  }
  loop do
    a = gets.chomp
    stdin.print "#{a}\n"
  end 
end
geändert zu:
AddHandler cgi-script .cgi
Zeilen 163-190 bearbeitet:
Damit sollt man adduser wie gewohnt ausführen können. Auf einem Ubuntu-Dapper klappt auch alles soweit. Wenn man die Fragen nach "Full Name, Room Number..." jeweils mit Enter übergeht, kommt man bis zum letzten Punkt "Other []:". Dann sollte eigentlich die Abfrage kommen "Is the information correct?". Die kommt aber nicht, dafür wartet stdin jedoch schon auf "y" oder "n" als Antwort. Wenn man die Antwort "y" gibt, erscheint nachträglich dann die Frage. Da ist also stdout irgendwo zwischengepuffert und erreicht einen zu spät.

Auf einem Debian Woody funktioniert es noch schlechter. Da kommen erst gar nicht die Fragen 5 Fragen, die kommen komplett nachträglich.

Folglich: Interaktive Programme mit popen3 zum mitspielen anzuregen, funktioniert wohl nur, wenn die nicht weitere Programme aufrufen, die ihrerseits unabhängig arbeiten. Genau verstanden habe ich das Verhalten allerdings nicht. Adduser ist ein Perl-Skript, worin mit system(cmd) ein weiteres Kommando aufgerufen wird. Dessen stdout scheint irgendwie zeitversetzt weitergeleitet zu werden.

Auch mit Open4 ist das übrigens getestet, gleiche Problematik.

[[#A021]]
!! 01.11.2006 Vorsicht bei mitgelieferten Bibliotheken
[--[[#A021|Permalink]]--]

Auf einem Ubuntu Dapper System brauchte ich die http-access2 für ein bereits existierendes und funktionierendes Skript. Also eben mal mit apt-cache nachgeschaut, ob diese Bibliothek vom Ubuntu Paketmanagment her existiert. Die gab es auch. Also eben mit apt-get installiert. Mein Skript wollte aber einfach nicht laufen.

Nach 3 Stunden Fehlersuche hab ich dann endlich herausbekommen, dass eine völlig veraltete Bibliothek mitgeliefert wurde. Aus dem Jahre 2004. Weil die letzte Änderung an dieser Bibliothek 2005 war und Dapper erst 2006 rausgekommen ist, hätte ich nie damit gerechnet, dass die Bibliothek noch aus 2004 stammt.

Also Vorsicht: Am besten alle Ruby-Bibliotheken über rubygems installieren bzw. aus den Quellpaketen. Zumindest sollte man dann dran denken, sobald es Probleme gibt.

Die http-access2 scheint übrigens ziemlich Buggy zu sein. Zumindest läuft sie bei mir auf mehreren Rechnern nicht korrekt. Der Content der abgeholten Seite hat sehr oft die Länge 0, obwohl als Rückantwort 200 OK kam. Unter Ubuntu Dapper mit Ruby 1.8.4 funktioniert mitunter gar nichts richtig. Unter Windows und Ruby 1.8.2 bzw. Ruby 1.8.4 läuft alles problemlos.

[[#A020]]
!! 01.11.2006 Minimal-Konfiguration Apache 2 und Ruby-Skripte
[--[[#A020|Permalink]]--]

Was braucht man, um ein Ruby-Skript über den Apache ausführen zu lassen? Braucht man mod-ruby oder eruby? Nein, wenn man ein Ruby-Skript als ganz gewöhnliches cgi-skript laufen lassen möchte.

Zuerst einmal testet man, ob ein normales cgi-Bash Skript läuft. Das legt man z.B. unter /var/www/cgi-bin/test.cgi ab.

geändert zu:
Auch muss für das entsprechende Verzeichnis "Options ExecCGI" gesetzt sein.

Bei der test.cgi müssen auch die Rechte richtig gesetzt sein, z.B. 755.

Wenn das alles funktioniert, kann man auch ein Ruby-Skript mit der Endung .cgi schreiben, was dann funktionieren sollte:

Zeilen 170-176 bearbeitet:
#!/bin/bash

echo "Status:
200 OK"
echo "
Content-type: text/plain"
echo ""
echo ""
echo "Hello World"
geändert zu:
#!/usr/bin/ruby1.8

out = <<EOF
Status:
200 OK
Content-type: text/plain


Hello World!
EOF

print out
Zeilen 183-184 bearbeitet:
Wenn das nicht läuft, ist vermutlich der CGI-Handler nicht gesetzt. Den kann man unter Apache 2 entweder in der apache2.conf oder unter sites-available/<my-site> festlegen. In der apache2.conf sähe das so aus:
geändert zu:

Besser ist es noch
, wenn man statt der Endung .cgi die Endung .rbx für Ruby Skripte definiert. Auch dies geht wieder in apache2.conf oder in der entsprechenden Virtual-Host-Datei.
Zeile 187 bearbeitet:
AddHandler cgi-script .cgi
geändert zu:
AddHandler cgi-script .rbx
Zeilen 189-215 gelöscht:
Auch muss für das entsprechende Verzeichnis "Options ExecCGI" gesetzt sein.

Bei der test.cgi müssen auch die Rechte richtig gesetzt sein, z.B. 755.

Wenn das alles funktioniert, kann man auch ein Ruby-Skript mit der Endung .cgi schreiben, was dann funktionieren sollte:

 [=
#!/usr/bin/ruby1.8

out = <<EOF
Status: 200 OK
Content-type: text/plain


Hello World!
EOF

print out
=]


Besser ist es noch, wenn man statt der Endung .cgi die Endung .rbx für Ruby Skripte definiert. Auch dies geht wieder in apache2.conf oder in der entsprechenden Virtual-Host-Datei.

 [=
AddHandler cgi-script .rbx
=]