• 友链

  • 首页

  • 文章归档
h u a n b l o g
h u a n b l o g

欢

HI,Friend

04月
19
Lua

Lua输入和输出

发表于 2022-04-19 • 字数统计 6729 • 被 767 人看爆

简单I/O模型

  简单模型虚拟了一个当前输入流和一个当前输出流,其I/O操作是通过这些流实现的。I/O库把当前输入流初始化为进程的标准输入(C语言中的stdin),将当前输出流初始化为进程的标准输出(C语言中的stdout)。因此,当执行类似于io.read()这样的语句时,就可以从标准输入中读取一行。

  函数io.input和函数io.output可以用于改变当前的输入输出流。调用io.input(filename)会以只读模式打开指定文件,并将文件设置为当前输入流。之后,所有的输入都将来自该文件,除非再次调用io.input。对于输出而言,函数io.output的逻辑与之类似。如果出现错误,这两个函数都会抛出异常。如果想直接处理这些异常,则必须使用完整IO模型。

  函数io.write可以读取任意数量的字符串(或者数字)并将其写入当前输出流。由于调用该函数时可以使用多个参数,因此应该避免使用io.write(a..b..c),应该调用io.write(a, b,c),后者可以用更少的资源达到同样的效果,并且可以避免更多的连接动作。

  作为原则,应该只在“用后即弃”的代码或调试代码中使用函数print;当需要完全控制输出时,应该使用函数io.write。与函数print不同,函数io.write不会在最终的输出结果中添加诸如制表符或换行符这样的额外内容。此外,函数io.write允许对输出进行重定向,而函数print只能使用标准输出。最后,函数print可以自动为其参数调用tostring,这一点对于调试而言非常便利,但这也容易导致一些诡异的Bugo

  函数io.write在将数值转换为字符串时遵循一般的转换规则;如果想要完全地控制这种转换,则应该使用函数string.format

io.write("sin(3) = ", math.sin(3),"In")                     --> sin(3) = 0.14112000805987
io.write(string.format("sin(3)= %.4f\n", math.sin(3)))      --> sin(3) = 0.1411

  函数io.read可以从当前输入流中读取字符串,其参数决定了要读取的数据:

字符描述
"a"读取整个文件
"l"读取下一行(丢弃换行符)
"L"读取下一行(保留换行符)
"n"读取一个数值
num以字符串读取num个字符

  调用io.read("a")可从当前位置开始读取当前输入文件的全部内容。如果当前位置处于文件的末尾或文件为空,那么该函数返回一个空字符串。

t = io.read("a")                        --读取整个文件
t = string.gsub(t,"bad", "good ")       --进行处理
io.write(t)                             --输出结果

  调用io.read("l")会返回当前输入流的下一行,不包括换行符在内;调用io.read("L")与之类似,但会保留换行符(如果文件中存在)。当到达文件末尾时,由于已经没有内容可以返回,该函数会返回nil。选项"l"是函数read的默认参数。通常只在逐行处理数据的算法中使用该参数,其他情况则更倾向于使用选项"a"一次性地读取整个文件,或者像后续介绍的按块(block)读取。

for count = 1, math.huge do
    local line = io.read("L")
    if line == nil then break end
    io.write(string.format("%6d ", count),line)
end

  不过,如果要逐行迭代一个文件,那么使用io.lines迭代器会更简单:

local count = 0
for line in io.lines() do
    count = count + 1
    io.write(string.format("%6d ", count), line,"in")
end

  调用io.read("n")会从当前输入流中读取一个数值,这也是函数read返回值为数值(整型或者浮点型,与Lua语法扫描器的规则一致)而非字符串的唯一情况。如果在跳过了空格后,函数io.read仍然不能从当前位置读取到数值(由于错误的格式问题或到了文件末尾),则返回nil。

  除了上述这些基本的读取模式外,在调用函数read时还可以用一个数字n作为其参数:在这种情况下,函数read 会从输入流中读取n个字符。如果无法读取到任何字符(处于文件末尾)则返回nil;否则,则返回一个由流中最多n个字符组成的字符串。

