How do you construct a read-write pipe with lua?

后端 未结 7 1048
甜味超标
甜味超标 2020-12-09 04:26

I\'d like to do the equivalent of:

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

within lua -- ie, I\'ve got a variable containing a bunch of text, and

相关标签:
7条回答
  • 2020-12-09 04:53

    There is nothing in the Lua standard library to allow this.

    Here is an in-depth exploration of the difficulties of doing bidirectional communication properly, and a proposed solution:

    if possible, redirect one end of the stream (input or output) to a file. I.e.:

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

    You may be interested in this extension which adds functions to the os and io namespaces to make bidirectional communication with a subprocess possible.

    0 讨论(0)
  • 2020-12-09 05:00

    A not very nice solution that avoids a temporary file...

    require("io")
    require("posix")
    
    x="hello\nworld"
    
    posix.setenv("LUA_X",x)
    i=popen('echo "$LUA_X" | myfilter')
    x=i.read("*a")
    
    0 讨论(0)
  • 2020-12-09 05:06

    It's easy, no extensions necessary (tested with 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 
    

    save as inout.lua and do chmod +x /tmp/inout.lua

    20:30 $ foo=$(echo "bla"|  /tmp/inout.lua)
    20:30 $ echo $foo
    bla
    
    0 讨论(0)
  • 2020-12-09 05:07

    As long as your Lua supports io.popen, this problem is easy. The solution is exactly as you have outlined, except instead of $(...) you need a function like this one:

    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
    

    You can then call

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

    I do stuff like this all the time. Here's a related useful function for forming commands:

    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 讨论(0)
  • 2020-12-09 05:12

    Aha, a possibly better solution:

    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 讨论(0)
  • 2020-12-09 05:15

    I stumbled on this post while trying to do the same thing and never found a good solution, see the code below for how I solved my issues. This implementation allows users to access stdin, stdout, stderr and get the return status code. A simple wrapper is called for simple pipe calls.

    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 讨论(0)
提交回复
热议问题