This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.

21.2 – The Complete I/O Model

For more control over I/O, you can use the complete model. A central concept in this model is the file handle, which is equivalent to streams (FILE*) in C: It represents an open file with a current position.

To open a file, you use the function, which mimics the fopen function in C. It receives as arguments the name of the file to open plus a mode string. That mode string may contain an `r´ for reading, a `w´ for writing (which also erases any previous content of the file), or an `a´ for appending, plus an optional `b´ to open binary files. The open function returns a new handle for the file. In case of errors, open returns nil, plus an error message and an error number:

    print("non-existent file", "r"))
      --> nil     No such file or directory       2
    print("/etc/passwd", "w"))
      --> nil   Permission denied       13
The interpretation of the error numbers is system dependent.

A typical idiom to check for errors is

    local f = assert(, mode))
If the open fails, the error message goes as the second argument to assert, which then shows the message.

After you open a file, you can read from it or write to it with the methods read/write. They are similar to the read/write functions, but you call them as methods on the file handle, using the colon syntax. For instance, to open a file and read it all, you can use a chunk like this:

    local f = assert(, "r"))
    local t = f:read("*all")

The I/O library also offers handles for the three predefined C streams: io.stdin, io.stdout, and io.stderr. So, you can send a message directly to the error stream with a code like this:


We can mix the complete model with the simple model. We get the current input file handle by calling io.input(), without arguments. We set the current input file handle with the call io.input(handle). (Similar calls are also valid for io.output.) For instance, if you want to change the current input file temporarily, you can write something like this:

    local temp = io.input()   -- save current file
    io.input("newinput")      -- open a new current file
    ...                       -- do something with new input
    io.input():close()        -- close current file
    io.input(temp)            -- restore previous current file