作为这种读取模式的示例,以下的代码展示了将文件从stdin复制到stdout的高效方法:

while true do
    local block = io.read(2^13)         --块大小是8KB
    if not block then break end
    io.write(block)
end

  io.read(0)是一个特例,它常用于测试是否到达了文件末尾。如果仍然有数据可供读取,它会返回一个空字符串;否则,则返回nil。

  调用函数read时可以指定多个选项,函数会根据每个参数返回相应的结果。

完整I/O模型

  可以使用函数io.open来打开一个文件,该函数仿造了C语言中的函数fopen。这个函数有两个参数,一个参数是待打开文件的文件名,另一个参数是一个模式(mode)字符串。模式字符串包括表示只读的r、表示只写的w(也可以用来删除文件中原有的内容)、表示追加的a,以及另外一个可选的表示打开二进制文件的b。函数io.open返回对应文件的流。当发生错误时,该函数会在返回nil的同时返回一条错误信息及一个系统相关的错误码:

print(io.open( "non-existent-file","r"))
--> nilnon-existent-file: No such file or directory 2
print( io.open(" /etc/passwd" ,"w" ))
-->nil letc/passwd: Permission denied 13

  检查错误的一种典型方法是使用函数assert:

local f = assert(io.open(filename, mode))

  如果函数io.open执行失败,错误信息会作为函数assert的第二个参数被传入,之后函数assert会将错误信息展示出来。

  在打开文件后,可以使用方法read和write从流中读取和向流中写入。它们与函数read和write类似,但需要使用冒号运算符将它们当作流对象的方法来调用。

例如,可以使用如下的代码打开一个文件并读取其中所有内容:

local f = assert(io.open(filename,"r"))
local t = f:read("a")
f:close()

  IO库提供了三个预定义的C语言流的句柄: io.stdin、io.stdout和 io.stderr。

例如,可以使用如下的代码将信息直接写到标准错误流中:

io.stderr:write(message)

  函数io.input和io.output允许混用完整I/O模型和简单I/O模型。调用无参数的io.input()可以获得当前输入流,调用io.input(handle)可以设置当前输入流(类似的调用同样适用于函数io.output)。

例如,如果想要临时改变当前输入流,可以像这样:

local temp = io.input()             --保存当前输入流
io.input("newinput")                --打开一个新的当前输入流
--对新的输入流进行某些操作
io.input():close()                  --关闭当前流
io.input(temp)                      --恢复此前的当前输入流

注意: io.read(args)实际上是io.input():read(args)的简写,即函数read是用在当前输入流上的。同样,io.write(args)是io.output():write(args)的简写。

  函数io.lines从流中读取内容。返回一个可以从流中不断读取内容的迭代器。给函数io.lines提供一个文件名,它就会以只读方式打开对应该文件的输入流,并在到达文件末尾后关闭该输入流。若调用时不带参数,函数io.lines就从当前输入流读取。我们也可以把函数lines当作句柄的一个方法。此外,从Lua5.2开始,函数io.lines可以接收和函数io.read一样的参数。

例如,下面的代码会以在8KB为块迭代,将当前输入流中的内容复制到当前输出流中:

for block in io.input():lines(2^13) do
    io.write(block)
end

