File IO - HimxhMC/HimDSL GitHub Wiki
文件 I/O / File I/O
为什么需要文件 / Why Files
脚本的变量在服务器重启后消失。文件是唯一的持久化手段——保存配置、记录数据、输出日志。
Variables vanish on server restart. Files are the only persistence — save config, record data, produce logs.
HimDSL 提供两组文件 API:
HimDSL offers two groups:
-
句柄式:
fopen/fread/fwrite/fclose——适合大文件、流式处理。 -
便捷式:
freadlines/fwritelines——一次读写全部行。 -
Handle-based:
fopen/fread/fwrite/fclose— for streaming. -
Convenience:
freadlines/fwritelines— read/write all lines at once.
日常脚本 90% 用便捷式。
90% of daily scripts use the convenience form.
路径规则 / Path Rules
相对路径基于插件数据目录(plugins/HimDSL/):
Relative paths resolve against the plugin data folder (plugins/HimDSL/):
freadlines("config.txt") // plugins/HimDSL/config.txt
freadlines("data/players.txt") // plugins/HimDSL/data/players.txt
绝对路径也可以,但会绕过安全目录检查:
Absolute paths work but bypass the safety directory check:
freadlines("/tmp/whatever.txt") // 允许,但小心
相对路径不能逃逸数据目录:
Relative paths cannot escape the data folder:
freadlines("../other/file.txt") // ✗ 报错
这个限制是保护,防止脚本意外读写系统文件。日常脚本用相对路径。
This restriction is protective — prevents accidental access to system files. Use relative paths.
便捷读取:freadlines / Convenience Read
读取整个文件为字符串列表,每行一个元素:
Reads the entire file as a list of strings, one per line:
var lines = freadlines("data.txt");
int count = lines.size();
for (int i = 0; i < count; i = i + 1) {
runcmd("CONSOLE", "say " + lines.get(i));
}
文件不存在会报错。先检查:
Missing files error. Check first:
if (!fexists("data.txt")) {
runcmd("CONSOLE", "say 文件不存在");
return;
}
var lines = freadlines("data.txt");
便捷写入:fwritelines / Convenience Write
把字符串列表写入文件,一行一个元素:
Writes a list of strings, one per line:
fwritelines("output.txt", {
"第一行",
"第二行",
"第三行"
});
这会覆盖文件——已有的内容全部丢失。没有追加模式。要追加内容,先读出来再写回去:
This overwrites the file — existing content is lost. No append mode. To append, read first and write back:
var lines = vector();
if (fexists("log.txt")) {
lines = freadlines("log.txt");
}
lines.add("新的一行");
fwritelines("log.txt", lines);
注意这个模式在文件很大时会很慢。不要用它做日志——日志应用句柄式的 fopen("log.txt", "a")。
This pattern is slow for large files. Do not use it for logging — use handle-based fopen("log.txt", "a").
句柄式读写 / Handle-Based I/O
fopen 打开文件,返回一个数字句柄:
fopen opens a file and returns a numeric handle:
int f = fopen("data.txt", "r"); // 只读
int f = fopen("data.txt", "w"); // 覆盖写
int f = fopen("data.txt", "a"); // 追加写
三种模式:
Three modes:
| 模式 | 含义 |
|---|---|
"r" |
读取(文件必须存在) |
"w" |
覆盖写(文件不存在则创建) |
"a" |
追加写(文件不存在则创建) |
返回的句柄一定要 fclose,否则文件保持占用:
Always fclose the handle, or the file stays open:
int f = fopen("log.txt", "a");
fwrite(f, "新日志行\n");
fclose(f);
fwrite 追加日志 / fwrite for Logging
"a" 模式配合 fwrite 是正确的日志做法:
"a" mode with fwrite is the correct logging pattern:
int f = fopen("actions.log", "a");
fwrite(f, player.name + " 使用了钻石\n");
fclose(f);
每次追加一行,不影响已有内容。
Each call appends a line, without touching existing content.
fread 读取全部 / fread Reads Everything
fread(f) 返回文件剩余内容的单个字符串:
fread(f) returns the remaining content as one string:
int f = fopen("data.txt", "r");
string content = fread(f);
fclose(f);
// content 里可能包含多个 \n
小文件用 freadlines 更方便。fread 主要用于处理二进制或大文件。
For small files, freadlines is easier. Use fread for binary or huge files.
存在与删除 / Existence and Deletion
bool exists = fexists("data.txt");
bool deleted = fdelete("old.txt"); // 返回是否成功删除
fdelete 删除失败时不报错,返回 false。删除前先 fexists 检查更稳妥。
fdelete does not error on failure; returns false. Check fexists first for safety.
一个完整的保存/读取例子 / A Complete Save/Load Example
把玩家等级保存到文件:
Save a player's level to a file:
void saveLevel(string playerName, int level) {
fwritelines("levels.txt", { playerName + "=" + level });
}
int loadLevel(string playerName) {
if (!fexists("levels.txt")) return 0;
var lines = freadlines("levels.txt");
if (lines.size() == 0) return 0;
var parts = lines.get(0).split("=");
if (parts.size() != 2) return 0;
return toInt(parts.get(1));
}
int main() {
saveLevel("Steve", 30);
int level = loadLevel("Steve");
runcmd("CONSOLE", "say Steve 等级 " + level);
}
这个例子只保存一个玩家。多玩家需要更多行——fwritelines 会覆盖,所以要先读全部、改一行、再写回。或者用 JSON(见《JSON》一章)。
This stores one player. Multiple players need multiple lines — since fwritelines overwrites, read all, modify one, write back. Or use JSON (see the JSON chapter).
常见错误 / Common Mistakes
错误一:路径逃逸
freadlines("../config.yml"); // ✗ 报错
相对路径不能离开插件目录。
错误二:把 fwritelines 当追加
fwritelines("log.txt", {"new line"}); // ✗ 覆盖全部
追加要用 fopen("log.txt", "a")。
错误三:忘记 fclose
int f = fopen("data.txt", "r");
string s = fread(f);
// 忘记 fclose(f) → 文件一直开着
错误四:以 "r" 模式打开不存在的文件
int f = fopen("nope.txt", "r"); // ✗ 报错
先 fexists 检查,或用 "a"(追加模式会创建文件)。
Mistake 1: Path escape
Relative paths stay inside the plugin directory.
Mistake 2: Treating fwritelines as append
It overwrites. Use fopen("...", "a") for append.
Mistake 3: Forgetting fclose
The file stays open.
Mistake 4: Opening a missing file with "r"
Check fexists, or use "a" (creates the file).
小结 / Summary
-
相对路径基于
plugins/HimDSL/,不能逃逸。 -
便捷式:
freadlines/fwritelines,适合小文件。 -
句柄式:
fopen/fread/fwrite/fclose,适合追加、大文件。 -
fopen模式:"r"/"w"/"a"。 -
fwritelines会覆盖,追加要用"a"模式。 -
存在与删除:
fexists/fdelete。 -
句柄用完
fclose。 -
Relative paths resolve against
plugins/HimDSL/and cannot escape. -
Convenience:
freadlines/fwritelinesfor small files. -
Handle-based:
fopen/fread/fwrite/fclosefor appends and large files. -
fopenmodes:"r"/"w"/"a". -
fwritelinesoverwrites; append with"a". -
Existence and deletion:
fexists/fdelete. -
Close handles with
fclose.
下一步 / Next Steps
下一章 [命令与 sleep / Commands-and-Sleep] 讲 runcmd 的细节和 sleep 的正确用法。
Next, [命令与 sleep / Commands-and-Sleep] covers runcmd details and correct sleep usage.