强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Nim 完全指南 / 15 文件与系统 I/O

第 15 章:文件与系统 I/O

15.1 文件读写

15.1.1 快速读写

# 读取整个文件
let content = readFile("data.txt")
echo content

# 写入文件
writeFile("output.txt", "Hello, Nim!\n")

# 追加写入
let f = open("output.txt", fmAppend)
f.writeLine("Appended line")
f.close()

15.1.2 逐行读取

# 逐行处理大文件
let f = open("data.txt")
defer: f.close()

var line: string
while f.readLine(line):
  echo line

15.1.3 格式化读写

var f = open("numbers.txt", fmWrite)
for i in 1..10:
  f.writeLine($i)
f.close()

# 读取所有行到序列
let lines = readFile("numbers.txt").splitLines()
for line in lines:
  if line.len > 0:
    echo parseInt(line)

15.2 文件系统操作

import std/os

# 文件信息
echo fileExists("data.txt")        # true/false
echo dirExists("src")              # true/false
echo getFileSize("data.txt")       # 字节数
echo getLastModificationTime("data.txt")

# 创建/删除
createDir("tmp/nested")            # 递归创建目录
removeDir("tmp")                   # 递归删除目录
removeFile("output.txt")

# 复制/移动
copyFile("source.txt", "dest.txt")
moveFile("old.txt", "new.txt")

# 遍历目录
for entry in walkDir("src"):
  echo entry.kind, ": ", entry.path
  
for file in walkDirRec("src"):
  echo file

# 临时文件
let (tmpFile, tmpHandle) = createTempFile("myapp", ".txt")
tmpHandle.writeLine("Temp data")
tmpHandle.close()

15.3 文件路径操作

import std/os

let path = "/home/user/documents/report.pdf"

echo extractFilename(path)    # "report.pdf"
echo parentDir(path)          # "/home/user/documents"
echo splitFile(path)          # ("/home/user/documents", "report", ".pdf")
echo splitPath(path)          # ("/home/user", "documents/report.pdf")
echo lastPathPart(path)       # "report.pdf"

echo "/home" / "user" / "file.txt"  # "/home/user/file.txt"
echo "file.txt".changeFileExt(".md") # "file.md"
echo expandTilde("~/documents")      # 完整路径
echo getCurrentDir()                 # 当前工作目录
echo getAppFilename()                # 可执行文件路径

15.4 环境变量

import std/os

# 读取环境变量
echo getEnv("HOME")
echo getEnv("PATH")
echo getEnv("MY_VAR", "default_value")  # 带默认值

# 设置环境变量
putEnv("MY_APP_DEBUG", "true")

# 检查是否存在
echo existsEnv("HOME")  # true

# 遍历所有环境变量
for key, value in envPairs():
  echo &"{key}={value}"

15.5 命令行参数

import std/os

# 原始参数
echo paramCount()           # 参数个数
echo paramStr(0)            # 程序名
echo commandLineParams()    # 所有参数(不含程序名)

# 高级参数解析
import std/parseopt

var
  filename = ""
  verbose = false
  output = ""

for kind, key, val in getopt():
  case kind
  of cmdArgument:
    filename = key
  of cmdLongOption, cmdShortOption:
    case key
    of "verbose", "v": verbose = true
    of "output", "o": output = val
  of cmdEnd: discard

echo &"File: {filename}, verbose: {verbose}, output: {output}"

15.6 进程与系统调用

import std/osproc

# 执行外部命令
let (output, exitCode) = execCmdEx("ls -la")
echo output
echo "Exit code: ", exitCode

# 捕获输出
let result = execProcess("echo hello")
echo result

# 创建子进程
let p = startProcess("sleep", args = ["5"])
echo "PID: ", p.processID()
p.close()

15.7 实战示例

🏢 场景:日志文件轮转

import std/[os, times, strformat]

type RotatingLogger = object
  basePath: string
  maxSize: int64
  maxFiles: int

proc newRotatingLogger(basePath: string, maxSize = 1024*1024, maxFiles = 5): RotatingLogger =
  RotatingLogger(basePath: basePath, maxSize: maxSize, maxFiles: maxFiles)

proc rotateIfNeeded(rl: var RotatingLogger) =
  if not fileExists(rl.basePath):
    return
  if getFileSize(rl.basePath) < rl.maxSize:
    return
  
  for i in countdown(rl.maxFiles - 1, 1):
    let src = &"{rl.basePath}.{i}"
    let dst = &"{rl.basePath}.{i + 1}"
    if fileExists(src):
      if i + 1 >= rl.maxFiles:
        removeFile(src)
      else:
        moveFile(src, dst)
  
  moveFile(rl.basePath, rl.basePath & ".1")

proc log(rl: var RotatingLogger, msg: string) =
  rl.rotateIfNeeded()
  let f = open(rl.basePath, fmAppend)
  let ts = now().format("yyyy-MM-dd HH:mm:ss")
  f.writeLine(&"[{ts}] {msg}")
  f.close()

var logger = newRotatingLogger("app.log", maxSize = 1024*1024, maxFiles = 5)
for i in 1..100:
  logger.log(&"Log entry {i}")

🏢 场景:配置文件管理

import std/[os, json, options, strutils]

type
  Config = object
    data: JsonNode

proc loadConfig(path: string): Option[Config] =
  if not fileExists(path):
    return none(Config)
  try:
    let content = readFile(path)
    some(Config(data: parseJson(content)))
  except:
    none(Config)

proc saveConfig(cfg: Config, path: string) =
  writeFile(path, cfg.data.pretty())

proc get(cfg: Config, path: string, default = ""): string =
  var node = cfg.data
  for part in path.split("."):
    if node.hasKey(part):
      node = node[part]
    else:
      return default
  if node.kind == JString: node.getStr()
  elif node.kind == JInt: $node.getInt()
  else: default

# 使用
var cfg = loadConfig("config.json").get(Config(data: %*{}))
cfg.data = %*{
  "server": {"host": "localhost", "port": 8080},
  "debug": true
}
saveConfig(cfg, "config.json")
echo cfg.get("server.host")  # localhost
echo cfg.get("server.port")  # 8080

本章小结

操作函数模块
快速读写readFile, writeFilesystem
逐行读取f.readLine()system
文件操作fileExists, copyFile, moveFileos
目录操作createDir, walkDir, walkDirRecos
路径操作extractFilename, parentDir, /os
环境变量getEnv, putEnvos
命令行paramStr, getoptos, parseopt
进程execCmdEx, startProcessosproc

练习

  1. 编写一个文件搜索工具,支持通配符和正则
  2. 实现一个简单的 .env 文件解析器
  3. 创建一个带进度显示的文件复制工具
  4. 实现一个目录同步工具

扩展阅读


上一章:模块与包管理 | 下一章:并发编程