Uli Schlachter | 4 Apr 2012 23:17
Picon
Gravatar

Spawning processes, handling their output non-blocking and separated by stdout/stderr

Hi list,

people occasionally complain about awesome awesome.spawn() (and the wrappers
awful.util.spawn() and spawn_with_shell()). They want more control over the
argument array that is passed to the new process. They want to handle stdout and
stderr individually. They want all of this to be non-blocking, so that their WM
doesn't freeze. They want the child's exist code.

And the coder heard their complaints. But he didn't want to write any code. So
he asked the almighty Google. Anyway, attached is a sample program which does
all of the above. This needs lua-ev[0] and luaposix[1]. lua-ev handles the
non-blocking part (and accidentally integrates with awesome's main loop) while
luaposix let's us set up pipes, fork and execute a process.

Actually, I lied. To make lua-ev work with awesome, it needs a simple patch[2].
However, I hope that gets resolved eventually.

So now that I mentioned this, let's see what cool stuff you come up with.
Actually, I write this mail mostly as a reminder to myself, but whatever.

Cheers,
Uli

P.S.: Yes, POSIX is not trivial, nor is libev. So what? :-P

[0] https://github.com/brimworks/lua-ev
[1] https://github.com/rrthomas/luaposix
[2]
https://github.com/psychon/lua-ev/commit/7580fb759b8664f6598199eed4ac0c9d70c4352c
-- 
"For saving the Earth.. and eating cheesecake!"
local posix = require("posix")
local ev = require("ev")

-- Fork of a child process that executes something and read its stdout/stderr
local read_out, write_out = posix.pipe()
local read_err, write_err = posix.pipe()
local pid = posix.fork()
if pid == 0 then
	local stdin = posix.open("/dev/null", { "RDONLY" })
	posix.dup2(stdin, 0)
	posix.dup2(write_out, 1)
	posix.dup2(write_err, 2)
	for _, fd in pairs({ stdin, write_out, write_err, read_out, read_err }) do
		posix.close(fd)
	end

	-- Let's get out of our parent's session
	posix.setpid('s')

	local err = posix.execp("/bin/echo", "hello", "world")
	posix.write(2, err)
	os.exit(-1)
end
posix.close(write_out)
posix.close(write_err)
print("Parent", pid)

-- Now handle input from the child
local function handle_io(fd, callback)
	return ev.IO.new(function(loop, io, revents)
		local str = posix.read(fd, 1024)
		if str ~= "" then
			callback(str)
		else
			io:stop(loop)
			posix.close(fd)
		end
	end, fd, ev.READ)
end
handle_io(read_out, function(str)
	print("stdout: " .. str)
end):start(ev.Loop.default)
handle_io(read_err, function(str)
	print("stderr: " .. str)
end):start(ev.Loop.default)
-- Wait for child to die
ev.Child.new(function(loop, child, revents)
	-- Note: we don't necessarily have all of the child's output read yet
	local status = child:getstatus()
	print("Child " .. child:getrpid() .. " died with status "
		.. status.exit_status .. "!")
	child:stop(loop)
end, pid, false):start(ev.Loop.default)

ev.Loop.default:loop()

Gmane