查看源代码 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
在此函数中,创建电子邮件地址将复制 username
和 domain
二进制文件。现在想象一下,你想在另一个二进制文件中使用生成的电子邮件
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_tcp
和 IO
,接收 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
中读取。
将 IO device
转换为 IO.Stream
。
将 message
写入 stderr,以及当前的堆栈跟踪。
将 message
写入 stderr,以及给定的 stacktrace_info
。
将 chardata
写入给定的 device
。
类型
函数
@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 设备上使用此函数,因为它将返回错误的结果。
@spec binstream() :: Enumerable.t(binary())
在 :stdio
上返回一个原始的、基于行的 IO.Stream
。此操作对 Unicode 不安全。
这等效于
IO.binstream(:stdio, :line)
@spec binstream(device(), :line | pos_integer()) :: Enumerable.t()
将 IO device
转换为 IO.Stream
。此操作对 Unicode 不安全。
IO.Stream
同时实现了 Enumerable
和 Collectable
,允许它用于读写。
根据给定的字节数或行数(如果给出 :line
)迭代 device
。这将从 IO 设备读取为原始二进制文件。
请注意,IO 流具有副作用,每次你遍历流时,你可能会得到不同的结果。
最后,不要在 Unicode 模式下的 IO 设备上使用此函数,因为它将返回错误的结果。
binstream/0
在 Elixir v1.12.0 中引入,而 binstream/2
自 v1.0.0 起可用。
将 iodata
写入给定的 device
。
此操作旨在与“原始”设备一起使用,这些设备是在没有编码的情况下启动的。给定的 iodata
将原样写入设备,而不会进行转换。有关 IO 数据的更多信息,请参阅模块文档中的“IO 数据”部分。
对于具有编码的设备,请使用 write/2
。
重要:不要在 Unicode 模式下的 IO 设备上使用此函数,因为它会写入错误的数据。特别是,标准 IO 设备默认情况下设置为 Unicode,因此使用此函数写入 stdio 可能会导致错误的数据发送到网络。
将 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"
@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
。
@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}
@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")
@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]
根据给定的选项使用 IO device
检查 item
。
有关选项的完整列表,请参见 inspect/2
。
@spec iodata_length(iodata()) :: non_neg_integer()
返回 IO 数据的大小。
有关 IO 数据的更多信息,请参见模块文档中的 "IO 数据" 部分。
由编译器内联。
示例
iex> IO.iodata_length([1, 2 | <<3, 4>>])
4
将 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>>
@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
@spec read(device(), :eof | :line | non_neg_integer()) :: chardata() | nodata()
从 IO device
中读取。
如果给定了 :line
,则 device
会逐行遍历给定数量的字符,或者直到 :eof
。
它返回
data
- 输出字符:eof
- 遇到文件末尾{:error, reason}
- 其他(罕见)的错误条件;例如,如果从 NFS 卷读取,则为{:error, :estale}
@spec stream() :: Enumerable.t(String.t())
在 :stdio
上返回一个基于行的 IO.Stream
。
这等效于
IO.stream(:stdio, :line)
@spec stream(device(), :line | pos_integer()) :: Enumerable.t()
将 IO device
转换为 IO.Stream
。
IO.Stream
同时实现了 Enumerable
和 Collectable
,允许它用于读写。
如果给定了 :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
@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
@spec write(device(), chardata() | String.Chars.t()) :: :ok
将 chardata
写入给定的 device
。
默认情况下,device
是标准输出。
示例
IO.write("sample")
#=> sample
IO.write(:stderr, "error")
#=> error