06-输入输出
争取两三周入门Lua!
由于Lua强调可移植性和嵌入性,所以Lua语言本身并没有提供太多与外部交互的机制。
简单I/O模型
简单I/O模型虚拟了一个当前输入流和一个当前输出流,其I/O操作是通过这些流实现的。I/O库把当前输入流初始化为进程的标准输入(C语言中的stdin
),将当前输出流初始化为进程的标准输出(C语言的stdout
)。
改变当前输入/输出流
调用函数io.input(filename)
会以只读模式打开指定文件,并将文件设置为当前输入流,之后所有的输入都将来自该文件。同理,调用函数io.output(filename)
会将指定文件设置为当前输出流。
如果出现错误,这两个函数会抛出异常。如果想直接处理这些异常,得使用下面提到的完整I/O模型。
读写操作
写操作
函数io.write
可以读取任意数量的字符串/数字并将其写入当前输出流。由于调用该函数时可以使用多个参数,因此应避免使用io.write(a..b..c)
,而是使用io.write(a, b, c)
,后者可以用更少的资源达到同样的效果,并且可以避免更多的连接动作。
和输出函数print
不同的是,函数io.write
不会在最终的输出结果中添加诸如制表符或换行符这样的额外内容;而且print
只能用标准输出stdout
,而io.write
则可以重定向输出。
函数io.write
在将数值转换为字符串时遵循一般的转换规则:
io.write("sin(3) = ", math.sin(3), "\n") --> sin(3) = 0.14112000805987
如果想要完全控制这种转换,需要用函数string.format
:
io.write(string.format("sin(3) = %.4f\n", math.sin(3))) --> sin(3) = 0.1411
读操作
函数io.read
可以从当前输入流中读取字符串,其参数决定了要读取的数据:
参数 | 要读取的数据 |
---|---|
"a" | 读取整个文件 |
"l" | 读取下一行(丢弃换行符),是io.read 的默认参数 |
"L" | 读取下一行(保留换行符) |
"n" | 读取一个数值 |
num | 以字符串形式读取num 个字符,也被称为“块读取”。其中, io.read(0) 常用于测试是否到达EOF,是的话返回nil ,否则返回空串 |
例如,以下代码段将某文件内容的所有非ASCII字符用MIME编码:
t = io.read("a")
t = string.gsub(t, "([\128-\255=])", function (c)
return string.format("=%02X", string.byte(c))
end)
io.write(t)
io.lines()
迭代器可以逐行读取一个文件:
local count = 0
for line in io.lines() do
count = count + 1
io.write(string.foramt("%6d ", count), line, "\n")
end
像其他函数一样,io.read()
也支持多返回值,例如要一下子读取3个数值可以这样写:
local n1, n2, n3 = io.read("n", "n", "n")
完整I/O模型
如果要执行同时读写多个文件等高级操作,简单I/O模型就力不从心了。还需要完整I/O模型。
打开文件
可以用io.open()
打开一个文件,该函数类似于C语言中的fopen()
。
这个函数有两个参数:
- 要打开的文件名
- 模式字符串,如 表示只读的
r
;表示只写的w
(也可用来删除文件中原有的内容);表示追加的a
;表示打开二进制文件的b
。
这个函数的返回值是对应文件的“流”,如果打开文件时发生错误会返回nil
、错误信息和错误码。
# 打开不存在的文件
io.open("notExist.txt", "r")
nil notExist.txt: No such file or directory 2
可以用assert()
来检查错误,该函数接收两个参数,第一个是表达式,第二个是错误信息,如果表达式结果为nil
,那么会输出错误信息:
assert(io.open("notExist.txt", "r"))
stdin:1: notExist.txt: No such file or directory
stack traceback:
[C]: in function 'assert'
stdin:1: in main chunk
[C]: in ?
打开文件后,可以用它的方法read
和write
对流进行读写操作:
local f = assert(io.open(filename, "r"))
local t = f:read("a")
f:close()
预定义的输入/输出流
I/O库提供了三个预定义的C语言流的句柄:io.stdin
,io.stdout
,io.stderr
。例如,可以使用如下代码将信息直接写入标准错误流中:
io.stderr:write(message)
调用无参数的io.input()
可以获得当前输入流,调用io.input(handle)
可以设置当前输入流,io.output()
同理。实际上,io.read(args)
是io.input():read(args)
的缩写,io.write(args)
同理。
使用io.lines()
也能以行读取当前流中的内容,前提是没有参数。从Lua5.2开始,io.lines()
可以接收和io.read()
一样的参数。
其他文件操作
操作临时文件
函数io.tmpfile()
返回一个操作临时文件的句柄,该句柄是以读/写模式打开的。当程序运行结束后,该临时文件会被自动删除。
流操作
刷新流
成员函数flush()
将所有缓冲数据写入文件。例如io.flush()
刷新当前输出流;f:flush()
刷新流f
。
设置流的缓冲模式
成员函数setvbuf()
用于设置流的缓冲模式,该函数的参数如下:
- 第一个参数指定模式:
"no"
表示无缓冲;"full"
表示缓冲区满/显式调用flush()
时才写入数据;"line"
表示输出一直被缓冲直至遇到换行符或从一些特定文件(例如终端设备)中读取到了数据。 - 第二个参数是可选的,指定缓冲区大小。
在大多数系统中,标准错误流io.stderr
是不被缓冲的,而标准输出流io.stdout
按行缓冲。因此,当向标准输出中写入了不完整的行(例如进度条时),可能需要刷新这个输出流才能看到输出结果。
设置读取文件的位置
函数seek
用来获取和设置文件的当前位置,常常使用f:seek(whence, offset)
的形式来调用,其中:
- 参数
whence
指定偏移模式:"set"
表示相对于文件开头的偏移;"cur"
表示相对于文件当前位置的偏移;"end"
表示相对于文件尾部的偏移。 - 参数
offset
指定偏移量,单位为字节。
它的默认值为f:seek("cur", 0)
,因此调用f:seek()
会返回当前的位置且不改变;调用f:seek("set")
会将位置重置到文件开头并返回0;调用f:seek("end")
会将当前位置重置到文件结尾并返回文件的大小。
可以用seek()
在不修改当前读入位置的情况下获得文件大小:
function fsize(file)
local current = file:seek() -- 保存当前位置
local size = file:seek("end") -- 获取文件大小
file:seek("cur", current) -- 恢复当前位置
return size
end
文件重命名
函数os.remane()
用于文件重命名。
删除文件
函数os.remove()
用于删除文件。
其他系统调用
终止程序执行
函数os.exit()
用于终止程序的执行,有两个可选参数:
- 第一个参数是该程序的返回状态,执行成功的值为
0
或true
; - 第二个参数是退出程序后是否要析构所有资源;
获取环境变量
函数os.getenv()
用于获取某个环境变量:
print(os.getenv("HOME")) --> nil
print(os.getenv("JAVA_HOME")) --> D:\JavaJDK
运行系统命令
使用系统调用
函数os.execute()
用于运行系统命令,类似于C语言中的system()
。该函数的参数为一个表示要执行命令的字符串;返回值有三个:
- 第一个返回值是一个布尔类型,表示命令是否成功执行;
- 第二个返回值是一个字符串,当为
"exit"
时表示命令正常执行结束,为"signal"
时表示因信号而中断; - 第三个返回值是返回状态(该程序正常终结)或终结该程序的信号代码。
使用io库函数
函数io.popen()
也能运行系统命令,但该函数 还可以重定向命令的输入/输出,从而使得程序可以向命令中写入或从命令输出中读取,返回值就是IO流,可以用io.lines()
对命令的执行结果按行遍历。