3

I have the following function that works fine as long as I give it a valid command to execute. As soon as I give it a non-existent command, the script is interrupted with an error message.

#!/usr/bin/lua
function exec_com(com)
    local ok,res=pcall(function() return io.popen(com) end)
    if ok then
        local tmp=res:read('*a')
        res:close()
        return ok,tmp
    else
        return ok,res
    end
end
local st,val=exec_com('uptime')
print('Executed "uptime" with status:'..tostring(st)..' and value:'..val)
st,val=exec_com('zzzz')
print('Executed "zzzz" with status:'..tostring(st)..' and value:'..val)

When I run the script above I get the following output:

Executed "uptime" with status:true and value: 18:07:38 up 1 day, 23:00,  3 users,  load average: 0.37, 0.20, 0.20

sh: zzzz: command not found
Executed "zzzz" with status:true and value:

You can clearly see above that pcall() function still reported success when executing "zzzz" which is odd.

Can someone help me devise a way to catch an exception when executing a non-existent or ill-formed Linux command using Lua script? Thanks.

Edit: Restated my request after getting the clarification that pcall() works as expected, and the problem is due to popen() failing to throw an error.

1
  • 1
    You should rephrase and retitle your question since the the problem is that a Lua error is not being thrown, therefore there is nothing for pcall to catch. Your pcall code is correct. Commented Jun 14, 2014 at 23:56

4 Answers 4

3

I use a method which is similar to your "temporary workaround" but which gives you more information:

local cmd = "uptime"
local f = io.popen(cmd .. " 2>&1 || echo ::ERROR::", "r")
local text = f:read "*a"

if text:find "::ERROR::" then
  -- something went wrong
  print("error: " .. text)
else
  -- all is fine!!
  print(text)
end
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for sharing your solution. I have modified it slightly to clean up the error message and get rid of newline created by echo. print(text:match('^(.*)%s+::ERROR::'))
2

If you look at io.popen(), you'll see that it'll always return a file handle.

Starts program prog in a separated process and returns a file handle that you can use to read data from this program (if mode is "r", the default) or to write data to this program (if mode is "w").

Since, a file handle returned is still a valid value for lua, the pcall(), your local function inside the pcall is returning a true value (and an error is not being propagated); thereby, giving you a true status and no output.

1 Comment

Thank you for the clarification. I have edited my question a bit, to make it clear that I am still looking for a solution to determining if my linux command failed to execute.
2

I have come up with my own temporary workaround that pipes the error to /dev/null and determines the success/failure of executed command based on the text received from io.popen():read('*a') command.

Here is my new code:

#!/usr/bin/lua
function exec_com(com)
    local res=io.popen(com..' 2>/dev/null')
    local tmp=res:read('*a')
    res:close()
    if string.len(tmp)>0 then
        return true,tmp
    else
        return false,'Error executing command: '..com
    end
end
local st,val=exec_com('uptime')
print('Executed "uptime" with status:'..tostring(st)..' and value:'..val)
st,val=exec_com('cat /etc/shadow')
print('Executed "cat /etc/shadow" with status:'..tostring(st)..' and value:'..val)

And the corresponding output is now correct:

Executed "uptime" with status:true and value: 00:10:11 up 2 days,  5:02,  3 users,  load average: 0.01, 0.05, 0.19
Executed "cat /etc/shadow" with status:false and value:Error executing command: cat /etc/shadow

In my example above I am creating a "generic" error description. This is an intermediate fix and I am still interested in seeing alternative solutions that can return a more meaningful error message describing why the command failed to execute.

Comments

0

Rather than taking the time reading the whole file into a variable, why not just check if the file is empty with f:read(0)?

Local f = io.popen("NotExist")

if not f:read(0) Then
    for l in st:lines() do
        print(l)
    end
else
    error("Command Does Not Exist")
end

From the lua Manual: As a special case, io.read(0) works as a test for end of file: It returns an empty string if there is more to be read or nil otherwise.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.