一、前言
Lua 语言是以一个脚本存在,所以他自身不会提供太多和外部交互的机制。需要交互则由宿主提供或是由外部库。
接下来分享下如何使用以 iso c 作为宿主,进行标准库的 io 操作。
二、io.input、io.output
1、io.input
io.input(filename)
会以只读模式打开指定文件,并将文件设置为当前输入流。后续的输入都将来自该文件,除非重新调用 io.input
。
2、io.ouput
io.ouput(filename)
会以只写模式打开指定文件,并将文件设置为当前输出流。后续的输出都会给到该文件,除非重新调用 io.ouput
。
3、io.input 和 io.output 影响
io.input
、 io.output
的设置会改变当前的输入输出流
二、io.write
读取任意数量的字符串或者数字,将内容写入当前输出流
性能优化点
如果需要写入多个参数,应该使用 io.write
多参数传参,而不要自行拼凑,下面的代码是等价的
-- 这样性能好,避免不必要的连接动作
io.write("jiang", "peng", "yong") --> jiangpengyong-- 浪费资源效率低
io.write("jiang" .. "peng" .. "yong") --> jiangpengyong
格式化
在输出中,一样可以使用 string.format
进行格式化,只要写入最终结果合法即可。
io.write("sin(3) = ", math.sin(3), "\n")
-- 如果需要格式化,则需要调用 string.format
io.write(string.format("sin(3) = %.4f", math.sin(3)), "\n")
print 和 io.wirte 区别
两者都可以做到输出到控制台的作用。
print:
- 会在最终输出结果中添加诸如制表符或换行符的额外内容
- 只能使用标准输出
- 会为参数自动调用 tostring ,可能会带来一些奇怪的 bug
io.write
- 输出很纯粹,没有添加任何内容
- 允许对输出进行重定向
如果只是调试,使用 print 会方便很多
三、io.read
从当前输入流读取内容,io.read("mode")
mode 可选参数,read 都是针对当前流当前位置(可以通过下面的例子体会)。
符号 | 描述 |
---|---|
“a” | 读取整个文件 |
“l” | 读取下一行(丢弃换行符) (默认参数) |
“L” | 读取下一行(保留换行符) |
“n” | 读取一个数值,如果下一个可读的内容不是数值,则会获取到 nil |
num | 以字符串读取 num 个字符 |
io.input(rootPath .. "一件小事.txt")
print(io.read("l")) -- 读取一行的内容,内容太多,自行运行程序
article = io.read("a") -- 会接着上一个读取的位置,继续往下读,将全文读取完
print(article)
Lua 对长字符串处理很高效,所以可以考虑将整个文件读取出来后,进行处理。当然内存的消耗是不可避免的。
article = io.read("a")
change = string.gsub(article, "我", "*")
print(change)
如果已经到输入流末尾,进行读取,除了 " a " 读取为 空字符串,其他都返回 nil
print("io.read(\"a\"): ", io.read("a")) --> io.read("a"): (空字符串)
print("io.read(\"l\"): ", io.read("l")) --> io.read("l"): nil
print("io.read(\"L\"): ", io.read("L")) --> io.read("L"): nil
print("io.read(\"n\"): ", io.read("n")) --> io.read("n"): nil
print("io.read(9): ", io.read(9)) --> io.read(9): nil
1、遍历输入流方式
1-1、可以通过 io.read 返回 nil 进行判断
io.input(rootPath .. "一件小事.txt")
io.output(rootPath .. "一件小事-copyByReadLine")
for count = 1, math.huge dolocal line = io.read("L")if line == nil thenbreakendio.write(string.format("%6d ", count), line)
end
1-2、可以通过 io.lines 进行
io.lines(filename, ...)
返回一个从流中不断读内容的迭代器。
io.lines(filename, …) 相当于 file:lines(…) 。 即后面的可边参数是给到文件使用的,具体这种写法在后续的文章会进行分享,先有一个印象。
可以不传递文件名,此时则相当于使用当前的文件流,并且操作完之后不会自动关闭流。
io.input(rootPath .. "一件小事.txt")
io.output(rootPath .. "一件小事-copyByLines")local count = 0
for line in io.lines() docount = count + 1io.write(string.format("%6d ", count), line, "\n")
end
运行效果和上面一样
进行传递文件名,则 io.lines 的操作会作用于该文件上,并且在迭代完之后会进行关闭文件流。
io.output(rootPath .. "numberAndString-copyByLinesFilename.txt")
local count = 0
for line in io.lines(rootPath .. "number.txt") docount = count + 1io.write(string.format("%6d ", count), line, "\n")
end
效果是一样的,只是读取的文件已经变为 “number.txt”
还可以在可变参数中设置参数,可以设置的参数和 io.read
一致
可以使用 read 的 num 形式
io.output(rootPath .. "numberAndString-copyByLinesBlock.txt")local count = 0
for line in io.lines(rootPath .. "number.txt", 2) docount = count + 1io.write(line)
end
可以使用 read 的 n 形式
io.output(rootPath .. "numberAndString-copyByLinesNumber.txt")local count = 0
for line in io.lines(rootPath .. "number.txt", "n") docount = count + 1io.write(line)
end
2、对文件进行排序
思路是将文件按行读入存入序列,将序列进行排序。本质是序列排序
io.input(rootPath .. "一件小事.txt")
io.output(rootPath .. "一件小事-copyForSort")
local lines = {}
local count = 0
for line in io.lines() docount = count + 1-- 因为 table 是从 1 开始的lines[#lines + 1] = line
end
table.sort(lines)
for _, v in ipairs(lines) doio.write(v, "\n")
end
3、io.read(“n”)
用于读取一个数值,如果跳过了空格后,仍然不能从当前位置读取到数值(由于错误格式或到了文件末尾),则返回 nil 。
值得注意的是,如果无法正常读取一个数值时,不会挪动当前位置。举个例子:
文件 numberAndString.txt
内容
j 10 20 30
40 50 60
70
进行读取,会发现前两次读取数值失败返回 nil
,因为内容 j
不是数值,最后一次进行内容读取才正常。说明读取失败并不会消耗流内容。
io.input(rootPath .. "numberAndString.txt")
print("number: ", io.read("n"), io.read("n"), io.read(1)) --> number: nil nil j
4、io.read(num)
可以从输入流中读取 n 个字符,如果无法读取到任何字符(处于文件末尾),则返回 nil,否则返回一个流中最多 n 个字符组成的字符串。
可以用 io.read(0)
进行判断是否已经到达了文件末尾。 如果仍有数据可供读取,则会返回一个空字符串,没有则返回 nil ,并且这样也不会有任何流内容被消耗。
io.input(rootPath .. "一件小事.txt")
print(io.read(0)) --> (空字符串)
--- 将整个文章读取,文件位置就到了末尾
io.read("a")
print(io.read(0)) --> nil
可以使用该函数,做到类似 java、2 kotlin 中的分块拷贝。
io.input(rootPath .. "一件小事.txt")
io.output(rootPath .. "一件小事-copyByReadNum")
while true dolocal block = io.read(2 ^ 13) -- 8kif block == nil thenbreakendio.write(block)
end
5、指定多个读取参数
可以在 io.read()
中放入多个值,函数会根据每个参数进行返回结果
io.input(rootPath .. "number.txt")
while true don1, n2, n3 = io.read("n", "n", "n")if n1 == nil thenbreakendprint(n1, n2, n3)
end--> 10 20 30
--> 40 50 60
--> 70 80 90
--> 100 nil nil
四、io.open
io.open(filename, mode)
打开一个文件,仿造 c 中 fopen
这样方式打开,就可以持有文件的句柄,可以对多个文件进行操作,而不在局限于之前只能针对当前的文件流。
mode 可选以下值
mode | 描述 |
---|---|
r | 只读 |
w | 只写(可以用来删除文件中原有的内容) |
a | 追加 |
b | 以二进制打开 |
1、io.open 发生异常
如果发生异常,函数会返回三个值 [nil 错误信息 错误码]
print(io.open("notExistFile.txt")) --> nil notExistFile.txt: No such file or directory 2
使用 assert
检查错误
固定模式是使用如下
local file = assert(io.open(filename, mode))
如果 io.open
执行失败,错误信息会作为函数 assert
的第二个参数传入(这里就有多值返回多值入参),之后函数 assert
会将错误信息展示出来。 举个例子
local file = assert(io.open("notExistFile.txt", "r"))
assert(v, message)
在参数v
为false
的时候(即v
为nil
或false
),会抛出内容为message
的错误。
2、write、read 方法
和 io.write
、 io.read
函数相似,只需要在打开文件后就可以操作了,但是需要用冒号进行使用。
冒号的调用,在后续的文章会分享。可以简单理解为调用对象的一个方法。
local file = io.open(rootPath .. "一件小事.txt", "r")
local t = file:read("a")
--- 要进行关闭
file:close()
print(t)
3、切换当前输入流、输出流
io.input
、 io.output
调用无参函数时,则使用当前的输入流。如果带上参数 io.input(fileHandle)
、 io.output(fileHandle)
则可以设置当前的输入流。
io.input(rootPath .. "一件小事.txt", "r")
-- 获取当前流局柄,即上一行代码的文件流
article1 = io.input()-- 重新打开一个文件,读取内容,关闭
io.input(rootPath .. "names.txt", "r")
print(io.read("l")) --> jiangpengyong
io.input():close()-- 切换为 article1
io.input(article1)
print(io.read("l")) --> 我从乡下跑进京城里,一转眼已经六年了。其间耳闻目睹的所谓国家大事,算起来也很不少;但在我心里,都不留什么痕迹,倘要我寻出这些事的影响来说,便只是增长了我的坏脾气——老实说,便是教我一天比一天的看不起人。-- 关闭 article1
io.input():close()
浅析 io.input
、 io.output
io.input(file, ... )
、 io.output(file, ...)
的返回值都是 file ,而他们的入参也都能接收一个 file 参数。
file 参数可以有两种形式:一种是 string 即传递文件名称,一种是文件句柄。
所以调用这两个函数时,可以获取其返回值(即文件句柄),然后在需要的时候,进行传入进行调用,这样就达到了切换效果。当然不传递参数也是可以的,就是默认继续使用当前文件流。
4、简写方式
io.read(args) 等同于 io.input():read(args)
io.write(args)等同于 io.output():write(args)
五、io.tmpfile
如果创建成功,返回一个操作临时文件的句柄,是以 读/写 模式打开。
当程序运行结束,则会自动删除该文件。
tmpFile = io.tmpfile()
tmp:write("jiangpengyong")
六、预设了三个流句柄
io.stdin
、 io.stdout
、 io.stderr
1、io.stdin
termial 会进入交互模式,让用户输入后,接收内容
readNum = io.stdin:read("n")
print("num", readNum)
2、io.stdout
输出到控制台
io.stdout:write("jiang", "peng", "yong!!!") --> jiangpengyong!!!
3、io.stderr
会在错误流中展示
io.stderr:write("error message.")
七、file:flush()
将缓存写入文件
-- 将当前输出流缓存写入文件
io.flush()
-- 等价于
io.output():flush()-- 将文件缓存写如文件
file:flush()
八、file:setvbuf()
设置流的缓存模式,有以下几种模式
模式 | 描述 |
---|---|
“no” | 无缓冲 |
“full” | 在缓冲区满时或者显示刷新文件时,才写入数据 |
“line” | 输出一直被缓冲直到遇到换行符或从一些特定文件(例如终端设备)中读取到了数据 |
对于 “full” 和 “line” 两种模式,还支持第二个参数,用于指定缓冲区大小。
file = io.open(rootPath .. "outputBuf.txt", "w")
-- 设置缓存模式
file:setvbuf("no")
file:close()
值得注意
预设句柄 io.stderr
是不被缓冲的。
预设句柄 io.stdout
按行进行缓冲,所以如果写入了不完整行的时候,可能需要进行刷新流(flush)才会看到。
九、file:seek(whence, offset)
用来获取和设置文件的当前位置
参数:
- whence: 指定如何使用偏移的字符串,有如下取值
whence | 描述 |
---|---|
“set” | 相对于文件开头的偏移 |
“cur” | 相对于文件当前位置的偏移(默认值) |
“end” | 相对于文件尾部的偏移 |
- offset: 偏移量,默认值 0
返回值
无论使用那种 whence ,函数都以单位为字节,返回当前新位置在流中相对于文件开头的偏移。
使用
file:seek()
并不会改变当前文件流的位置,只会返回当前流位置
file:seek("set")
返回 0 ,并会重置到文件开头
file:seel("end")
返回文件的长度,并会重置到文件结尾
file = io.open(rootPath .. "一件小事.txt", "r")
file:read("l")
print("file:seek(): " .. file:seek()) --> file:seek(): 304
print("file:seek(\"set\"): " .. file:seek("set")) --> file:seek("set"): 0
print("file:seek(\"end\"): " .. file:seek("end")) --> file:seek("end"): 3079
print(file:read("l")) --> nil
十、os.rename(oldname, newname)
用于文件重命名
-- 创建文件
local file = io.open(rootPath .. "original.txt", "w")
file:write("江澎涌")
file:close()
-- 重命名
print(os.rename(rootPath .. "original.txt", rootPath .. "rename.txt"))
十一、os.remove(filename)
文件删除 filename
-- 创建文件
local file = io.open(rootPath .. "delete.txt", "w")
file:write("江澎涌")
file:close()
-- 删除文件
os.remove(rootPath .. "delete.txt")
十二、调用系统命令
1、os.exit(code, close)
终止程序执行
参数:
- 第一个参数:可选,bool,程序是否成功运行;当为数值(0 也表示执行成功)或一个布尔值(true 表示执行成功)
- 第二个参数:可选,bool,当值为 true 时会关闭 Lua State 并调用所有析构器释放所占用的内存(这种终止方式并非必要,因为大多数操作系统会在进程退出时释放其占用的所有资源)。
os.exit(1, true)
2、os.getEnv(string)
获取某个环境变量,如果获取不到则会返回 nil
print(os.getenv("HOME")) --> /Users/jiangpengyong
print(os.getenv("JIANGPENGYONG")) --> nil
3、os.execute(command)
执行系统命令,等价于 C 语言中的函数 system 。
参数: 需要执行的命令字符串
返回值:
-
第一个返回值:bool,true 表示程序成功运行完成
-
第二个返回值:string,会有以下
字符串 | 描述 |
---|---|
“exit” | 表示程序正常运行结束 |
“signal” | 表示因信号而中断 |
- 第三个返回值:数值,返回状态(若该程序正常终结)或者终结该程序的信号代码。
-- 创建一个目录
print(os.execute("mkdir " .. rootPath .. "createByExecute")) --> true exit 0
-- 创建一个文件
print(os.execute("touch " .. rootPath .. "createByExecute/jiangpengyong.txt")) --> true exit 0
运行结果
4、io.popen(“prog”, “mode”)
和 os.execute
一样,该函数运行一条系统命令。
但该函数可以重定向命令的输入/输出,从而使得程序可以向命令中写入或从命令的输出中读取。
参数:
- prog:要运行的命令
- mode:“r” 读取(默认),“w” 写入
返回值: file
读取文件目录下的所有文件
local f = io.popen("ls /Users/jiangpengyong/Desktop/code/Lua/lua_study_2022/io", "r")
local dir = {}
for entry in f:lines() dodir[#dir + 1] = entry
end
for i, v in ipairs(dir) doprint(i, "-->", v)
end1 --> createByExecute.txt
2 --> io.lua
3 --> jiangpengyong.txt
4 --> names.txt
5 --> number.txt
6 --> outputBuf.txt
7 --> rename.txt
8 --> std
9 --> 一件小事-copyByLines
10 --> 一件小事-copyByReadLine
11 --> 一件小事-copyByReadNum
12 --> 一件小事-copyForSort
13 --> 一件小事.txt
十三、io.type(obj) 判断对象是否为有效文件类型
当 obj
是可用的 file 时,则会返回 file
(字符串)
当 obj
是已经关闭的 file 时(调用了 close
),则会返回 closed file
(字符串)
当 obj
不是一个 file
类型对象,则会返回 nil
(字符串)
local file = io.open(rootPath .. "一件小事.txt", "r")
print(io.type(file)) --> file
file:close()
print(io.type(file)) --> closed file
print(io.type("jiangpengyong")) --> nil
十四、小结
本章主要是分享一些 io 操作的 api 和使用,现在再来回顾一下
io 相关的操作
api | 返回值 | 描述 |
---|---|---|
io.input(file or filename) | file | 设置当前输入流,空参数时则是获取当前在用的输入流 |
io.output(file or filename) | file | 设置当前输出流,空参数时则是获取当前在用的输出流 |
io.write(…) | 正对当前输出流写入内容,相当于 io.output():write(···) | |
io.read(…) | 读取当前输入流的内容,等同于 io.input():read(···) | |
io.open(filename, mode) | file | 以 mode 形式打开 filename 文件 |
io.close(file) | void | 关闭句柄 file |
io.tmpfile() | file | 会创建一个临时文件,在程序运行结束时会自动删除 |
io.flush() | void | 刷新当前输出流,等同于 io.output():flush() |
io.lines(filename, …) | 迭代器 | 按行返回文件 filename 中的内容迭代器,等同于 file:lines(...) |
io.type(obj) | string | 检测 obj 是否为一个有效的 file |
io.popen(prog, mode) | file | 运行系统的命令,但是会返回 file 可以进行对应的输入或输出操作 |
io.stdin | 可以从终端中获取输入流的内容 | |
io.stdout | 可以将内容输出到终端 | |
io.stderr | 将错误输出错误流中展示 |
file 相关的操作
在 io 中的操作,如果第一个参数是 file 的话,则可以使用冒号方式调用,即
io.close(file) --- 也可以使用file:close()
api | 返回值 | 描述 |
---|---|---|
file:flush() | void | 刷新文件流 |
file:lines(…) | 迭代器 | 按行返回文件中的内容迭代器 |
file:read(…) | 读取当前文件流的内容 | |
file:write(…) | void | 将参数写入到文件流 |
file:seek(whence, offset) | number | 获取和设置文件的当前位置 |
file:setvbuf(mode, size) | void | 设置流的缓存模式 |
file:close() | void | 关闭文件 |
os 相关的操作
api | 返回值 | 描述 |
---|---|---|
os.rename(oldname, newname) | nil or string | 将文件重命名 |
os.remove(filename) | nil or string | 删除文件 |
os.exit(code, close) | number | 退出程序 |
os.getenv(varname) | nil or string | 获取环境变量 |
os.execute(command) | bool string number | 执行系统命令 |
十五、写在最后
Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)
本章相关代码传送门
公众号搜索 “江澎涌” 可以更快的获取到后续的更新文章