争取两三周入门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()对命令的执行结果按行遍历。