处理文件的函数由File模块提供。
要读取整个文件,使用File.read/1。要写入文件,使用File.write/2。
该模块还提供用于复制、删除、重命名等操作的文件函数。它们的名称与 UNIX 中的对应命令相似,例如:
File.cp/3:复制文件。File.rm/1:删除文件。File.rename/2:重命名或移动文件。File.mkdir/1:创建目录。File.cwd/0:获取当前工作目录。上面提到的 File 模块中的所有函数都有一个带!的变体,它抛出错误,而不是返回错误元组(例如File.read!/1)。如果你不打算处理文件不存在、权限不足这类错误,就用这个变体。
File.read("does_not_exist")
# => {:error, :enoent}
File.read!("does_not_exist")
# => ** (File.Error) could not read file "does_not_exist": no such file or directory
# (elixir 1.10.4) lib/file.ex:353: File.read!/1
每次用File.write/2写入文件时,都会打开一个文件描述符,并启动一个新的 Elixir 进程。因此,应避免在循环中使用File.write/2写入文件。
改用File.open/2打开文件。File.open/2的第二个实参是一个模式列表,用来指定你是想以读取还是写入的方式打开文件。
常用的模式有:
:read:以读取方式打开,文件必须存在:write:以写入方式打开,文件不存在时会创建,已有内容会被覆盖:append:以写入方式打开,文件不存在时会创建,已有内容会保留完整的模式列表见File.open/2的文档。
File.open/2返回处理该文件的进程的 PID。要读写该文件,使用IO模块中的函数,并把这个 PID 作为 IO 设备传入。
文件用完后,用File.close/1把它关闭。
file = File.open!("README.txt", [:write])
# => #PID<0.157.0>
IO.puts(file, "# README")
# => :ok
File.close(file)
# => :ok
用File.read/1读取文件会一次性把整个文件加载到内存中。处理非常大的文件时,这可能是个问题。要高效地处理它们,你可以用File.open/2和IO.read/2逐行读取文件,也可以用File.stream/3把文件当作流来处理。这个流同时实现了Enumerable和Collectable这两种协议,也就是说,它既能用来读取,也能用来写入。
File.stream!("file.txt")
|> Stream.map(&(&1 <> "!"))
|> Stream.into(File.stream!("new_file.txt"))
|> Stream.run()
所有处理文件的函数都需要一个文件路径。文件路径可以是绝对路径,也可以是相对于当前目录的相对路径。要操作路径,使用Path模块中的函数。为了跨平台兼容,请用Path.join/1来创建路径,它会选择适合当前平台的分隔符。
Path.expand(Path.join(["~", "documents", "important.txt"]))
"/home/user/documents/important.txt"