Lua 使用 —— IO 操作

一、前言

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.inputio.output 的设置会改变当前的输入输出流

image.png

二、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

image-2.png

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

运行效果和上面一样

image-1.png

进行传递文件名,则 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”

image-2.png

还可以在可变参数中设置参数,可以设置的参数和 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

image.png

可以使用 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

image-1.png

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"))

image-2.png

assert(v, message) 在参数 vfalse 的时候(即 vnilfalse),会抛出内容为 message 的错误。

2、write、read 方法

io.writeio.read 函数相似,只需要在打开文件后就可以操作了,但是需要用冒号进行使用

冒号的调用,在后续的文章会分享。可以简单理解为调用对象的一个方法。

local file = io.open(rootPath .. "一件小事.txt", "r")
local t = file:read("a")
--- 要进行关闭
file:close()
print(t)

3、切换当前输入流、输出流

io.inputio.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.inputio.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.stdinio.stdoutio.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

运行结果

image-3.png

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吧,码字不易,请多多支持)

本章相关代码传送门

公众号搜索 “江澎涌” 可以更快的获取到后续的更新文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/55302.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【windows】windows上如何使用linux命令?

前言 windows上的bat命令感觉不方便,想在windows上使用linux命令。 有人提供了轮子,本文简单介绍一些该轮子的安装与使用,希望能够帮助到和我有一起需求的网友。 我的答案是busybox。 1.安装busybox.exe 在这个网站上安装busybox busyb…

如何设计一个自动化测试框架

在进行自动化框架设计之前我们先来看两个问题,什么是自动化框架,设计的时候应该注意什么原则,然后该怎么做?本文会以一个web端的UI自动化测试框架设计为例 Python自动化测试:2023最新合集Python自动化测试开发框架【全…

fishing之第四篇使用案例一模拟登陆口

文章目录 一、访问钓鱼平台二、Sending Profiles(发件人邮箱配置)三、User&Groups(接收人邮件列表)四、Landing Pags(钓鱼页面配置)五、Email Templates(邮件内容配置)六、Campa…

【C++】二叉搜索树的模拟实现

🌇个人主页:平凡的小苏 📚学习格言:命运给你一个低的起点,是想看你精彩的翻盘,而不是让你自甘堕落,脚下的路虽然难走,但我还能走,比起向阳而生,我更想尝试逆风…

AI介绍——chat gpt/文心一言/claude/bard/星火大模型/bing AI

AI体验 1. AI 介绍(注册和使用)1.1 Chat GPT1.2 文心一言1.3 Slack 上的 Claude1.3.1 Claude 介绍1.3.2 Claude 使用 1.4 Google的Bard1.4.1 Bard 介绍1.4.2 Bard 使用 1.5 科大讯飞的星火大模型1.5.1 星火大模型 介绍1.5.2 星火大模型 使用 1.6 new bin…

iOS Viper架构(中文版)【看懂这篇就够了】

完整源码地址 一、iOS_Viper iOS的Viper架构,作为一个从业多年的iOS开发者,我个人认为应该要会一点viper 二、前言 viper的设计模式在iOS开发中不流行,甚至是Swift中,也没有用,我认为比较可惜。作为iOSer,当你掌握…

2023年第四届“华数杯”数学建模思路 - 案例:随机森林

## 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 什么是随机森林? 随机森林属于 集成学习 中的 Bagging(Bootstrap AGgregation 的简称) 方法。如果用图来表示他们之…

electron+vue+ts窗口间通信

文章目录 一. 目的二.逻辑分析三. 代码示例 "types/node": "^20.3.1","vitejs/plugin-vue": "^4.1.0","vueuse/electron": "^10.2.1","electron": "^25.2.0","electron-packager":…

激荡十三年,消费金融进入“体验争夺战”的下半场

消费金融行业又开始涌动着变局。 先是一些老玩家悬着的心,终于落地。过去两年,消费金融是蚂蚁集团整改的关键板块。前不久,蚂蚁集团被监管部门开出71.23亿元的“罚单”,市场普遍认为这是利空出尽的信号。 与此同时,竞…

EventBus 开源库学习(三)

源码细节阅读 上一节根据EventBus的使用流程把实现源码大体梳理了一遍,因为精力有限,所以看源码都是根据实现过程把基本流程看下,中间实现细节先忽略,否则越看越深不容易把握大体思路,这节把一些细节的部分再看看。 …

OA办公自动化系统设计与实现(论文+源码)_kaic

摘要 随着信息化建设的日益深入,无论是政府还是企事业单位,部门之间的信息沟通与协调工作越来越重要。人们迫切需要一个能充分利用网络优势,并可以管理企业的各种重要信息的软件平台,利用该平台快速建立自己的信息网络和办公管理系…

C语言案例 按序输出多个整数-03

难度2复杂度3 题目:输入多个整数,按从小到大的顺序输出 步骤一:定义程序的目标 编写一个C程序,随机输入整数,按照从小到大的顺序输出 步骤二:程序设计 整个C程序由三大模块组成,第一个模块使…