funkcjonalność proc_open w Ruby

Cześć !

Szukałem po sieci, ale ciężko jest szukać jak się się czuka nie za bardzo wiadomo czego, jakiejś metody podobnej do proc_open z PHP. Chodzi o to aby móc pisać do procesu na stdin, stdout, stderr (i odbierać) + móc otworzyć strumienie 3 i 4. Czy ktoś ma jakiś pomysł ?

Nie wiem co dokładnie chcesz uzyskać ale w Rubim masz dostęp do zmiennych globalnych $stderr, $stdin, $stdout

Spróbuj w irb:

$stderr
==> #IO:0xb7c7ef54

$stderr.class
==> IO

Te strumienie to obiekty klasy IO, napisz co konkretnie chcesz zrobić

Chyba chodzi o odpalanie procesów i pisanie do nich/czytanie z ich outputu.

Do tego nada się IO.popen:
irb(main):014:0> p=IO.popen(“ls -l”)
=> #IO:0x82689d4
irb(main):015:0> while (not p.eof)
irb(main):016:1> puts p.gets
irb(main):017:1> end
total 34
-rw-r–r-- 1 ffm ffm 8001 Nov 7 11:35 README
-rw-r–r-- 1 ffm ffm 1410 Nov 7 11:35 Rakefile
drwxr-xr-x 7 ffm ffm 512 Nov 10 01:23 app
drwxr-xr-x 3 ffm ffm 512 Nov 10 01:23 components
drwxr-xr-x 4 ffm ffm 512 Nov 10 01:23 config
drwxr-xr-x 4 ffm ffm 512 Nov 10 01:23 db
drwxr-xr-x 3 ffm ffm 512 Nov 10 01:23 doc
drwxr-xr-x 5 ffm ffm 512 Nov 16 15:37 lib
drwxr-xr-x 3 ffm ffm 512 Nov 10 01:23 log
drwxr-xr-x 6 ffm ffm 512 Nov 10 01:23 public
drwxr-xr-x 5 ffm ffm 512 Nov 10 01:23 script
drwxr-xr-x 8 ffm ffm 512 Nov 10 01:23 test
drwxr-xr-x 7 ffm ffm 512 Nov 10 01:23 tmp
drwxr-xr-x 5 ffm ffm 512 Nov 10 01:23 vendor

Do tak otwartego IO masz dopięte STDIN i STDOUT wywołanej komendy.

@NetManiac:
Chodzi o coś więcej. Chodzi o odpalenie procesu, pisanie do niego na stdin i dwa dodatkowo otwarte dedykowane struminie (3,4) oraz czytanie jego stdin i stderr.

W tym się czai problem. Bo wiadomo stdin, stdout, stderr ( to kolejno strumienie o numerach: 0,1,2) da się otworzyć łatwo. A jak otworzyć strumienie 3,4 (po prostu takie dla siebie ?). Chciałem przemigrować aplikację z CodeIgnieter -> Rails. I to jest chyba właśnie punkt, w którym staje się to niemożliwe.

Gdyby problem tego rodzaju nie był rozwiązany w Ruby, to myślę, że przyszłość tego języka byłaby marna… :wink:

Te strumienie (takie dla siebie, jak sam to określasz) to zdaje się pipes? W klasie IO jest metoda statyczna pipe, która tworzy właśnie strumień tego rodzaju. Przy jej wywołaniu tworzone są dwa “końce”, które służą do przesyłania danych. Przykład użycia znajduje się oczywiście w dokumentacji języka.

Na przyszłość polecam www.gotapi.com + zakładka Ruby/Rails + sensowne słowa kluczowe w języku angielskim. Ewentualnie noobkit.com

Dzięki - jest jeszcze jakieś IO::new - wielkie dzięki !

No dobra - utknąłem :confused: i to już na samym początku. Otóż:

[code=ruby] IO.popen(“ls” “w+”) { |io|

}[/code]

Na razie tyle napisałem i czegoś już tu nie rozumiem. Po pierwsze:

io.readlines()

Na początku myślałem, że to $stderr, $stdout i $stdin jest uchwytem do procesu, ale nie jest uchwytem do wyjścia/wejścia mojego programu ok…

I teraz jak uzyskać uchwyty do tego co odpaliłem przez popen ? io.new nie działa, a metoda pipe nie ma parametru, który pozwoliłby określić numer strumienia. Co zrobić ?

Dla uściślenia jeszcze raz nakreślę problem. Odpalam sobie jakiś program i chcę do niego pisać/czytać poprzez strumienie 0,1,2,3,4. Kod, który uważam powinien działać (ale nie działa):

