引言

I/O 库提供了两套不同风格的文件处理接口。 第一种风格使用隐式的文件句柄; 它提供设置默认输入文件及默认输出文件的操作, 所有的输入输出操作都针对这些默认文件。 第二种风格使用显式的文件句柄。

当使用隐式文件句柄时, 所有的操作都由表 io 提供。 若使用显式文件句柄, io.open 会返回一个文件句柄,且所有的操作都由该文件句柄的方法来提供。

表 io 中也提供了三个 和 C 中含义相同的预定义文件句柄: io.stdin io.stdout io.stderr。 I/O 库永远不会关闭这些文件。

除非另有说明, I/O 函数在出错时都返回 nil(第二个返回值为错误消息,第三个返回值为系统相关的错误码)。 成功时返回与 nil不同的值。

1. io:open(filename [, mode])

这个函数用字符串 mode 指定的模式打开一个文件。 返回新的文件句柄。 当出错时,返回 nil加错误消息。

mode的模式如下:

模式

描述

r

以只读方式打开文件,该文件必须存在。

w

打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。

a

以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)

r+

以可读写方式打开文件,该文件必须存在。

w+

打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。

a+

与a类似,但此文件可读可写

b

二进制模式,如果文件是二进制文件,可以加上b

+

号表示对文件既可以读也可以写

当文件不存在时:

模式"r","r+",会提示错误,这两种模式不会自动创建文件。
模式"a","a+","w","w+"都会创建文件

local file = io.open("/Users/jason/Desktop/test.txt")
file:close()

local file = io.open("/Users/jason/Desktop/test.txt","a")
file:close()
--会生成文件test.txt

不同模式下,分别调用读写操作

-1. Mode 为"r",文本可读,不可写

[[
创建本地文件,写入以下内容
test.txt
this is test 1
this is test 2
]]
local file = io.open("/Users/jason/Desktop/iotest.txt",'r')
print(file:write("123", "a"))
file:close()

local file = io.open("/Users/jason/Desktop/iotest.txt",'r+')
print("after write",file:read("*a"))
file:close()
输出为:
nil	Bad file descriptor	9
after write:	test.txt
this is test 1
this is test 2

-2. Mode 为"r+",文件内容保留,新内容从文件头输入,可读

输出为:
file (0x7fff8f4f4030)
after write:	123a.txt
this is test 1
this is test 2

-3. Mode 为"w",打开文件时,会立即删除文件内容(即使不写入内容),用w模式读取的时候返回nil

输出为:
file (0x7fff8f4f4030)
after write:	nil	Bad file descriptor	9

-4. Mode为"w+",打开文件时,会立即删除文件内容,读时,返回空字符串

file (0x7fff8f4f4030)
after write:

-5. Mode为"a",追加模式(末尾)写入,不可读

file (0x7fff8f4f4030)
after write:	nil	Bad file descriptor	9

文件内容:
this is test 1
this is test 2123a

-6. Mode为"a+",追加模式写入,可读(),但是本地测试读取为空字符串-->?

file (0x7fff8f4f4030)
after write:	

文件内容:
this is test 1
this is test 2123a

结论

  • 代码中我们使用了mode="r"的方式打开文件是由于这种模式要求文件必须存在,如果使用其他模式可能会创建新的文件,而不会返回失败。
  • 我们可以使用mode="r"的特性来自己实现一个检测文件是否存在的函数。
  • 注意取得函数错误信息的方法,便于查找错误原因。
-- 打开存在的文件
local file, msg = io.open("iotest.txt",'r')
if file == nil then
    print(msg)
else
    print("open exist file success :")
    print(file)
end

-- 打开不存在的文件
local ret, errormsg = io.open("iotest2.txt", "r")
print("\nopen don't exist file ret :")
print(ret)
if ret == nil then
    print(errormsg)
end

2. io.close

这个函数等价于file:close(),如果省略参数file的话,那么就表示关闭默认输出文件。函数file:close()也是用来关闭文件的,只是调用形式有所不同。另外当文件描述符被垃圾回收时,对应的文件也会自动关闭,但是这个时间是不确定的。

local file = io.open("/Users/jason/Desktop/iotest.txt","r")
print("\nopen a file:")
print(file)

-- 关闭打开的文件
local ret = io.close(file);
print("\nclose file ret:")
print(ret)

local filenew = io.open("/Users/jason/Desktop/iotest.txt","r")
print("\n\nopen a file again:")
print(filenew)

-- 换种方式再次关闭打开的文件
local retagain = filenew:close();
print("\nclose file again ret:")
print(retagain)

-- 设置并打开默认输出文件
io.output("/Users/jason/Desktop/iotest.txt")
local outret = io.close()
print("\nclose default out file ret:")
print(outret)

