查看源代码 IO (Elixir v1.16.2)

处理输入/输出 (IO) 的函数。

此模块中的许多函数都期望一个 IO 设备作为参数。IO 设备必须是 PID 或表示进程的原子。为了方便起见,Elixir 提供了 :stdio:stderr 作为 Erlang 的 :standard_io:standard_error 的快捷方式。

大多数函数期望 chardata。如果给出其他类型,函数将通过 String.Chars 协议(如类型说明中所示)将这些类型转换为字符串。有关 chardata 的更多信息,请参见下面的“IO 数据”部分。

IO 设备

IO 设备可以是原子或 PID。如果是原子,则原子必须是已注册进程的名称。此外,Elixir 提供了两个快捷方式

  • :stdio - :standard_io 的快捷方式,它映射到 Erlang 中当前的 Process.group_leader/0

  • :stderr - Erlang 中提供的命名进程 :standard_error 的快捷方式

IO 设备会维护其位置,这意味着随后对任何读取或写入函数的调用将从设备上次访问的位置开始。可以使用 :file.position/2 函数更改文件的位置。

IO 数据

IO 数据是一种数据类型,在某些情况下可以用作二进制文件的更高效替代方案。

类型为 **IO 数据** 的项是包含字节(0..255 范围内的整数)或嵌套的 IO 数据的二进制文件或列表。该类型是递归的。让我们看一个表示二进制文件 "hello" 的可能 IO 数据的示例

[?h, "el", ["l", [?o]]]

内置的 iodata/0 类型是根据 iolist/0 定义的。IO 列表与 IO 数据相同,但它不允许在顶层使用二进制文件(但二进制文件仍然允许在列表本身中)。

IO 数据的使用案例

IO 数据的存在是因为你经常需要对较小的二进制文件块进行许多追加操作,以便创建一个更大的二进制文件。但是,在 Erlang 和 Elixir 中,连接二进制文件将复制连接的二进制文件到一个新的二进制文件中。

def email(username, domain) do
  username <> "@" <> domain
end

在此函数中,创建电子邮件地址将复制 usernamedomain 二进制文件。现在想象一下,你想在另一个二进制文件中使用生成的电子邮件

def welcome_message(name, username, domain) do
  "Welcome #{name}, your email is: #{email(username, domain)}"
end

IO.puts(welcome_message("Meg", "meg", "example.com"))
#=> "Welcome Meg, your email is: [email protected]"

每次你连接二进制文件或使用插值 (#{}) 时,你都会复制这些二进制文件。但是,在很多情况下,你不需要在创建时完整的二进制文件,而只需要在最后将其打印出来或发送到某个地方。在这种情况下,你可以通过创建 IO 数据来构建二进制文件

def email(username, domain) do
  [username, ?@, domain]
end

def welcome_message(name, username, domain) do
  ["Welcome ", name, ", your email is: ", email(username, domain)]
end

IO.puts(welcome_message("Meg", "meg", "example.com"))
#=> "Welcome Meg, your email is: [email protected]"

构建 IO 数据比连接二进制文件更便宜。连接多个 IO 数据块只需将它们放在列表中,因为 IO 数据可以任意嵌套,这是一个廉价且高效的操作。大多数基于 IO 的 API,如 :gen_tcpIO,接收 IO 数据并将其直接写入套接字,而无需将其转换为二进制文件。

IO 数据的一个缺点是,你不能像使用二进制文件那样对 IO 数据的一部分执行模式匹配,因为你通常不知道 IO 数据的形状。在这些情况下,你可能需要通过调用 iodata_to_binary/1 将其转换为二进制文件,这在效率上相当高,因为它是在 C 中本地实现的。其他功能,比如计算 IO 数据的长度,可以直接通过调用 iodata_length/1 在 iodata 上计算。

Chardata

Erlang 和 Elixir 还具有 chardata/0 的概念。Chardata 与 IO 数据非常相似:唯一的区别是 IO 数据中的整数表示字节,而 chardata 中的整数表示 Unicode 代码点。字节 (byte/0) 是 0..255 范围内的整数,而 Unicode 代码点 (char/0) 是 0..0x10FFFF 范围内的整数。 IO 模块提供了 chardata_to_string/1 函数作为 chardata 的“对应函数”,与 IO 数据的 iodata_to_binary/1 函数对应。

如果你尝试在 chardata 上使用 iodata_to_binary/1,它将导致参数错误。例如,让我们尝试将一个不能用一个字节表示的代码点,比如 ,放入 IO 数据中

IO.iodata_to_binary(["The symbol for pi is: ", ])
#=> ** (ArgumentError) argument error

如果我们使用 chardata,它将按预期工作

iex> IO.chardata_to_string(["The symbol for pi is: ", ])
"The symbol for pi is: π"

摘要

函数

从 IO device 中读取。此操作对 Unicode 不安全。

:stdio 上返回一个原始的、基于行的 IO.Stream。此操作对 Unicode 不安全。

将 IO device 转换为 IO.Stream。此操作对 Unicode 不安全。

iodata 写入给定的 device

将 chardata 转换为字符串。

从 IO 设备 :stdio 获取一定数量的字节。

从 IO device 获取一定数量的字节。

从 IO device 读取一行。

检查并写入给定的 item 到设备。

根据给定的选项使用 IO device 检查 item

返回 IO 数据的大小。

将 IO 数据转换为二进制文件

item 写入给定的 device,类似于 write/2,但在末尾添加一个换行符。

从 IO device 中读取。

:stdio 上返回一个基于行的 IO.Stream

message 写入 stderr,以及当前的堆栈跟踪。

message 写入 stderr,以及给定的 stacktrace_info

chardata 写入给定的 device

类型

@type chardata() ::
  String.t() | maybe_improper_list(char() | chardata(), String.t() | [])
@type device() :: atom() | pid()
@type nodata() :: {:error, term()} | :eof

函数

链接到此函数

binread(device \\ :stdio, line_or_chars)

查看源代码
@spec binread(device(), :eof | :line | non_neg_integer()) :: iodata() | nodata()

从 IO device 中读取。此操作对 Unicode 不安全。

根据 line_or_chars 参数指定的规则迭代 device

  • 如果 line_or_chars 是一个整数,则它表示字节数。设备将按该字节数进行迭代。

  • 如果 line_or_chars:line,则设备将逐行迭代。

  • 如果 line_or_chars:eof,则设备将一直迭代到 :eof。自 Elixir 1.13.0 以来,line_or_chars 只能是 :eof:eof 替换了已弃用的 :all,区别在于 :all 在文件末尾返回 "",而 :eof 返回 :eof 本身。

它返回

  • data - 输出字节

  • :eof - 遇到文件末尾

  • {:error, reason} - 其他(罕见)的错误条件;例如,如果从 NFS 卷读取,则为 {:error, :estale}

注意:不要在 Unicode 模式下的 IO 设备上使用此函数,因为它将返回错误的结果。

链接到此函数

binstream()

查看源代码 (自 1.12.0 以来)
@spec binstream() :: Enumerable.t(binary())

:stdio 上返回一个原始的、基于行的 IO.Stream。此操作对 Unicode 不安全。

这等效于

IO.binstream(:stdio, :line)
链接到此函数

binstream(device \\ :stdio, line_or_bytes)

查看源代码
@spec binstream(device(), :line | pos_integer()) :: Enumerable.t()

将 IO device 转换为 IO.Stream。此操作对 Unicode 不安全。

IO.Stream 同时实现了 EnumerableCollectable,允许它用于读写。

根据给定的字节数或行数(如果给出 :line)迭代 device。这将从 IO 设备读取为原始二进制文件。

请注意,IO 流具有副作用,每次你遍历流时,你可能会得到不同的结果。

最后,不要在 Unicode 模式下的 IO 设备上使用此函数,因为它将返回错误的结果。

binstream/0 在 Elixir v1.12.0 中引入,而 binstream/2 自 v1.0.0 起可用。

链接到此函数

binwrite(device \\ :stdio, iodata)

查看源代码
@spec binwrite(device(), iodata()) :: :ok

iodata 写入给定的 device

此操作旨在与“原始”设备一起使用,这些设备是在没有编码的情况下启动的。给定的 iodata 将原样写入设备,而不会进行转换。有关 IO 数据的更多信息,请参阅模块文档中的“IO 数据”部分。

对于具有编码的设备,请使用 write/2

重要:不要在 Unicode 模式下的 IO 设备上使用此函数,因为它会写入错误的数据。特别是,标准 IO 设备默认情况下设置为 Unicode,因此使用此函数写入 stdio 可能会导致错误的数据发送到网络。

链接到此函数

chardata_to_string(chardata)

查看源代码
@spec chardata_to_string(chardata()) :: String.t()

将 chardata 转换为字符串。

有关 chardata 的更多信息,请参阅模块文档中的 “Chardata” 部分。

如果转换失败,它将引发一个 UnicodeConversionError。如果给出字符串,它将返回字符串本身。

示例

iex> IO.chardata_to_string([0x00E6, 0x00DF])
"æß"

iex> IO.chardata_to_string([0x0061, "bc"])
"abc"

iex> IO.chardata_to_string("string")
"string"
链接到此函数

getn(prompt, count \\ 1)

查看源代码
@spec getn(
  device() | chardata() | String.Chars.t(),
  pos_integer() | :eof | chardata() | String.Chars.t()
) :: chardata() | nodata()

从 IO 设备 :stdio 获取一定数量的字节。

如果 :stdio 是一个 Unicode 设备,则 count 表示要检索的 Unicode 代码点数。否则,count 是要检索的原始字节数。

有关返回值的说明,请参见 IO.getn/3

链接到此函数

getn(device, prompt, count)

查看源代码
@spec getn(device(), chardata() | String.Chars.t(), pos_integer() | :eof) ::
  chardata() | nodata()

从 IO device 获取一定数量的字节。

如果 IO device 是一个 Unicode 设备,count 表示要检索的 Unicode 代码点的数量。否则,count 是要检索的原始字节数。

它返回

  • data - 输入字符

  • :eof - 遇到文件末尾

  • {:error, reason} - 其他(罕见)的错误条件;例如,如果从 NFS 卷读取,则为 {:error, :estale}

链接到此函数

gets(device \\ :stdio, prompt)

查看源代码
@spec gets(device(), chardata() | String.Chars.t()) :: chardata() | nodata()

从 IO device 读取一行。

它返回

  • data - 以换行符 (LF) 或文件结尾 (EOF) 结尾的行中的字符

  • :eof - 遇到文件末尾

  • {:error, reason} - 其他(罕见)的错误条件;例如,如果从 NFS 卷读取,则为 {:error, :estale}

示例

要显示“你的名字是什么?”作为提示并等待用户输入

IO.gets("What is your name?\n")
链接到此函数

inspect(item, opts \\ [])

查看源代码
@spec inspect(
  item,
  keyword()
) :: item
when item: var

检查并写入给定的 item 到设备。

需要注意的是,它返回给定的 item 不变。这使得可以在代码中的几乎任何地方(例如,在管道中间)插入 IO.inspect/2 调用来“监视”值。

它默认情况下启用漂亮打印,宽度为 80 个字符。可以通过显式传递 :width 选项来更改宽度。

可以通过提供 :label 选项来装饰输出,以便轻松地将其与其他 IO.inspect/2 调用区分开来。标签将在被检查的 item 之前打印。

有关其余格式选项的完整列表,请参见 Inspect.Opts

示例

IO.inspect(<<0, 1, 2>>, width: 40)

打印

<<0, 1, 2>>

我们可以使用 :label 选项来装饰输出

IO.inspect(1..100, label: "a wonderful range")

打印

a wonderful range: 1..100

:label 选项在管道中尤其有用

[1, 2, 3]
|> IO.inspect(label: "before")
|> Enum.map(&(&1 * 2))
|> IO.inspect(label: "after")
|> Enum.sum()

打印

before: [1, 2, 3]
after: [2, 4, 6]
链接到此函数

inspect(device, item, opts)

查看源代码
@spec inspect(device(), item, keyword()) :: item when item: var

根据给定的选项使用 IO device 检查 item

有关选项的完整列表,请参见 inspect/2

@spec iodata_length(iodata()) :: non_neg_integer()

返回 IO 数据的大小。

有关 IO 数据的更多信息,请参见模块文档中的 "IO 数据" 部分。

由编译器内联。

示例

iex> IO.iodata_length([1, 2 | <<3, 4>>])
4
链接到此函数

iodata_to_binary(iodata)

查看源代码
@spec iodata_to_binary(iodata()) :: binary()

将 IO 数据转换为二进制文件

该操作不是 Unicode 安全的。

请注意,此函数将给定 IO 数据中的整数视为原始字节,并且不执行任何类型的编码转换。如果您想从字符列表转换为 UTF-8 编码的字符串,请改用 chardata_to_string/1。有关 IO 数据和字符数据的更多信息,请参见模块文档中的 "IO 数据" 部分。

如果此函数接收二进制数据,则返回相同的二进制数据。

由编译器内联。

示例

iex> bin1 = <<1, 2, 3>>
iex> bin2 = <<4, 5>>
iex> bin3 = <<6>>
iex> IO.iodata_to_binary([bin1, 1, [2, 3, bin2], 4 | bin3])
<<1, 2, 3, 1, 2, 3, 4, 5, 4, 6>>

iex> bin = <<1, 2, 3>>
iex> IO.iodata_to_binary(bin)
<<1, 2, 3>>
链接到此函数

puts(device \\ :stdio, item)

查看源代码
@spec puts(device(), chardata() | String.Chars.t()) :: :ok

item 写入给定的 device,类似于 write/2,但在末尾添加一个换行符。

默认情况下,device 是标准输出。如果成功,它将返回 :ok

示例

IO.puts("Hello World!")
#=> Hello World!

IO.puts(:stderr, "error")
#=> error
链接到此函数

read(device \\ :stdio, line_or_chars)

查看源代码
@spec read(device(), :eof | :line | non_neg_integer()) :: chardata() | nodata()

从 IO device 中读取。

如果给定了 :line,则 device 会逐行遍历给定数量的字符,或者直到 :eof

它返回

  • data - 输出字符

  • :eof - 遇到文件末尾

  • {:error, reason} - 其他(罕见)的错误条件;例如,如果从 NFS 卷读取,则为 {:error, :estale}

链接到此函数

stream()

查看源代码 (自 1.12.0 起)
@spec stream() :: Enumerable.t(String.t())

:stdio 上返回一个基于行的 IO.Stream

这等效于

IO.stream(:stdio, :line)
链接到此函数

stream(device \\ :stdio, line_or_codepoints)

查看源代码
@spec stream(device(), :line | pos_integer()) :: Enumerable.t()

将 IO device 转换为 IO.Stream

IO.Stream 同时实现了 EnumerableCollectable,允许它用于读写。

如果给定了 :line,则 device 会遍历给定数量的字符或逐行遍历。

这从 IO 中读取 UTF-8。查看 IO.binstream/2 以将 IO 作为原始二进制数据处理。

请注意,IO 流具有副作用,每次你遍历流时,你可能会得到不同的结果。

stream/0 已在 Elixir v1.12.0 中引入,而 stream/2 自 v1.0.0 起可用。

示例

这是一个关于如何从命令行模拟回声服务器的示例

Enum.each(IO.stream(:stdio, :line), &IO.write(&1))

另一个示例,您可能希望在每条新行收集用户输入,并在空行上断开,然后删除多余的换行符 ("\n")

IO.stream(:stdio, :line)
|> Enum.take_while(&(&1 != "\n"))
|> Enum.map(&String.replace(&1, "\n", ""))
@spec warn(chardata() | String.Chars.t()) :: :ok

message 写入 stderr,以及当前的堆栈跟踪。

如果成功,它将返回 :ok

不要在另一个函数的尾部调用此函数。由于尾调用优化,不会添加堆栈跟踪条目,并且堆栈跟踪将被错误地截断。因此,请确保至少有一个表达式(或一个原子,例如 :ok)跟随 IO.warn/1 调用。

示例

IO.warn("variable bar is unused")
#=> warning: variable bar is unused
#=>   (iex) evaluator.ex:108: IEx.Evaluator.eval/4
链接到此函数

warn(message, stacktrace_info)

查看源代码
@spec warn(
  chardata() | String.Chars.t(),
  Exception.stacktrace() | keyword() | Macro.Env.t()
) :: :ok

message 写入 stderr,以及给定的 stacktrace_info

stacktrace_info 必须是以下之一

  • 一个 __STACKTRACE__,其中堆栈跟踪中的所有条目都将包含在错误消息中

  • 一个 Macro.Env 结构(自 v1.14.0 起),其中将使用来自编译环境的单个堆栈跟踪条目

  • 一个关键字列表,至少包含 :file 选项,表示单个堆栈跟踪条目(自 v1.14.0 起)。还支持 :line:column:module:function 选项

此函数通知编译器已打印警告并发出编译器诊断 (Code.diagnostic/1)。如果给出了 Macro.Env 或将这些值作为关键字列表传递,则诊断将包含精确的文件和位置信息,但对于堆栈跟踪则不会,因为它们通常不精确。

如果成功,它将返回 :ok

示例

IO.warn("variable bar is unused", module: MyApp, function: {:main, 1}, line: 4, file: "my_app.ex")
#=> warning: variable bar is unused
#=>   my_app.ex:4: MyApp.main/1
链接到此函数

write(device \\ :stdio, chardata)

查看源代码
@spec write(device(), chardata() | String.Chars.t()) :: :ok

chardata 写入给定的 device

默认情况下,device 是标准输出。

示例

IO.write("sample")
#=> sample

IO.write(:stderr, "error")
#=> error