Troubleshooters.Com and Code Corner Present

Litt's Lua Laboratory:
Lua Input and Output
(With Snippets)

Copyright (C) 2011 by Steve Litt

Debug like a Ninja


  • Standard Input and Output
  • Output to stderr
  • Reading and Writing Files
  • Standard Input and Output

    Like most languages, Lua reserves its easiest input and output for standard input and standard output. The print() statement is easiest:
    print("Hello World")
    If you want to print a blank line, just use the function without arguments:
    The print() statement always adds a newline, so each print() statement's contents occur on their own lines. Now let's say you need to write something without a newline, use io.write():
    local pi = 3.14159
    local pi_name = "PI"
    io.write("The value of ")
    io.write(" is ")
    print(", isn't that cool?")
    It's often easier to use string.format():
    print(string.format("The value of %s is %d, isn't that cool?",pi_name, pi))
    One problem with string.format() is it's a lot of typing and makes for long lines. So what you can do give it a substitute name at the top of the program:
    print(sf("The value of %s is %d, isn't that cool?",pi_name, pi))
    You read from standard input (stdin) via the function, as shown in the following program that prompts the user to input their name, and then repeats the name:

    local answer
    io.write("What\'s your name?=>")
    answer =
    print(sf("OK, your name is %s.",answer))
    The preceding code produces the following output (the bold type is input by the user):
    slitt@mydesk:~$ ./test.lua
    What's your name?=>Steve Litt
    OK, your name is Steve Litt.

    Output to stderr

    Error messages should go to the Standard Error file handle, stderr. Another place for stderr is writing messages to the user on applications whose data goes out stdout, such as filters. The way you write "Hello World" to stderr is the following:
    io.stderr:write("Hello World")
    NOTICE THE COLON!!! The io.stderr object is a table containing a function called write, so you must either introduce io.stderr as the first argument to its write function, or else use the colon, as will be explained in the article on functions.

    But for the time being, it's obviously less verbiage, shorter lines and more readable to use the colon.

    Now let's demonstrate with the following program:

    print("This should go to stdout")
    io.stderr:write("This should go to stderr\n")
    print("Again stdout")
    io.stderr:write("Again stderr\n")
    print("Third stdout")
    io.stderr:write("Third stderr\n")
    The preceding code produces the following output:
    slitt@mydesk:~$ ./test.lua
    This should go to stdout
    This should go to stderr
    Again stdout
    Again stderr
    Third stdout
    Third stderr
    But now look what happens when you redirect stdin to /dev/null:
    slitt@mydesk:~$ ./test.lua > /dev/null
    This should go to stderr
    Again stderr
    Third stderr
    And here's what happens if you send stderr to /dev/null:
    slitt@mydesk:~$ ./test.lua 2> /dev/null
    This should go to stdout
    Again stdout
    Third stdout

    Reading and Writing Files

    Reading or writing a file requires a file handle. Three file handles are normally open all the time:
    Other file handles are opened with various Lua commands, primarily, like this:
    myinputhandle ="myinputfilename", "r")
    myoutputhandle ="myoutputfilename", "w")
    You must test for proper opening. The easiest way is to use assert, like this:
    local inf = assert("brighthouse.log", "r"), "Failed to open input file")
    local ouf = assert("junk251.jnk", "w"), "Failed to open output file"
    If you want a more definitive message or error handling, you can test for the handle being nil and if so do your error routine. I'm a big fan of assert because it's quick and easy while writing code. My opinion is you can always make it more sophisticated later.

    Reading Files

    There are many ways to read a file. One is to read the entire file into an immense string, like this:
    local inf = assert("brighthouse.log", "r"), "Failed to open input file")
    local bigstring = inf:read("*all")
    Personally I'm not a fan. At least in most cases. My experiments indicate that doing a whole file read into a string and then out again into a file is roughly half the speed of a Linux cp command on the same computer, so on large files it's worth shelling out to Linux and doing the cp command.

    I see only a few situations file-at-once to string reading is advantageous:
    A long, long time ago I wrote a program (in C) to simultaneously search a long file for several different keywords. I mean so different regex wouldn't have worked. I would have given my right arm to have Lua's file-at-once-to-string read.

    But of course in general, especially in these days of XML, most file reads we application programmers read are line by line, not binary or even fixed-length-sequential. In that case it makes more sense to read line-at-a-time, like this:
    local inf = assert("brighthouse.log", "r"), "Failed to open input file")
    local line = inf:read("*line")
    But the coolest way to read line by line is the io.lines() iterator:

    local lines = 0
    local menus = 0
    for line in io.lines("/d/bats/s.emdl") do
    lines = lines + 1
    if string.match(line, ":::") then menus = menus + 1 end

    print(sf("The file had %d menus out of %d lines.", menus, lines))
    For those occasional fixed length sequential files you can use a number argument for, as shown:
    local inf = assert("/d/bats/s.emdl", "r"))
    local str = inf:read(53)
    s.emdl isn't fixed length sequential, but I haven't had a fixed length sequential file in years, so I just used my EMDL file. Anyway, here's the resulting output:
    slitt@mydesk:~$ ./test.lua
    S:::Main Menu
    Internet ::: Internet menu
    Another place where numeric arguments to could be very handy is on files where a couple bytes give you the length of the next section, on and on. PDF files can be kind of like that.

    Writing Files

    Writing files is about as simple as it gets:

    local ouf = assert("junk251.jnk", "w"))
    for i=1, 20 do
    ouf:write(tostring(1000 + i))
    It's easy.

     [| Code Corner | Email Steve Litt ]

    Copyright (C) 2011 by Steve Litt --Legal