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

Contents

  • 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:
    print()
    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(pi_name)
    io.write(" is ")
    io.write(pi)
    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:
    sf=string.format
    print(sf("The value of %s is %d, isn't that cool?",pi_name, pi))
    You read from standard input (stdin) via the io.read() function, as shown in the following program that prompts the user to input their name, and then repeats the name:
    #!/usr/bin/lua 

    sf=string.format
    local answer
    io.write("What\'s your name?=>")
    answer = io.read()
    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.
    slitt@mydesk:~$

    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:
    #!/usr/bin/lua 

    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
    slitt@mydesk:~$
    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
    slitt@mydesk:~$
    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
    slitt@mydesk:~$

    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 io.open(), like this:
    myinputhandle = io.open("myinputfilename", "r")
    myoutputhandle = io.open("myoutputfilename", "w")
    You must test for proper opening. The easiest way is to use assert, like this:
    local inf = assert(io.open("brighthouse.log", "r"), "Failed to open input file")
    local ouf = assert(io.open("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(io.open("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(io.open("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:
    #!/usr/bin/lua 

    sf=string.format
    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
    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 io.read(), as shown:
    local inf = assert(io.open("/d/bats/s.emdl", "r"))
    local str = inf:read(53)
    print(str)
    print(#str)
    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
    IPCop
    par
    53
    slitt@mydesk:~$
    Another place where numeric arguments to io.read() 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:
    #!/usr/bin/lua 

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



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

    Copyright (C) 2011 by Steve Litt --Legal