tags:

views:

848

answers:

5

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 I'd like to run it through a filter (implemented in python as it happens).

Any hints?

Added: would really like to do this without using a temporary file

A: 

How about use io.popen()?

Karl Voigtland
FYI, `io.popen()` is much less portable than the rest of Lua. But if it works on the target platform, it is the answer.
RBerteig
+2  A: 

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.

Miles
Is there some way to create a lua subprocess (fork() or similar) to pass the data to the filter, like the shell does? That would avoid the deadlock...
Anthony Towns
A: 

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")
Anthony Towns
+2  A: 

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.." !")
Anthony Towns
A: 

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
Norman Ramsey