Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

interactive popen() Lua call

Tags:

lua

I'm trying to create a program that runs a shell on the background and sends user commands to it for execution and return the result. This is the code:

--note: this runs on windows but I assume replacing "cmd" with "sh" it can run on linux as well
exe,err=io.popen("cmd > stdout.txt 2> stderr.txt");
if not exe then
    print("Could not run command. Error: "..err)
    return
else
    print("Command run successfully... ready!")
end

stdout,err=io.open("stdout.txt","r")
if not stdout then print("Could not open stdout: "..err) return end
stderr,err=io.open("stderr.txt","r")
if not stdout then print("Could not open stderr: "..err) return end

function execute(str)
    exe:write(str)
    return stdout:read("*all") or stderr:read("*all") or "nil"
end

repeat
    print("COMMAND: ")
    userinput=io.read("*line")
    print("You entered: '"..userinput.."'")
    if userinput=="" then print "Empty line! Exiting program..." break end
    print("Result: "..execute(userinput))
until true

print "Closing..."
execute("exit")
print "1"
exe:close()
print "2"
stdout:close()
print "3"
stderr:close()
print "Finished!"

Problem: when exiting the program, it hangs on exe:close() call. The execution loop behaves weird too (sometimes I have to press enter several times for userinput=io.read("*line") to work.

I googled to see if file:close() also works on the file handle that is the result of io.popen() but didn't find anything. But that call doesn't fail. It just hangs up. In other words the output of the program is like this:

Command run successfully... ready!
COMMAND: 
dir

dir
You entered: 'dirdir'
Result: Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\lua>
C:\lua>
C:\lua>
Closing...
1
like image 508
AlexStack Avatar asked Jan 03 '12 17:01

AlexStack


1 Answers

Lua only depends on ANSI C features. Therefore io.popen uses the popen(3) function. Quoting from it's manual:

Since a pipe is by definition unidirectional, the type argument may specify only reading or writing, not both; the resulting stream is correspondingly read-only or write-only.

You attempt to solve this limitation by redirecting the output to a file and simultaneously open that file and read from it after executing a command. However, in this case you may run into problems with output buffering - I think this is what you are experiencing.

Instead of trying to work around io.popen, you may try out the Lua Ex API (wiki page here), which provides an alternative process spawning API and allows you to do things like this:

-- popen2(), from http://lua-users.org/wiki/ExtensionProposal
function popen2(...)
  local in_rd, in_wr = io.pipe()
  local out_rd, out_wr = io.pipe()
  local proc, err = os.spawn{stdin = in_rd, stdout = out_wr, ...}
  in_rd:close(); out_wr:close()
  if not proc then
    in_wr:close(); out_rd:close()
    return proc, err
  end
  return proc, out_rd, in_wr
end
-- usage:
local p, i, o = assert(popen2("wc", "-w"))
o:write("Hello world"); o:close()
print(i:read"*l"); i:close()
p:wait()
like image 57
Michal Kottman Avatar answered Oct 22 '22 18:10

Michal Kottman