2009-08-07 7 views
13

Ich möchte das Äquivalent tun:Wie konstruierst du eine Lese-Schreibröhre mit lua?

foo=$(echo "$foo"|someprogram) 

innerhalb lua - das heißt, ich habe eine Variable bekam eine Reihe von Text enthält, und ich möchte es durch einen Filter laufen (implementiert in Python wie es passiert).

Irgendwelche Hinweise?

Hinzugefügt: würde das wirklich gerne tun, ohne eine temporäre Datei dieses

Antwort

3

Aha, eine möglicherweise bessere Lösung:

require('posix') 
require('os') 
require('io') 

function splat_popen(data,cmd) 
    rd,wr = posix.pipe() 
    io.flush() 
    child = posix.fork() 
    if child == 0 then 
     rd:close() 
     wr:write(data) 
     io.flush() 
     os.exit(1) 
    end 
    wr:close() 

    rd2,wr2 = posix.pipe() 
    io.flush() 
    child2 = posix.fork() 
    if child2 == 0 then 
     rd2:close() 
     posix.dup(rd,io.stdin) 
     posix.dup(wr2,io.stdout) 
     posix.exec(cmd) 
     os.exit(2) 
    end 
    wr2:close() 
    rd:close() 

    y = rd2:read("*a") 
    rd2:close() 

    posix.wait(child2) 
    posix.wait(child) 

    return y 
end 

munged=splat_popen("hello, world","/usr/games/rot13") 
print("munged: "..munged.." !") 
+0

Warum bekomme ich einen Fehler über die 'require'? "nicht vorhandene globale Variable 'require'" –

4

Es gibt nichts in der Lua-Standardbibliothek zu ermöglichen, zu verwenden.

Here is an in-depth exploration of the difficulties of doing bidirectional communication properly, und eine vorgeschlagene Lösung:

wenn möglich, in eine Datei ein Ende des Stroms (Eingang oder Ausgang) umleiten. D.h .:

fp = io.popen("foo >/tmp/unique", "w") 
fp:write(anything) 
fp:close() 
fp = io.open("/tmp/unique") 
x = read("*a") 
fp:close() 

Sie können in this extension interessiert sein, die Funktionen zu den os und io Namensräume ergänzt die bidirektionale Kommunikation mit einem subprocess möglich zu machen.

+0

Gibt es irgendeine Art und Weise eine lua subprocess (fork() oder ähnliches) zu erstellen, um die Daten an den Filter passieren, wie die Shell tut? Das würde den Deadlock vermeiden ... –

-1

Eine nicht sehr schöne Lösung, die eine temporäre Datei vermeidet ...

require("io") 
require("posix") 

x="hello\nworld" 

posix.setenv("LUA_X",x) 
i=popen('echo "$LUA_X" | myfilter') 
x=i.read("*a") 
3

Solange Ihr Lua unterstützt io.popen, dieses Problem ist einfach . Die Lösung ist genau so, wie Sie skizziert haben, außer statt $(...) müssen Sie eine Funktion wie diese:

function os.capture(cmd, raw) 
    local f = assert(io.popen(cmd, 'r')) 
    local s = assert(f:read('*a')) 
    f:close() 
    if raw then return s end 
    s = string.gsub(s, '^%s+', '') 
    s = string.gsub(s, '%s+$', '') 
    s = string.gsub(s, '[\n\r]+', ' ') 
    return s 
end 

Sie dann

local foo = ... 
local cmd = ("echo $foo | someprogram"):gsub('$foo', foo) 
foo = os.capture(cmd) 

ich Sachen wie diese die ganze Zeit tun aufrufen können. Hier ist eine im Zusammenhang nützliche Funktion zur Bildung Befehl:

