第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但有一些不同之处。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还可以帮助支持 Lua 项目


21.1 – 简单 I/O 模型

简单模型对其所有操作都在两个当前文件中执行。该库将当前输入文件初始化为进程的标准输入 (stdin),将当前输出文件初始化为进程的标准输出 (stdout)。因此,当我们执行类似 io.read() 的操作时,我们从标准输入读取一行。

我们可以使用 io.inputio.output 函数更改这些当前文件。类似 io.input(filename) 的调用会打开给定文件(以读取模式),并将其设置为当前输入文件。从这时起,所有输入都将来自此文件,直到再次调用 io.inputio.output 对输出执行类似的工作。如果出现错误,这两个函数都会引发错误。如果您想直接处理错误,则必须使用完整模型中的 io.open

由于 writeread 更简单,因此我们先来看一下它。io.write 函数只是获取任意数量的字符串参数,并将它们写入当前输出文件。数字按照通常的转换规则转换为字符串;要完全控制此转换,您应该使用 string 库中的 format 函数。

    > io.write("sin (3) = ", math.sin(3), "\n")
      --> sin (3) = 0.1411200080598672
    > io.write(string.format("sin (3) = %.4f\n", math.sin(3)))
      --> sin (3) = 0.1411
避免使用类似 io.write(a..b..c) 的代码;调用 io.write(a,b,c) 可以通过更少的资源实现相同的效果,因为它避免了连接。

通常,您应该将 print 用于快速且不完善的程序或用于调试,将 write 用于需要完全控制输出的情况。

    > print("hello", "Lua"); print("Hi")
      --> hello   Lua
      --> Hi
    
    > io.write("hello", "Lua"); io.write("Hi", "\n")
      --> helloLuaHi
print 不同,write 不会向输出添加额外的字符,例如制表符或换行符。此外,write 使用当前输出文件,而 print 始终使用标准输出。最后,print 会自动对其参数应用 tostring,因此它还可以显示表、函数和 nil

read 函数从当前输入文件读取字符串。其参数控制读取的内容

"*all"读取整个文件
"*line"读取下一行
"*number"读取一个数字
num读取一个最多包含 num 个字符的字符串

调用 io.read("*all") 会从当前位置开始读取整个当前输入文件。如果我们处于文件末尾,或者文件为空,该调用会返回一个空字符串。

由于 Lua 可以高效地处理长字符串,因此在 Lua 中编写筛选器的简单技术是将整个文件读入字符串,对字符串进行处理(通常使用 gsub),然后将字符串写入输出

    t = io.read("*all")         -- read the whole file
    t = string.gsub(t, ...)     -- do the job
    io.write(t)                 -- write the file
例如,以下代码是一个完整程序,用于使用 MIME 的quoted-printable 编码对文件的文本进行编码。在此编码中,非 ASCII 字符编码为 =XX,其中 XX 是十六进制中字符的数字代码。为了保持编码的一致性,=´ 字符也必须编码。gsub 中使用的模式捕获了代码从 128 到 255 的所有字符,以及等号。
    t = io.read("*all")
    t = string.gsub(t, "([\128-\255=])", function (c)
          return string.format("=%02X", string.byte(c))
        end)
    io.write(t)
在奔腾 333MHz 上,此程序需要 0.2 秒来转换一个包含 200K 字符的文件。

调用 io.read("*line") 会从当前输入文件中返回下一行,不带换行符。当我们到达文件末尾时,该调用会返回 nil(因为没有下一行要返回)。此模式是 read 的默认模式,因此 io.read() 的效果与 io.read("*line") 相同。通常,我们仅在算法按行自然处理文件时使用此模式;否则,我们倾向于一次使用 *all 读取整个文件,或分块读取,如我们稍后将看到的。作为使用此模式的一个简单示例,以下程序将其当前输入复制到当前输出,对每一行进行编号

    local count = 1
    while true do
      local line = io.read()
      if line == nil then break end
      io.write(string.format("%6d  ", count), line, "\n")
      count = count + 1
    end
但是,要逐行迭代整个文件,我们最好使用 io.lines 迭代器。例如,我们可以编写一个完整程序来对文件的行进行如下排序
    local lines = {}
    -- read the lines in table 'lines'
    for line in io.lines() do
      table.insert(lines, line)
    end
    -- sort
    table.sort(lines)
    -- write all the lines
    for i, l in ipairs(lines) do io.write(l, "\n") end
此程序在 1.8 秒内对一个包含 4.5 MB(32K 行)的文件进行排序(在奔腾 333MHz 上),而系统 sort 程序花费 0.6 秒,该程序是用 C 编写的,并且经过高度优化。

调用 io.read("*number") 会从当前输入文件中读取一个数字。这是 read 返回数字而不是字符串的唯一情况。当您需要从文件中读取许多数字时,中间字符串的缺失可以显著提高性能。*number 选项会跳过数字之前的任何空格,并接受诸如 -3+5.21000-3.4e-23 的数字格式。如果它在当前文件位置找不到数字(由于格式错误或文件末尾),它会返回 nil

您可以使用多个选项调用 read;对于每个参数,该函数将返回相应的结果。假设您有一个文件中每行包含三个数字

    6.0       -3.23     15e12
    4.3       234       1000001
    ...
现在您想打印每行的最大值。您可以在对 read 的单次调用中读取所有三个数字
    while true do
      local n1, n2, n3 = io.read("*number", "*number",
                                 "*number")
      if not n1 then break end
      print(math.max(n1, n2, n3))
    end
在任何情况下,您都应考虑使用 io.read 的选项 "*all" 读取整个文件,然后使用 gfind 将其分解的替代方案
    local pat = "(%S+)%s+(%S+)%s+(%S+)%s+"
    for n1, n2, n3 in string.gfind(io.read("*all"), pat) do
      print(math.max(n1, n2, n3))
    end

除了基本的读取模式外,您还可以使用数字 n 作为参数调用 read:在这种情况下,read 尝试从输入文件中读取 n 个字符。如果它无法读取任何字符(文件结尾),read 返回 nil;否则,它返回一个最多包含 n 个字符的字符串。作为此读取模式的一个示例,以下程序是(当然在 Lua 中)从 stdin 复制文件到 stdout 的一种有效方式

    local size = 2^13      -- good buffer size (8K)
    while true do
      local block = io.read(size)
      if not block then break end
      io.write(block)
    end

作为特例,io.read(0) 可用作文件结尾的测试:如果还有更多内容要读取,它将返回一个空字符串,否则返回 nil