Troubleshooters.Com and Code Corner Present

Litt's Lua Laboratory:
Lua Operating System Interface
(With Snippets)

Copyright (C) 2011 by Steve Litt



Debug like a Ninja

Contents

Introduction

In order to be as portable as possible, Lua has greatly scaled down the language's standard OS linkage compared to other languages. If you load the posix module you'll get everything you get from Linux, which is a lot. So Linux, Unix, BSD and Mac users are taken care of. I don't know whether there's a Windows module similar to the posix module.

Command Line Arguments

Command line arguments are trivial -- you just use ipairs() on the built in table called arg, as shown following:
#!/usr/bin/lua 

sf =string.format
for k, v in ipairs(arg) do
print(sf("k=>%s<, v=>%s<", k, v))
end
The preceding code produces the following output:
slitt@mydesk:~$ ./test.lua one two three four
k=>1<, v=>one<
k=>2<, v=>two<
k=>3<, v=>three<
k=>4<, v=>four<
slitt@mydesk:~$
There's no getopt() or getopt_long(), but you can either incorporate the posix module or just roll your own getopt() or getopt_long() substitute.

Environment Variables

All your environment variables are available through a call to os.env(), where the argument you pass is the name of the environment variable. Works like a charm.
#!/usr/bin/lua 

local user = os.getenv("USER")
print(user)
The preceding code produces the following output:
slitt@mydesk:~$ ./test.lua
slitt
slitt@mydesk:~$
The one improvement I'd like to see is a table containing all the environent variables so I can iterate through them. If you need that capability, you need to use your operating system's env command or else use Lua's posix module.

Temporary Files

Lua's standard libraries provide io.tempfile() and os.tempname(). Not a fan.

Neither provides you with a good way to influence the filename or the directory containing the file. io.tempfile() won't even give you its filename. I'm not sure what good a temp file does you if you don't know its name for later reading.

If you absolutely insist on using os.tempname(), here's how you do it:
#!/usr/bin/lua

local fname = os.tmpname()
local handle = io.open(fname, "w")
print(fname)
handle:write("Hello World!\n")
io.close(handle)


Don't EVEN try to give os.tmpname() a template type argument that you'd give to mktemp() or mkstemp() in C -- it ignores it, naming the temp file /tmp/lua_XXXXXX where XXXXXX is a string making it a unique file.

There are three alternatives:
  1. The Posix module
  2. Roll your own
  3. Make a C module passing a template to C's mkstemp() and passing back the filename and file.
Here's my "roll your own" program showing how to roll your own open_temp_file() function, where you can pass it a template containing "@@@", and the "@@@" is replaced by a 8 digit number from 10000000 through 99999999.
#!/usr/bin/lua 

function open_temp_file(template)
local handle
local fname
assert(string.match(template, "@@@"),
"ERROR open_temp_file: template must contain \"%%%\".")
while true do
fname = string.gsub(template, "@@@", tostring(math.random(10000000,99999999)))
handle = io.open(fname, "r")
if not handle then
handle = io.open(fname, "w")
break
end
io.close(handle)
io.write(".") -- Shows collision, comment out except for diagnostics
end
return handle, fname
end

local handle, fname = open_temp_file("/tmp/xample@@@.jnk")
handle:write(fname .."<->")
handle:write(tostring(os.time()).."\n")
io.close(handle)
Yeah, I know, I know, this has a race condition between the time you check for the file's existance and the time you create it. This is a very short time, but yeah, it's a possible race condition or security problem. When using the code on an extremely hard hit server exposed to the public, you're probably best off with the Posix module or making a C module. C module construction is discussed at http://www.troubleshooters.com/codecorn/lua/lua_lua_calls_c.htm.

File and Directory Operations

My research tells me that the file and directory operations provided by Lua's standard libraries are sparse indeed:
If you want to do things like get file attributes, retrieve listings of files in a directory, or recurse directories you'll need to go beyond the standard libraries:
The rest of this article focuses on the Lua File System library, lfs for short. Hopefully your Linux distribution has lfs in its package manager. My Ubuntu does. I was unable to install it from LuaRocks, probably because I'm not familiar enough with LuaRocks. In any case, assuming you've installed lfs, do this hello world:
#!/usr/bin/lua 

require "lfs"
sf = string.format

--### FOR THE NEXT TWO LINES,
--### USE ANY EXISTING DIR AND FILE
--### FOR WHICH YOU HAVE READ RIGHTS
--### AND EXEC RIGHTS ON THE DIRECTORY
existingfile = "junk.jnk" -- THIS FILE MUST ALREADY EXIST!!!
existingdir = "junk" -- THIS DIRECTORY MUST ALREADY EXIST!!!

local attribs = lfs.attributes(existingfile)
for k, v in pairs(attribs) do
print(sf("%s=>%s",k,v))
end
print("======================")
attribs = lfs.attributes(existingdir)
for k, v in pairs(attribs) do
print(sf("%s=>%s",k,v))
end
The preceding code produces the following output:
slitt@mydesk:~$ ./test.lua
dev=>2072
change=>1295302296
access=>1295821604
rdev=>0
nlink=>1
blksize=>4096
uid=>503
blocks=>8
gid=>503
ino=>2827232
mode=>file
modification=>1295302296
size=>1938
======================
dev=>2072
change=>1295591507
access=>1295591511
rdev=>0
nlink=>23
blksize=>4096
uid=>503
blocks=>8
gid=>503
ino=>2842637
mode=>directory
modification=>1295591507
size=>4096
slitt@mydesk:~$
I leave it as an exercise to recurse directories. Suffice it to say that lfs.attributes().mode indicates whether it's a directory or not.