-- 设置并打开默认输入文件
io.input("/Users/jason/Desktop/iotest.txt")
local inret = io.close()
print("\nclose default in file ret:")
print(inret)
open a file:
file (0x7fff8f4f4030)

close file ret:
true


open a file again:
file (0x7fff8f4f4030)

close file again ret:
true

close default out file ret:
true
lua: /Users/jason/Desktop/opentest.lua:31: attempt to use a closed file
stack traceback:
	[C]: in function 'io.close'
	/Users/jason/Desktop/opentest.lua:31: in main chunk
	[C]: in ?

总结

  • 保证打开文件和关闭文件成对出现是一个良好的习惯。
  • 由结果可知函数io.close(file)file:close()完全等价,但是当要关闭默认输出文件时,需要选择io.close()
  • 结果中最后一段报错原因就是io.close()只能用来关闭默认的输出文件,不能用关闭默认的输入文件,而默认的输出文件在上面已经关闭过了,如果尝试再次关闭的话,Lua 解释器就会发出报错警告。

3. io.read

从文件中读取内容,还有另一种写法就是file:read(),而题目中的写法相当于对标准输入文件的操作,也就是io.input():read()。这个函数会根据所给的格式来读取内容内容,如果读不到所指定格式的内容则会返回nil,如果不指定读取的格式,则函数会选择*l做为默认的形式读取。

可选的读取方式有:

  • *n:读取一个数字,这是唯一返回数字而不是字符串的读取格式。
  • *a:从当前位置读取余下的所有内容,如果在文件尾,则返回空串""
  • *l:读取下一个行内容,如果在文件尾部则会返回nil
  • number:读取number个字符的字符串,如果在文件尾则会返回nil,如果吧number=0,则这个函数不会读取任何内容而返回一个空串"",在文件尾返回nil
--在文本里写入以下字串测试
[[
2019 11 4 
Not number
1.this is a string
2.this is a test
3.the rain outside the wall 
4.over
]]


local file = io.open("/Users/jason/Desktop/iotest.txt", "r")
if nil == file then
    print("open file readtest.txt fail")
end

--  读取数字
local year = file:read("*n")
local month = file:read("*n")
local day = file:read("*n")
local hour = file:read("*n")

print("year = "..year)
print("month = "..month)
print("day = "..day)
print("hour = "..(hour or "nil"))

-- 读取行
local content = file:read("*l")
print("\ncontent = "..content)

-- 按行读取
local content2 = file:read("*l")
print("content2 = "..content2)

-- 读取0个字节
local zerobyte = file:read(0)
print("\nzerobyte = "..zerobyte)

-- 读取6个字节
local sixbyte = file:read(6)
print("sixbyte = "..sixbyte)

-- 读取所有内容
local readall = file:read("*a")
print("\nreadall = "..readall)

-- 文件结尾读取所有内容
local readallagain = file:read("*a")
print("readallagain = "..readallagain)

-- 文件结尾读取行
local reademptyline = file:read("*l")
if reademptyline == nil then
    print("\nread the end of file")
end

-- 文件尾读取0个字节
local zerobyteagain = file:read(0)
if zerobyteagain == nil then
    print("read the end of file")
end

file:close()
year = 2019
month = 11
day = 4
hour = nil

content = Not number
content2 = 1.this is a string

zerobyte = 
sixbyte = 2.this

readall =  is a test
3.the rain outside the wall 
4.over
readallagain = 

read the end of file
read the end of file

总结

  • 使用函数要注意不同格式的读取返回nil""的情况,注意结尾的判断条件。
  • 几种不同的读取形式在文件结尾处的返回值是不同的,"*a"作用在结尾处返回空字符串"",而"*l""*n"number在结尾处返回nil

4. io.write

将每一个参数写入到文件中(言外之意可以有多个参数),但是参数的类型必须是字符串或者是数字,如果要写入其他类型则需要使用tostring(arg)函数或者string.format()函数,另外这个函数还有一种形式就是file:write(),而题目中这种形式等价于io.output():write()

-- 打开文件
local file = io.open("/Users/jason/Desktop/iotest.txt", "w")
if nil == file then
    print("open file iotest.txt fail")
end

-- 输入字符串
file:write("test io.write\n");

-- 输入数字
file:write(2019)

-- 输入分隔符
file:write(" ")

-- 继续输入数字
file:write(11)
file:write(" ")
file:write(4)
file:write("\n")

-- 继续输入其他类型
file:write(tostring(os.date()))
file:write("\n")
file:write(tostring(file))

-- 关闭文件
file:close()

-- 读取文件并显示
local fileread = io.open("/Users/jason/Desktop/iotest.txt", "r")

local content = fileread:read("*a");
print("file content is : \n")
print(content)

