三、额外内容-关于文件I/O的写法
很多用户在使用带有require
的脚本时出现了这样的错误:
(aaa
是我自己做的函数库名称。它真的叫aaa
……)
骰娘运行lua文件G:\Dice - 副本\Dice3306860448\plugin\测试.lua失败:G:\Dice - 副本\Dice3306860448\plugin\测试.lua:3: module 'aaa' not found:
no field package.preload['aaa']
no file 'G:\Dice - 鍓湰\Dice3306860448\plugin\aaa.lua'
no file 'G:\Dice - 鍓湰\Dice3306860448\plugin\aaa\init.lua'
no file 'G:\Dice - 鍓湰\Diceki\lua\aaa.lua'
no file 'G:\Dice - 鍓湰\Diceki\lua\aaa\init.lua'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\lua\aaa.lua'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\lua\aaa\init.lua'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\aaa.lua'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\aaa\init.lua'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\..\share\lua\5.4\aaa.lua'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\..\share\lua\5.4\aaa\init.lua'
no file '.\aaa.lua'
no file '.\aaa\init.lua'
no file 'G:\Dice - 鍓湰\Diceki\lua\aaa.dll'
no file 'G:\Dice - 鍓湰\Diceki\lib\aaa.dll'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\aaa.dll'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\..\lib\lua\5.4\aaa.dll'
no file 'C:\Program Files\Java\jdk-17.0.1\bin\loadall.dll'
no file '.\aaa.dll'
或者是在有文件I/O的脚本中,出现这样的问题:
骰娘调用G:\Dice - 副本\Dice3306860448\plugin\DriftingBottles.lua函数throw_bottle失败!
G:\Dice - 副本\Dice3306860448\plugin\DriftingBottles.lua:103: attempt to index a nil value (global 'file')
查看用户手册可以发现这样一条内容。
lua文件的字符编码问题 <来源>
Windows系统一般使用GBK字符集。Dice!支持utf-8及GBK两种字符集的lua文件,在读写字符串时将自动检测utf-8编码并转换。而出现以下情况时,编码并非二者皆可:
- lua文件相互调用或读写其他文本文件,且字符串含有非ASCII字符时,关联文件字符集应保持一致;
- lua文件使用require或os等以文件名为参数的函数,且路径含有非ASCII字符时,必须使用GBK;
出现上述错误的原因就是使用了UTF-8编码的脚本而自己的文件路径含有非ASCII字符,例如中文。
文件I/O也会出现同样的状况,是因为它调用的路径通常是以骰娘的getDiceDir()
开始的。
所以路径和编码总得妥协一个。尤其是在json.lua
本身也是UTF-8编码的、并且安装新脚本也需要和它编码一致的情况下,与其将所有脚本都转码为GBK,还不如干脆将骰娘文件夹移动到纯英文路径。
文件的I/O是涉及脚本本体外的内容的一种形式。它会在指定的路径创建并读写一个文档,通常来说使用txt就足够了。文末会放出读写文件时常用的三个函数,有需要的可以直接复制进相关脚本中。
需要注意的是,在利用含JSON的脚本时,第一次读取的文件无法直接交给json.lua
解码,因为文档里空空荡荡什么都没有,甚至可能连文档都没有。
因此推荐读文件时添加一个类似于初始化的条件判定。可以利用read_file()
读取空文件返回零长字符串的特点加以判定,若满足条件则不进行json.decode()
的工作。
于是,在文件为空的情况下,如果我想实现前面提到的记录用户留言及其他信息,我必须这么做:
function ttbd_write(message,user_qq)
local letter = read_file(ttbd_path) -- 读取
json = require("json")
if #letter==0 then -- 如果是空的
j = {} -- 初始化表“j”
j.usagi = {} -- 初始化数组“j.usagi”
num = 1 -- 数组索引为1
else
j = json.decode(letter) -- 否则进行解码
num = #j.usagi + 1 -- 数组索引为比原有数组的数字多1
end
j.usagi[num] = {} -- 初始化所在数组索引的表
j.usagi[num].message = message -- 进行正常的存表内容工作
j.usagi[num].name = getUserConf(user_qq,"nick","")
j.usagi[num].qq = user_qq -- 最后放弃了记录群号的想法,因为意义不大
j.usagi[num].times = 0
j.usagi[num].isPermanent = false
letter_full = json.encode(j) -- 编码
overwrite_file(ttbd_path,letter_full) -- 覆写文件,由于JSON有特定格式因此无法使用追加写入
end
这样在没有文件的情况下,程序就会跳过解码,直接创建指定文件夹路径和文本文档。
我向骰娘添加的留言是喵喵喵
,因此会写入如下内容:
{"usagi":[{"qq":"1142145792","name":"兔兔零号机","times":0,"message":"喵喵喵","isPermanent":false}]}
而在第二次及以上留言时,则会正常的进行解码-添加新表-编码
的过程,再覆写文件。第二次我的留言是汪汪汪
,文档内容会被修改为如下:
{"usagi":[{"times":1,"isPermanent":false,"qq":"1142145792","message":"喵喵喵","name":"兔兔零号机"},{"times":0,"isPermanent":false,"qq":"1142145792","message":"汪汪汪","name":"兔兔零号机"}]}
其中喵喵喵
这一条已经被我写好的读取代码调用了一次,因此可以看见它的times
值有增加。
以下为文件读、写、覆写函数,可直接复制使用:
-- 用于读文件,参数为文件路径
function read_file(path)
local text = ""
local file = io.open(path, "r") -- 打开了文件读写路径,以读取的方式
if (file ~= nil) then -- 如果文件不是空的
text = file.read(file, "*a") -- 读取内容
io.close(file) -- 关闭文件
end
return text -- 返回读取的内容
end
-- 用于写文件,参数为路径和需要写入的文本
function write_file(path, text)
file = io.open(path, "a") -- 以追加的方式
file.write(file, text) -- 写入内容
io.close(file) -- 关闭文件
end
-- 用于覆写文件,参数与写文件相同
function overwrite_file(path, text)
file = io.open(path, "w") -- 以只写的方式,会将原内容清空后写
file.write(file, text)
io.close(file)
end