其它文件操作

  函数io.tmpfile返回一个操作临时文件的句柄,该句柄是以读/写模式打开的。当程序运行结束后,该临时文件会被自动移除(删除)。

  函数flush将所有缓冲数据写入文件。与函数write一样,我们也可以把它当作io.flush()使用,以刷新当前输出流;或者把它当作方法f:flush()使用,以刷新流f。

  函数setvbuf用于设置流的缓冲模式。该函数的第一个参数是一个字符串: "no"表示无缓冲,"full"表示在缓冲区满时或者显式地刷新文件时才写人数据,"line”表示输出一直被缓冲直到遇到换行符或从一些特定文件(例如终端设备)中读取到了数据。对于后两个选项,函数setvbuf支持可选的第二个参数,用于指定缓冲区大小。

  在大多数系统中,标准错误流(io.stderr)是不被缓冲的,而标准输出流(io.stdout)按行缓冲。因此,当向标准输出中写入了不完整的行(例如进度条)时,可能需要刷新这个输出流才能看到输出结果。

  函数seek用来获取和设置文件的当前位置,常常使用f:seek(whence,offset)的形式来调用,其中参数whence是一个指定如何使用偏移的字符串。当参数whence取值为"set"时,表示相对于文件开头的偏移;取值为"cur"时,表示相对于文件当前位置的偏移;取值为"end"时,表示相对于文件尾部的偏移。不管whence的取值是什么,该函数都会以字节为单位,返回当前新位置在流中相对于文件开头的偏移。

  whence的默认值是"cur",offset的默认值是0。因此,调用函数file:seek()会返回当前的位置且不改变当前位置;调用函数file:seek("set")会将位置重置到文件开头并返回O;调用函数file:seek("end")会将当前位置重置到文件结尾并返回文件的大小。

下面的函数演示了如何在不修改当前位置的情况下获取文件大小:

function fsize (file)
    local current = file:seek()     --保存当前位置
    local size = file:seek("end")   --获取文件大小
    file:seek("set", current)       --恢复当前位置
    return size
end

  此外,函数os.rename用于文件重命名,函数os.remove用于移除(删除)文件。需要注意的是,由于这两个函数处理的是真实文件而非流,所以它们位于os库而非io库中。

  上述所有的函数在遇到错误时,均会返回nil外加一条错误信息和一个错误码。

其他系统调用

  函数os.exit用于终止程序的执行。该函数的第一个参数是可选的,表示该程序的返回状态,其值可以为一个数值(О表示执行成功)或者一个布尔值(true表示执行成功);该函数的第二个参数也是可选的,当值为true时会关闭Lua状态3并调用所有析构器释放所占用的所有内存(这种终止方式通常是非必要的,因为大多数操作系统会在进程退出时释放其占用的所有资源)。

  函数os.getenv用于获取某个环境变量,该函数的输入参数是环境变量的名称,返回值为保存了该环境变量对应值的字符串:

print(os.getenv("HOME"))        --> /home/lua

对于未定义的环境变量,该函数返回nil。

运行系统命令

  函数os.execute用于运行系统命令,它等价于C语言中的函数system。该函数的参数为表示待执行命令的字符串,返回值为命令运行结束后的状态。其中,第一个返回值是一个布尔类型,当为true时表示程序成功运行完成;第二个返回值是一个字符串,当为"exit"时表示程序正常运行结束,当为"signal"时表示因信号而中断;第三个返回值是返回状态(若该程序正常终结)或者终结该程序的信号代码。

例如,在POSIX和Windows中都可以使用如下的函数创建新目录:

function createDir(dirname)
    os.execute ( "mkdir " .. dirname)
end

  另一个非常有用的函数是io.popen。同函数os.execute一样,该函数运行一条系统命令,但该函数还可以重定向命令的输入/输出,从而使得程序可以向命令中写入或从命令的输出中读取。

例如,下列代码使用当前目录中的所有内容构建了一个表:

--对于POSIX系统而言,使用'ls '而非'dir'
local f = io.popen( "dir /B","r ")
local dir = {}
for entry in f:lines() do
    dir[#dir + 1] = entry
end

  其中,函数io.popen的第二个参数r表示从命令的执行结果中读取。由于该函数的默认行为就是这样,所以在上例中这个参数实际是可选的。

分享到:
Lua流程控制和循环语句
Lua函数
  • 文章目录
  • 站点概览
欢

网红 欢

你能抓到我么?

Email RSS
看爆 Top5
  • mac系统版本与Xcode版本有冲突 3,840次看爆
  • JAVA_HOME环境配置问题 3,552次看爆
  • AssetBundle使用 3,289次看爆
  • VSCode配置C++开发环境 3,058次看爆
  • Lua反射 3,011次看爆

Copyright © 2025 欢 粤ICP备2020105803号-1

由 Halo 强力驱动 · Theme by Sagiri · 站点地图