local quote_me = '[^%w%+%-%=%@%_%/]' -- complement (needn't quote) 
local strfind = string.find 

function os.quote(s) 
    if strfind(s, quote_me) or s == '' then 
    return "'" .. string.gsub(s, "'", [['"'"']]) .. "'" 
    else 
    return s 
    end 
end 
+0

Dies erfasst nicht stderr. Gibt es eine Möglichkeit, es auch zu erfassen? –

+0

@Vilas, wenn es Ihnen nichts ausmacht, stderr und stdout zu mischen, fügen Sie einfach '2> & 1' zur Befehlszeile hinzu. Oder schauen Sie sich das Debian-Skript 'annotieren-ausgeben' an. –

+0

Seien Sie sehr vorsichtig mit diesem Ansatz - Sie übergeben den gesamten Inhalt von 'foo' in der Befehlszeile, und Sie können nicht garantieren, wie groß die Befehlszeile ist. Ihre Daten werden auch öffentlich, wie es in der Prozessliste erscheint. –

0

Hier ist, wie ich das Problem gelöst, erfordert es lua posix.

  p = require 'posix' 
      local r,w = p.pipe() 
      local r1,w1 = p.pipe() 
      local cpid = p.fork() 
      if cpid == 0 then -- child reads from pipe          
      w:close() 
      r1:close() 
      p.dup(r, io.stdin) 
      p.dup(w1 ,io.stdout) 
      p.exec('./myProgram') 
      r:close() 
      w1:close() 
      p._exit(0) 
      else -- parent writes to pipe             
      IN = r1 
      OUT = w 
      end 

Während myProgram Ausführung, you'l Lesen und Schreiben von normalen io und nach diesem Teil des Codes müssen Sie nur noch schreiben/lesen auf IN und OUT mit Kind Programm comunicate.

2

Ich stolperte über diesen Beitrag, während ich versuchte, das Gleiche zu tun, und fand nie eine gute Lösung, siehe untenstehenden Code für, wie ich meine Probleme löste. Diese Implementierung ermöglicht es Benutzern, auf stdin, stdout, stderr zuzugreifen und den Rückkehrstatuscode abzurufen. Ein einfacher Wrapper wird für einfache Pipe-Aufrufe aufgerufen.

require("posix") 

-- 
-- Simple popen3() implementation 
-- 
function popen3(path, ...) 
    local r1, w1 = posix.pipe() 
    local r2, w2 = posix.pipe() 
    local r3, w3 = posix.pipe() 

    assert((w1 ~= nil or r2 ~= nil or r3 ~= nil), "pipe() failed") 

    local pid, err = posix.fork() 
    assert(pid ~= nil, "fork() failed") 
    if pid == 0 then 
     posix.close(w1) 
     posix.close(r2) 
     posix.dup2(r1, posix.fileno(io.stdin)) 
     posix.dup2(w2, posix.fileno(io.stdout)) 
     posix.dup2(w3, posix.fileno(io.stderr)) 
     posix.close(r1) 
     posix.close(w2) 
     posix.close(w3) 

     local ret, err = posix.execp(path, unpack({...})) 
     assert(ret ~= nil, "execp() failed") 

     posix._exit(1) 
     return 
    end 

    posix.close(r1) 
    posix.close(w2) 
    posix.close(w3) 

    return pid, w1, r2, r3 
end 

-- 
-- Pipe input into cmd + optional arguments and wait for completion 
-- and then return status code, stdout and stderr from cmd. 
-- 
function pipe_simple(input, cmd, ...) 
    -- 
    -- Launch child process 
    -- 
    local pid, w, r, e = popen3(cmd, unpack({...})) 
    assert(pid ~= nil, "filter() unable to popen3()") 

    -- 
    -- Write to popen3's stdin, important to close it as some (most?) proccess 
    -- block until the stdin pipe is closed 
    -- 
    posix.write(w, input) 
    posix.close(w) 

    local bufsize = 4096 
    -- 
    -- Read popen3's stdout via Posix file handle 
    -- 
    local stdout = {} 
    local i = 1 
    while true do 
     buf = posix.read(r, bufsize) 
     if buf == nil or #buf == 0 then break end 
     stdout[i] = buf 
     i = i + 1 
    end 

    -- 
    -- Read popen3's stderr via Posix file handle 
    -- 
    local stderr = {} 
    local i = 1 
    while true do 
     buf = posix.read(e, bufsize) 
     if buf == nil or #buf == 0 then break end 
     stderr[i] = buf 
     i = i + 1 
    end 

    -- 
    -- Clean-up child (no zombies) and get return status 
    -- 
    local wait_pid, wait_cause, wait_status = posix.wait(pid) 

    return wait_status, table.concat(stdout), table.concat(stderr) 
end 

-- 
-- Example usage 
-- 
local my_in = io.stdin:read("*all") 
--local my_cmd = "wc" 
--local my_args = {"-l"} 
local my_cmd = "spamc" 
local my_args = {} -- no arguments 
local my_status, my_out, my_err = pipe_simple(my_in, my_cmd, unpack(my_args)) 

-- Obviously not interleaved as they would have been if printed in realtime 
io.stdout:write(my_out) 
io.stderr:write(my_err) 

os.exit(my_status) 
+0

Das ist ganz nett, aber bist du dir sicher, dass das wie dargestellt funktioniert? Die Datei-Deskriptor-Nummern sind Off-by-One (sollte 0,1 und 2 sein) und die Tests für fork und execp Fehler sehen für mich umgekehrt. – jpc

+0

funktioniert meistens wie für mich geschrieben, musste 'local posix = require ("posix") hinzufügen. – robm

+0

Ich musste nach den Schleifen 'posix.close (r)' und 'posix.close (e)' hinzufügen, um sie in ps.pipe_simple() zu löschen. Sonst habe ich nach 500 Aufrufen unter Linux zu viele offene Dateien bekommen. – robm

0

Es ist einfach, keine Erweiterungen notwendig (getestet mit Lua 5.3).

#!/usr/bin/lua 
-- use always locals 
local stdin = io.stdin:lines() 
local stdout = io.write 

for line in stdin do 
    stdout (line) 
end 

speichern als inout.lua und tun chmod +x /tmp/inout.lua

20:30 $ foo=$(echo "bla"| /tmp/inout.lua) 
20:30 $ echo $foo 
bla