fileread:close()
file content is : 

test io.write
2019 11 4
Mon Nov  4 15:08:04 2019
file (0x7fff8f4f40c8)

5. io:input([file])

打开一个文件,然后把这个文件作为默认的输入文件,如果参数是一个文件名,则会以文本模式打开这个文件,并将这个文件句柄设置为默认输入文件;如果参数就是一个文件描述符,那么这个函数会把这个文件简单的设置为默认的输入文件;如果使用函数时不传参数,那么它将的返回当前的默认文件的描述符。

--文件名
io.input("/Users/jason/Desktop/iotest.txt")
print(io.read("*a"))
io.close()
-- test.txt
-- this is test 1
-- this is test 2

--文件句柄(需要使用可读模式)
local file = io.open("/Users/jason/Desktop/iotest.txt", "r")
print(io.input(file))
print(io.read("*a"))
print(file)
io.close()

-- file (0x7fff8f4f40c8)
-- test.txt
-- this is test 1
-- this is test 2
-- file (0x7fff8f4f40c8)

6. io:output([file])

用文件名调用它时,(以文本模式)来打开该名字的文件, 并将文件句柄设为默认输出文件。 如果用文件句柄去调用它, 就简单的将该句柄设为默认输出文件。 如果调用时不传参数,它返回当前的默认输出文件。
在出错的情况下,函数抛出错误而不是返回错误码。

local file = io.output("/Users/jason/Desktop/iotest.txt",'a+')
print(file)
io.write("1234")
io.close()

-- file (0x7fff8f4f4030)
-- 文件内容:
-- 1234

7. io.popen (prog [, mode])

在额外的进程中启动程序prog,并返回用于prog的文件句柄。通俗的来说就是使用这个函数可以调用一个命令(程序),并且返回一个和这个程序相关的文件描述符,一般是这个被调用函数的输出结果,这个文件打开模式由参数mode确定,有取值"r""w"两种,分别表示以读、写方式打开,默认是以读的方式,和os.execute()相似。

local myfile = io.popen("ls ~/Desktop/luaFile", "r")
if nil == myfile then
    print("open file for ls fail")
end

print("\n======commond ls result:")
-- 读取文件内容
for cnt in myfile:lines() do
    print(cnt)
end

-- 关闭文件
myfile:close()


local secondfile = io.popen("ps aux |grep inet")
if nil == secondfile then
    print("open file for ps fail")
end

print("\n======commond ps result:")
-- 读取文件内容
local content = secondfile:read("*a")
print(content)

-- 关闭文件
secondfile:close()
======commond ls result:
demo1.lua
demo2.lua

======commond ps result:
jason             2334   0.0  0.0  4268096    852 s003  S+    2:16PM   0:00.00 grep inet
jason             2332   0.0  0.0  4268676   1152 s003  S+    2:16PM   0:00.00 sh -c ps aux |grep inet

8. io.tmpfile()

返回一个临时文件的句柄,以可写(实际上也可读)的方式打开并且在程序结束时自动删除。

-- 创建并打开临时文件
local myfile = io.tmpfile()
print("\nfile handle is:")
print(myfile)

-- 向文件中写入内容
myfile:write("name=Jason\n");
myfile:write("age=28\n")
myfile:write("removed file when the program ends \n")

-- 做了许多操作之后
-- ...
-- ...

-- 移动文件指针到开头
myfile:seek("set")

-- 读取文件内容
local content = myfile:read("*a");
myfile:close();


print("\nfile content is:")
print(content)
file handle is:
file (0x7fff8f4f4030)

file content is:
name=Jason
age=28
removed file when the program ends

总结

  • 注意对比函数os.tmpname()io.tmpfile()的相同点和不同点,只用时候要注意,最重要的是os.tmpname()只返回文件名,需要手动打开和关闭,而io.tmpfile()函数实现打开和关闭都是自动的。
  • io.tmpfile()函数打开的文件句柄一旦关闭就无法再打开了,所以在使用完毕之前切勿随意关闭文件。

9. io.type(obj)

检测一个文件描述符obj是否有效,假如obj是一个打开的文件描述符则会返回字符串"file",如果obj是一个关闭的文件描述符则会返回字符串"closed file",而当obj是一个无效的文件描述符时则会返回结果nil

-- 打开文件
local myfile = io.open("iotypeest.txt", "w+")
if myfile == nil then
    print("open file iotypeest.txt fail")
end

print("\nafter open file:")
print("myfile handle status = "..io.type(myfile))

-- 关闭文件
myfile:close()

print("\nafter close file:")
print("myfile handle status = "..io.type(myfile))

-- 随便输入一个文件名
print("\nuse a error file:")
print("error file handle status = "..(io.type(errorfile) or "nil"))
after open file:
myfile handle status = file

after close file:
myfile handle status = closed file

use a error file:
error file handle status = nil

10. io.lines([filename])

提供一个循环迭代器以遍历文件,如果指定了文件名则当遍历结束后将自动关闭该文件;若使用默认文件,则遍历结束后不会自动关闭文件。

local contentfunc = io.lines("/Users/jason/Desktop/iotest.txt")
print("contentfunc is :")
print(contentfunc)

print("\nfirst file content is :")

-- 先手动调用一次
local content = contentfunc()
print(content.."\n")

-- 再手动调用一次
local content = contentfunc()
print(content.."\n")

-- 直接全部迭代调用
print("\nsecond file content is :")
for cnt in io.lines("/Users/jason/Desktop/iotest.txt") do
    print(cnt)
end

-- 使用文件描述符迭代
print("\nthird file content is :")
local myfile = io.open("/Users/jason/Desktop/iotest.txt");
for cnt in myfile:lines() do
    print(cnt)
end

myfile:close()
contentfunc is :
function: 0x7f852e406890

first file content is :
this is test 1

this is test 2


second file content is :
this is test 1
this is test 2

third file content is :
this is test 1
this is test 2

总结

  • 注意函数io.lines(filename)file:lines()的使用有什么不同,实际上后者在函数调用完之后并不会自动关闭文件,代码最后myfie:close()函数的调用没有报错,也说明了这个问题。
  • 由于这个函数是以读的模式打开文件的,所以要求文件必须存在,否则就会报错,所有在使用之前最好先检查一下文件是否存在。

11. io.flush

把用户程序中的缓冲区数据强制写入到文件或内存变量并清空缓冲区。`io.flush()`是作用在默认的输出文件描述符上,相当于`io.output():flush()`,对于其他的通用文件可以使用`file:flush()`或者`io.flush(file)`。

12. file:setvbuf()

- file:setvbuf (mode [, size])

- 设置输出文件缓冲区的模式,`mode`有以下三种方式可选:

  1. "full":满缓冲,冲区为空时,从流读入数据。或当缓冲区满时,向流写入数据。

  2. "line":行缓冲,每次从流中读入一行数据或向流中写入—行数据。

  3. "no":无缓冲,直接从流中读入数据或直接向流中写入数据,而没有缓冲区。

     full&line 的size可以指定缓冲的大小(按字节),忽略size将自动调整为最佳的大小

     ```lua
     --问题是结果为空,并且长度是全部,看起来setbuffer 没效果?
     f = io.open("/Users/jason/Desktop/he.txt","r+")
     f:setvbuf("full",12)
     f:write("nihaomama")
     f:write("heheh")
     result = f:read("*a")
     print ("the result is ",result)
     ```

13. file:flush

向文件写入缓冲中的所有数据

14. file:lines

返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件

for line in file:lines() do 
    body
end

15. file:read

按指定的格式读取一个文件,按每个格式函数将返回一个字串或数字,如果不能正确读取将返回nil,若没有指定格式将指默认按行方式进行读取

*n: 读取一个数字

*a: 从当前位置读取整个文件,若为文件尾,则返回空字串

*l: [默认]读取下一行的内容,若为文件尾,则返回nil

16.#### file:write

按指定的参数格式输出文件内容,参数必须为字符或数字,若要输出其它值,则需通过tostring或string.format进行转换

17. file:seek([whence][,offset])

功能:设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息

whence:

  "set": 从文件头开始

  "cur": 从当前位置开始[默认]

  "end": 从文件尾开始

  offset:默认为0

当 seek 成功时,返回最终从文件开头计算起的文件的位置。 当 seek 失败时,返回 **nil** 加上一个错误描述字符串。

whence 的默认值是 "cur", offset 默认为 0 。 因此,调用 file:seek() 可以返回文件当前位置,并不改变它; 调用 file:seek("set") 将位置设为文件开头(并返回 0); 调用 file:seek("end") 将位置设到文件末尾,并返回文件大小。

```lua
--向iotest写入
[[
1.this is a string
2.this is a test
]]
local file = io.open("/Users/jason/Desktop/iotest.txt", "r+")
print(file:seek("end"))
print(file:seek("set"))
print(file:seek())
print(file:seek("cur", 10))
print(file:seek("cur"))
print(file:read(1))
print(file:seek("cur"))
file:write("123")
print(file:read("*a"))
file:close()
```

```
输出为:
35
0
0
10
10
a
11
ring
2.this is a test
```

18. file:close()

关闭 file。 注意,文件在句柄被垃圾回收时会自动关闭, 但是多久以后发生,时间不可预期的。

Knowledge, like candlelight, can illuminate a person and countless people.