[code=ruby] @process = IO.popen(“command”, “w+”)

@stdin  = @process.new(0,"w")
@stdout = @process.new(1,"r")
@stderr = @process.new(2,"r")
@mystd1 = @process.new(3,"w")
@mystd2 = @process.new(4,"w")[/code]

Fragment wyciąłem z jednej z metod klasy, którą tworzę.

Sprawa jest istotnie nieco bardziej skomplikowana niż myślałem.

Dokładnego rozwiązania nie potrafię teraz przedstawić, mogę jedynie naszkicować.

  1. W programie napisanym w Ruby otwieramy odpowiednie strumien np. IO.new(4,“w”)
  2. Robimy forka, w ktorym otwieramy strumien o tych samych deskryptorach w odwrotnym kierunku np. IO.new(4,“r”). (Najlepiej jest przetestować wtedy komunikację pomiędzy procesami, zanim przejdzie się do następnego punktu).
  3. W podprocesie wywołujemy exec(“command”). Wtedy proces ten dziedziczy otwarte strumienie podprocesu.

Być może w powyższym rozwiązaniu IO.new należy zastąpić IO.pipe.
Aby sprawdzić jaki jest deskryptor otrzymanych plików można użyć metody IO#to_i.

Tutaj jest:

http://groups.google.pl/group/comp.lang.ruby/browse_thread/thread/a894667f272aefc/f0bd59448e97e5e4?hl=pl&lnk=gst&q=popen#f0bd59448e97e5e4

przykładowe rozwiązanie - myślę, że dam sobie radę. Szkoda tylko, że w ruby to jest takie trudne :slight_smile: ale spoko. Dzięki ! W razie czego będę nadal zgłaszał wątpliwości w tym wątku !

Istotnie nie ma dokładnie takiego rozwiązania jak w php.
Osobiście nie jestem pasjonatem programowania systemowego, więc jakoś specjalnie nie interesowałem się tym zagadnieniem. Pamiętam jednak ze studiów, że właśnie w ten sposób (tzn. otwarcie pipów i odpalenie komendy w podprocesie) robiło się to w C, więc przypuszczam, że implementatorzy Rubiego poszli właśnie tą drogą.
Z dyskusji, która przywołałeś wygląda na to, że w Ruby 1.9 jest to łatwiejsze.

Tak wiem :slight_smile: Mam nadal problem:

[code=ruby]pid = fork{
# child
fork{
# grandchild
pw[1].close
STDIN.reopen(pw[0])
pw[0].close

  pr[0].close
  STDOUT.reopen(pr[1])
  pr[1].close

  pe[0].close
  STDERR.reopen(pe[1])
  pe[1].close

  exec(*cmd)
}
exit!

}[/code]
ok to jest standardowa obsługa 3 strumieni - a jak podpiąć kolejne ? 3,4 ?

(zaczynam dochodzić do wniosku, że zrobię to w PHP :/)

@edit:
Oto kod klasy:
Dałem sobie radę z poprzednim problemem. Programik po prostu nie czytał z stdin-a i nic na niego nie należało pisać. Teraz największy problem jest z tym, żeby wysłać coś na strumienie 3,4. Obecny kod:

[code=ruby]class Broker_process

READ = 0 # Consts to explicite mark read
WRITE = 1 # and write pipes

STDLOG = IO.new(3, “w”)
STDPAS = IO.new(4, “w”)

@stdin # Stdin stream
@stdout # Stdout stream
@stderr # Stderr stream
@stdlog # Login stream
@stdpas # Password stream

@login # Login administrator
@password # Password administrator

@pid # Process handler

Set credentials for user

def self.set_credentials(login, password)
@login = login
@password = password
end

Log into broker by set credentials

def self.send_credentials()
@stdlog[WRITE].write @login
@stdlog[WRITE].close
@stdpas[WRITE].write @password
@stdpas[WRITE].close
end

Make pipes to communicate with child.

Each pipe is array of two: read and write pipe.

def self.make_pipes()
@stdin = IO.pipe()
@stdout = IO.pipe()
@stderr = IO.pipe()
@stdlog = IO.pipe()
@stdpas = IO.pipe()
end

Close unused pipes from parent site

def self.close_unused_pipes
@stdin[READ].close
@stdout[WRITE].close
@stderr[WRITE].close
@stdlog[READ].close
@stdpas[READ].close
end

Prepare and run child process

def self.run_child(command)
@stdin[WRITE].close
STDIN.reopen(@stdin[READ])
@stdin[READ].close

@stdout[READ].close
STDOUT.reopen(@stdout[WRITE])
@stdout[WRITE].close