Date and Time

No matter what you do, standard, plain vanilla Lua gives you time only to the nearest second. If you want finer control, you'll need to use the posix module.

Like most other languages, Lua's os.time() function gives you the number of seconds since epoch.

If you want a more human recognizeable time you need to use the os.date() function, which converts a seconds-from-epoch time representation to those commonly needed. The following program explores all the values you can get from os.date() when its first argument is "*t":
#!/usr/bin/lua 

sf = string.format
local today = os.time()
local datetable = os.date("*t", today)

print()
for k, v in pairs(datetable) do
print(sf("%s=%s", k, tostring(v)))
end

wdays = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
print(sf("\n%d seconds since epoch, %s, %d/%d/%d at %02d/%02d/%02d.",
today,
wdays[datetable.wday],
datetable.month,
datetable.day,
datetable.year,
datetable.hour,
datetable.min,
datetable.sec
))
The preceding code first iterated through all the elements of the returned table, and then prints a detail of the time. The iteration is an important feature for debugging -- if you do something wrong in printing the date you simply comment out the date and then investigate the information that's really being returned and fix your program accordingly. The preceding code prints out the following:
slitt@mydesk:~$ ./test.lua

hour=16
min=22
wday=1
day=23
month=1
year=2011
sec=37
yday=23
isdst=false

1295817757 seconds since epoch, Sunday, 1/23/2011 at 16/22/37.
slitt@mydesk:~$
Instead of passing "*t" to os.date(), you can also pass a format string to have it return whatever kind of formatted date you'd like. Here's an example:
#!/usr/bin/lua 

sf = string.format
local today = os.time()
io.write(tostring(today))
print(os.date(" seconds since epoch, %A, %x at %X"))
The preceding code, much shorter than the one with format "*t", printed basically the same thing without the table iteration:
slitt@mydesk:~$ ./test.lua
1295818399 seconds since epoch, Sunday, 01/23/11 at 16:33:19
slitt@mydesk:~$
Personally I don't like the two digit year for %x -- we paid dearly for that kind of thinking eleven short years ago. But I'll be safely in my grave before it's an issue again, and it's just a print representation -- obviously Lua keeps a 4 digit representation of years, good until 9999. :-)

Anyway, here are the most common replacement tokens for the format string in os.date():
There are many other replacement tokens. Most are similar to their C counterparts. The various replacement tokens are detailed in the "Programming in Lua" book in the Standard_libraries=>OS_library=>date_and_time section, which as of 1/23/2011 is located at http://www.lua.org/pil/22.1.html, but of course that URL may change with time.

Date Arithmetic

You can calculate time intervals by subtracting their time since epoch. You can go back a few days like this:
interval = os.time() - fewdays * 24*60*60
What my research hasn't uncovered is more sophisticated date arithmetic like "the first of the month" or "six months from now" or "two years ago". My research hasn't uncovered anything dealing with leap years or the differing number of days in the month, or the picket fence errors that crop up. Those features wouldn't be difficult to build yourself, but they appear not to come with the standard library.

Executing OS Commands

Use the os.execute() command to execute a command within the operating system's shell. The argument is the command to run.

Backticks Substitute

Lua has no backticks operator, the usual way of spawning a process and importing its output into either a string or a table. Not to worry, Lua has io.popen(), with which you can do the same thing. If you want to have a ready made backticks type function, here's how you roll your own. The following code has one called backticks_table() and another one called backticks_string(), which return a table or a string respectively.

#!/usr/bin/lua 

function backticks_table(cmd)
local tab = {}
local pipe = assert(io.popen(cmd),
"backticks_table(" .. cmd .. ") failed.")
local line = pipe:read("*line")
while line do
table.insert(tab, line)
line = pipe:read("*line")
end
return tab
end

function backticks_string(cmd)
local string
local pipe = assert(io.popen(cmd),
"backticks_string(" .. cmd .. ") failed.")
local line = pipe:read("*all")
return line
end

print("=== Testing backticks_string ===")
print(backticks_string("ping -c2 www.troubleshooters.com"))

print("=== Testing backticks_table ===")
for k, v in ipairs(backticks_table("ping -c2 www.troubleshooters.com"), k) do
print(v)
end
The preceding code outputs the following:
slitt@mydesk:~$ ./test.lua
=== Testing backticks_string ===
PING troubleshooters.com (69.89.18.20) 56(84) bytes of data.
64 bytes from box20.bluehost.com (69.89.18.20): icmp_seq=1 ttl=47 time=92.9 ms
64 bytes from box20.bluehost.com (69.89.18.20): icmp_seq=2 ttl=47 time=93.7 ms

--- troubleshooters.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 92.912/93.309/93.706/0.397 ms

=== Testing backticks_table ===
PING troubleshooters.com (69.89.18.20) 56(84) bytes of data.
64 bytes from box20.bluehost.com (69.89.18.20): icmp_seq=1 ttl=47 time=92.9 ms
64 bytes from box20.bluehost.com (69.89.18.20): icmp_seq=2 ttl=47 time=93.8 ms

--- troubleshooters.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 92.979/93.408/93.838/0.527 ms
slitt@mydesk:~$
So they both work perfectly.

Process IDs

Lua can't deduce process IDs so you'll need to execute out to the operating system to do that.


 [ Troubleshooters.com| Code Corner | Email Steve Litt ]

Copyright (C) 2011 by Steve Litt --Legal