Lua IO操作
回到:
简单IO模型
简单IO模型下,Lua只有两个文件的io流:输入流和输出流,默认的输入流是stdin,默认的输出流是stdout。所有操作都在这两个文件上操作。io.read()和io.write()两个函数可分别操作这两个IO流。
1 | echo -e "hello\nworld" | lua -e 'print(io.read())' |
使用io.input(FILENAME)
和io.output(FILENAME)
可以改变默认的输入流和输出流,使用它们设置输入流或输出流后,后续的IO操作都将基于其指定的文件,除非再次调用它们改变IO流。如果出现异常(比如无权限/文件不存在),将直接抛出错误。后面还会介绍这两个函数的其它功能。
1 | --> 从/etc/passwd中读取 <-- |
io.write()写入文件时,将覆盖原文件,当文件不存在时,将创建。
io.write()时,不建议采用io.write("hello".."world")
将字符串连接起来,而是将各段内容作为io.write()的参数。例如:
1 | io.write("hello".."world".."!\n") |
io.read()
io.read()默认每次读取一行,它有可选的参数来指定如何读取数据。当参数为:
a
时,(从当前位置开始)读取整个文件内容,读取不到内容返回nill
时,(从当前位置开始)读取一行内容,丢弃尾部换行符,读取不到内容返回nilL
时,(从当前位置开始)读取一行内容,保留尾部换行符,读取不到内容返回niln
时,(从当前位置开始)读取一个数值,读取成功则返回该数值,否则返回nilNUM
时,表示(从当前位置开始)读取至多NUM个字符(即按块读取),读取不到内容返回nil- 特别地,如果NUM=0,即io.read(0),它表示读取0个字符,可用于测试是否到了文件尾部,如果未到尾部,则返回空字符串,如果到了尾部,则返回nil
- io.read()可以有多个参数(参数如上所述),它将依次按照每个参数指定的方式读取数据(参见下方示例)
例如,可以将(不算太大的)文件所有内容读取到变量中进行筛选:
1 | io.input("/tmp/a.log") |
通过l
或L
可以迭代行。例如:
1 | io.input("/etc/resolv.conf") |
也可以使用io.lines()
来迭代行,注意,它不会保留尾部换行符:
1 | io.input("/etc/resolv.conf") |
例如,要对文件按行进行排序:
1 | io.input("/etc/resolv.conf") |
如果文件较大,那么一次性读取所有内容将消耗比较大的内存,为了更高效读取,可以按块进行读取,即io.read(NUM)
。
例如,下面高效拷贝一个文件:
1 | io.input("bigfile") |
按块读取时可能会读到行中间,有时候为了处理完整的行,可以按块读取之后再按行读取一次,这样便保证了行的完整性。
例如:
1 | io.input("/etc/passwd") |
如果使用io.read("n")
,则从前向后扫描,直到遇到数值(可识别正负号),然后读取该数值。但是一定要注意,扫描时只能跳过空白符号,如果有字母、下划线或其它符号,则读取立即停止,它认为文件格式错误。所以,该读取功能的使用场景是非常受限的。
此外,io.read()可以提供多个参数。
例如:
1 | a,b,c = io.read("n","n","n") --> 读取三个数值,赋值给a b c |
复杂io模型
复杂IO模型使用文件句柄来执行IO操作。
简单IO模型是复杂IO模型的特例,它使用的是隐式的文件句柄。
使用io.open(FILENAME,MODE)
可打开文件并返回文件句柄。其中MODE:
- “r”: 只读方式打开文件(默认模式)
- “w”: 可写方式打开文件
- “a”: 追加模式打开文件
- “r+”: 更新模式打开文件,可读可写,文件指针置于文件尾部
- “w+”: 更新模式打开文件,可读可写,文件指针置于文件头部
- “a+”: 追加更新模式打开文件,可读可写,文件指针置于文件尾部
还可以在上述模式后加上b
,表示二进制方式打开文件。
如果io.open()打开错误,则返回nil并报错。
1 | io.open("/etc/fstab") |
所以,根据io.open()的返回值可判断是否成功,并做出错误处理。当然,更常见的处理方式是使用assert():
1 | fh,err = io.open(FILENAME,MODE) |
文件句柄是对象,通过它来执行IO操作,需以对象的方式来调用各函数:
1 | <-- 以下是Lua支持的文件句柄对象的方法 --> |
Lua提供了三个预定义的文件句柄:io.stdin、io.stdout和io.stderr。所以,从标准输入读数据、写入标准输出和写入标准错误的方式:
1 | io.stdin:read() |
io.input()、io.output()可以获取或设置当前默认的输入流、输出流:
- 当无参数时,可获取当前的输入流、输出流
- 当给定字符串参数时,可打开某文件并设置其为当前的输入流、输出流
- 当给定文件句柄对象作为参数时,可设置该句柄作为当前的输入流、输出流
例如:
1 | local tmp_fh = io.input() --> 备份当前输入流 |
实际上,io.read()和io.write()分别是io.input():read()
以及io.output:write()
的简写形式。
io.type()
1 | io.type (obj) |
检测obj是否是一个有效的句柄:
- 如果obj是一个已经打开的文件句柄,则返回”file”字符串
- 如果obj是一个已关闭的文件句柄,则返回”closed file”字符串
- 如果不是文件句柄,则返回nil
io.lines()
1 | io.lines([filename,read_mode]) |
io.lines()
或fh:lines()
返回一个不断读取文件数据的迭代器。
对于io.lines()来说:
- 当不指定任何参数时,则从当前输入流中按照read_mode指定的方式不断读取数据
- 当指定文件名时,将以只读方式打开该文件并按照read_mode指定的方式不断读取数据
从Lua 5.2开始,fh:lines()
和io.lines()
的read_mode用于指定读取方式,是和io.read()一样的参数,比如io.lines(8192)。
例如:
1 | for line in io.lines("/etc/resolv.conf","L") do |
io.flush()
flush写io的缓存。
io.flush() flush当前的输出流buffer
fh:flush() flush fh的输出流buffer
io.setvbuf()
设置io buffer的缓冲模式。
1 | file:setvbuf (mode [, size]) |
mode的值:
- “no”:无缓冲模式,数据直接写入文件
- “full”:全缓冲(或块缓冲)模式,即堆满缓冲空间才写入文件
- “line”:行缓冲模式,遇到换行符才写入文件
对于full和line两种模式,可以设置第二个参数,它是一个表示字节数的数值,用于指定缓冲空间的大小。它们有默认值,默认值应该来源于操作系统。
fh:seek()
获取或设置文件指针的位置。
1 | fh:seek ([whence [, offset]]) |
返回设置后的文件指针所在位置偏移。
whence的值:
- “set”:表示相对于文件开头的偏移
- “cur”:表示相对于当前位置的偏移
- “end”:表示相对于文件尾部的偏移
offset的单位为字节。
当不给定任何参数时,其whence默认值为cur,offset默认值为0。所以:
fh:seek()
可获取当前文件指针的偏移量fh:seek("set")
将指针重置到文件开头fh:seek("end")
将偏移到文件结尾并返回结尾处的偏移量,即文件大小
所以,要临时获取文件大小时,可如下操作:
1 | function fsize(fh) |
io.popen()
运行外部程序,并指定向其写入数据还是从中读取数据。
1 | io.popen (prog [, mode]) |
以子进程的方式运行外部程序,并返回一个文件句柄。可指定模式参数mode:
- “r”:表示从外部程序读数据
- “w”:表示向外部程序写数据
所以:
1 | io.popen(CMD,"r") --> CMD | lua_program |
例如:
1 | fh = io.popen("head -n 5 /etc/passwd", "r") |
io.tmpfile()
io.tmpfile()
创建一个临时文件并返回其句柄,该句柄可读可写,在程序终止时文件自动删除。
二进制文件读写
io.input()和io.output()总以文本方式打开文件,在Unix系统中,二进制文件和文本文件是没有区别的,但是在Windows系统中,必须以二进制方式打开文件才能识别某些特殊的字符。
通常来说,以二进制方式打开文件时的模式要么是”a”表示读取所有字节,要么是NUM表示读取指定字节数量的数据。在二进制读写中,没有行的概念,换行符自身就是二进制字节,所以按行读取是没有意义的。
例如,DOS格式的文件转换成UNIX格式:
1 | local fh_in = assert(io.open(arg[1]),"rb") |
字符串缓冲区
想要将所有包含某类字符的行串联起来:
1 | local buf = "" |
这种方式效率很低,因为每次字符串连接的时候都会拷贝之前的buf。而这具有滚雪球效应,比如buf已经保存了2K数据,保存下一行时,将拷贝这2K,下一次又拷贝2K+。
比较高效的方式是将所有符合条件的行保存在序列中:
1 | -- store lines into table |
为了避免最后一次字符串串联导致的拷贝,可以在table.concat()之前,额外添加一个尾部空字符串元素。
1 | -- store lines into table |