@stderr[READ].close
STDERR.reopen(@stderr[WRITE])
@stderr[WRITE].close

@stdlog[WRITE].close
STDLOG.reopen(@stlog[READ])
@stdlog[READ].close

@stdpas[WRITE].close
STDPAS.reopen(@stdpas[READ])
@stdpas[READ].close
  
exec(*command) # Replace by command binary code

exit!

end

Run process

def self.run(command, params)
self.make_pipes()
if fork
self.close_unused_pipes()
self.send_credentials()
@stdin[WRITE].write params
@stdin[WRITE].close
Process.wait
out = @stdout[READ].read
@stdout[READ].close
err = @stderr[READ].read
@stderr[READ].close
puts out
else
self.run_child(command)
end
end

end[/code]
Wywala się w tym miejscu:

STDLOG = IO.new(3, "w") STDPAS = IO.new(4, "w")

[quote]./broker_process.rb:6:in initialize': Bad file descriptor (Errno::EBADF) from ./broker_process.rb:6:innew’
from ./broker_process.rb:6
from /home/johny/NetBeansProjects/zhradmin/lib/main.rb:4:in `require’
from /home/johny/NetBeansProjects/zhradmin/lib/main.rb:4[/quote]
Być może Ruby nie przewiduje otwierania kolejnych strumieni :confused: Jeżeli tylko stworzę pipy programik testowy napisany w C też tego nie łyka. A oto kod programiku:

[code=C]#include <stdio.h>
#include <unistd.h>

int main() {
char user[101];
char pass[101];
int i;
read(3, user, 101);
read(4, pass, 101);
printf(“Login: %s\nHaslo: %s\n”, user, pass);
return 0;
}[/code]
Proszę bardzo o pomoc !

Nie wiem, czy moje rozwiązanie całkowicie Cię zadowoli, ale być może wskaże drogę.

[code=ruby]#parent.rb
#!/usr/bin/ruby

rd, wr = IO.pipe
rd1, wr1 = IO.pipe

puts rd.to_i.to_s
puts wr.to_i.to_s
puts rd1.to_i.to_s
puts wr1.to_i.to_s

if fork
rd.close
rd1.close

puts “parent”
wr.write(“cm0”)
wr.close
wr1.write(“cm1”)
wr1.close
Process.wait
else
wr.close
wr1.close
exec("./child.rb")
exit!
end[/code]

[code=ruby]#child.rb
#!/usr/bin/ruby

rd = IO.open(3,“r”)
puts "parent sent to child on 0 #{rd.read} "
rd1 = IO.open(5,“r”)
puts "parent sent to child on 1 #{rd1.read} "[/code]
Pipy dla określonych deskryptorów można otwierać tylko po wcześniejszych ich utworzeniu.
Dlatego w procesie potomnym (child.rb) IO.open(3,“r”) nie powoduje błędu.
Problem polega na tym, że nie wiem, jak zrobić, żeby były to pipy o deskryptorach 3 i 4, a nie 3 i 5. Jeśli możesz zmodyfikować program wywoływany jako polecenie command lub określić dla niego z jakich pipów ma korzystać, to nie powinno to jednak stanowić problemu.

Postaram się coś tam zmontować - ale chyba poproszę programistę o zaimplementowanie działania logowanie przez stdin. Mało to bezpieczne - ale zawsze…

Poza tym sprawdzałeś czy to obsłuży ten programik w C na 3,5 ? Mi si wydaje, że jak podmienisz child-a na binarkę w C to nadal nic się nie zyska.

Tak czy siak ! Dzięki za pomoc. Myślę, że ponieważ Ruby nie jest językiem niskopoziomowym (w takim sensie jak w prównaniu do Ruby jest C) najnormalniej :stuck_out_tongue: nie przewidzili tego… Opakowali tylko w klasę standard - nie zaś dali funkcjonalność :confused: trudno !

Pozdrawiam !

Co do tego jestem pewien, bo wynika to z implementacji exec: proces potomny dziedziczy otwarte deskryptory plików. Tutaj nie ma znaczenia, czy jest on napisany w rubim, w C czy w jakimś innym języku - to jest zapewnione na poziomie systemu operacyjnego. Co prawda nie sprawdzałem tego, więc 100% pewności mieć nie mogę, ale tak powinno to działać.

W każdym razie - pozdrawiam również :slight_smile:

Ok więc - poddałem się i używam Open3.popen3 - pytanko. Jak dostać “kod” zwracany przez aplikację np 5, albo 10 ?, albo 0 w przypadku powodzenia ?

Już się dowiedziałem, że się nie da, że trzeba użyć Open4 :confused: