Как вы строите канал чтения-записи с помощью lua? - PullRequest
15 голосов
/ 07 августа 2009

Я хотел бы сделать эквивалент:

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

в lua - то есть у меня есть переменная, содержащая кучу текста, и я бы хотел запустить ее через фильтр (реализованный в python, как это происходит).

Есть подсказки?

Добавлено: очень хотелось бы сделать это без использования временного файла

Ответы [ 7 ]

4 голосов
/ 07 августа 2009

В стандартной библиотеке Lua нет ничего, что позволяло бы это.

Вот подробное исследование трудностей правильного выполнения двунаправленной связи и предлагаемое решение:

если возможно, перенаправить один конец потока (вход или выход) в файл. I.e.:

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

Вас может заинтересовать это расширение , которое добавляет функции к пространствам имен os и io, чтобы сделать возможной двунаправленную связь с подпроцессом.

3 голосов
/ 13 мая 2013

Я наткнулся на этот пост, пытаясь сделать то же самое, и так и не нашел хорошего решения, см. Код ниже, чтобы узнать, как я решил свои проблемы. Эта реализация позволяет пользователям получить доступ к stdin, stdout, stderr и получить код состояния возврата. Для простых вызовов канала вызывается простая оболочка.

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)
3 голосов
/ 08 августа 2009

Пока ваш Lua поддерживает io.popen, эта проблема проста. Решение точно такое, как вы обрисовали, за исключением того, что вместо $(...) вам нужна такая функция:

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

Вы можете позвонить

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

Я делаю такие вещи все время. Вот связанная полезная функция для формирования команд:

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
3 голосов
/ 07 августа 2009

Ага, возможно лучшее решение:

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 голосов
/ 07 июля 2016

Это просто, никаких расширений не требуется (протестировано с 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 

сохранить как inout.lua и сделать chmod +x /tmp/inout.lua

20:30 $ foo=$(echo "bla"|  /tmp/inout.lua)
20:30 $ echo $foo
bla
0 голосов
/ 12 декабря 2012

Вот как я решил проблему, для этого потребуется 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

Во время выполнения myProgram вы будете читать и писать с обычного ввода-вывода, а после этой части кода вам просто нужно будет писать / читать на IN и OUT, чтобы общаться с дочерней программой.

0 голосов
/ 07 августа 2009

Не очень хорошее решение, которое позволяет избежать временного файла ...

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

x="hello\nworld"

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