Skip to content

Commit 10154b4

Browse files
committed
update IO
1 parent 32d563e commit 10154b4

File tree

2 files changed

+131
-85
lines changed

2 files changed

+131
-85
lines changed

12-io.md

Lines changed: 116 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
12-I/O
1+
12-IO和文件系统
22
======
3-
[I/O模块](#121-io%E6%A8%A1%E5%9D%97)
4-
[文件模块](#122-%E6%96%87%E4%BB%B6%E6%A8%A1%E5%9D%97)
5-
[路径模块](#123-%E8%B7%AF%E5%BE%84%E6%A8%A1%E5%9D%97)
6-
[进程和组长](#124-%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BB%84%E9%95%BF)
7-
[*iodata**chardata*](#125-iodata%E5%92%8Cchardata)
8-
9-
本章简单介绍Elixir的输入、输出机制以及相关的模块,
10-
[IO](http://elixir-lang.org/docs/stable/elixir/IO.html)
11-
[文件](http://elixir-lang.org/docs/stable/elixir/File.html)
12-
[路径](http://elixir-lang.org/docs/stable/elixir/Path.html)
13-
14-
现在介绍I/O似乎有点早,但是I/O系统可以让我们一窥Elixir哲学,满足我们对该语言以及VM的好奇心。
15-
16-
##12.1-IO模块
17-
IO模块是Elixir语言中读写标准输入、输出、标准错误、文件、设备的主要机制。
18-
使用该模块的方法颇为直接:
3+
4+
本章简单介绍Elixir的输入、输出机制,文件系统相关的任务,
5+
以及涉及到的模块,如[```IO```](http://elixir-lang.org/docs/stable/elixir/IO.html)
6+
[```File```](http://elixir-lang.org/docs/stable/elixir/File.html)
7+
[```Path```](http://elixir-lang.org/docs/stable/elixir/Path.html)
8+
9+
我们曾经在早期的文章中说现在介绍IO似乎有点早。
10+
但是,我们注意到IO系统其实提供了一窥Elixir和虚拟机的设计哲学和精妙的绝佳机会。
11+
12+
>“早期的文章”:现在介绍I/O似乎有点早,但是I/O系统可以让我们一窥Elixir哲学,满足我们对该语言以及VM的好奇心。
13+
14+
## ```IO```模块
15+
16+
```IO```模块提供了Elixir语言中读写标准输入/输出(```:stdio```)、
17+
标准错误(```:stderr```)、文件和其他IO设备的主要机制。
18+
19+
该模块的使用方法颇为直白:
20+
1921
```elixir
2022
iex> IO.puts "hello world"
2123
"hello world"
@@ -25,18 +27,21 @@ yes or no? yes
2527
"yes\n"
2628
```
2729

28-
IO模块中的函数默认使用标准输入输出。
29-
我们也可以传递```:stderr```来指示将错误信息写到标准错误设备上:
30+
默认情况下,IO模块中的函数从标准输入中读取,向标准输出中写入。
31+
我们可以传递参数,比如```:stderr```,来指示将信息写到标准错误上:
32+
3033
```elixir
3134
iex> IO.puts :stderr, "hello world"
3235
"hello world"
3336
:ok
3437
```
3538

36-
#12.2-文件模块
37-
文件模块包含了可以让我们读写文件的函数。
38-
默认情况下文件是以二进制模式打开,它要求程序员使用特殊的```IO.binread/2```
39-
```IO.binwrite/2```函数来读写文件:
39+
## ```File```模块
40+
41+
```File```模块包含的函数可以让我们打开文件作为IO设备。
42+
默认情况下文件是以二进制模式打开,
43+
它要求程序员使用特定的```IO.binread/2``````IO.binwrite/2```函数来读写文件:
44+
4045
```elixir
4146
iex> {:ok, file} = File.open "hello", [:write]
4247
{:ok, #PID<0.47.0>}
@@ -48,22 +53,23 @@ iex> File.read "hello"
4853
{:ok, "world"}
4954
```
5055

51-
文件可以使用```:utf8```编码打开,然后就可以被IO模块中其他函数使用了:
56+
文件可以使用```:utf8```编码方式打开,这让```File```模块以UTF-8编码方式解析文件中读取的字节:
57+
5258
```elixir
5359
iex> {:ok, file} = File.open "another", [:write, :utf8]
5460
{:ok, #PID<0.48.0>}
5561
```
5662

57-
除了打开、读写文件的函数,文件模块还有许多函数来操作文件系统
58-
这些函数根据Unix功能相对应的命令命名
59-
```File.rm/1```用来删除文件```File.mkdir/1```
60-
用来创建目录;```File.mkdir_p/1```创建目录并保证其父目录一并创建
61-
还有```File.cp_r/2``````File.rm_rf/2```用来递归地复制和删除整个目录
63+
除了打开、读写文件的函数,文件模块还提供了许多函数来操作文件系统
64+
这些函数根据Unix平台上功能相对应的命令来命名
65+
```File.rm/1```用来删除文件```File.mkdir/1```用来创建目录,
66+
```File.mkdir_p/1```创建目录并保证其父目录一并创建
67+
甚至还有```File.cp_r/2``````File.rm_rf/1```用来递归地复制或删除整个目录
6268

69+
你会注意到```File```模块的函数有两种变体,一个普通的和一个名称末尾有```!```(bang)的。
70+
例如在上面的例子里,我们在读取“hello”文件时,用的是不带```!```的版本。
71+
相对地,我们也可以使用```File.read!/1```
6372

64-
你还会注意到文件模块中,一般函数都有一个名称类似的版本。区别是名称上一个有!(bang)一个没有。
65-
例如,上面的例子我们在读取“hello”文件时,用的是不带!号的版本。
66-
下面用例子演示下它们的区别:
6773
```elixir
6874
iex> File.read "hello"
6975
{:ok, "world"}
@@ -75,76 +81,107 @@ iex> File.read! "unknown"
7581
** (File.Error) could not read file unknown: no such file or directory
7682
```
7783

78-
注意看,当文件不存在时,带!号的版本会报错。就是说不带!号的版本能照顾到模式匹配出来的不同情况。
79-
但有的时候,你就是希望文件在那儿,!使得它能报出有意义的错误。
84+
注意看,当文件不存在时,带!号的版本会抛出一个错误。
85+
而不带!号的版本适用于你对不同结果进行模式匹配的情形:
86+
87+
```elixir
88+
case File.read(file) do
89+
{:ok, body} -> # do something with the `body`
90+
{:error, reason} -> # handle the error caused by `reason`
91+
end
92+
```
93+
94+
但有的时候,你就是希望文件在那儿,!号变体更加适用,因为它能报出有意义的错误。
95+
例如,如果这么写:
8096

81-
因此,不要写:
8297
```elixir
8398
{:ok, body} = File.read(file)
8499
```
85100

86-
相反地,应该这么写:
101+
当遇到文件不存在的情况时,函数返回```{:error, reason}```,然后导致在跟左侧元祖做模式匹配时失败。
102+
失败依然会抛出一个异常,但是该异常的错误信息是描述一次模式匹配失败,而不是文件的什么错误。
103+
从而在一定程度上掩盖了真正的失败原因。
104+
105+
可以这么写:
106+
87107
```elixir
88108
case File.read(file) do
89109
{:ok, body} -> # handle ok
90110
{:error, r} -> # handle error
91111
end
92112
```
93-
或者
113+
114+
当然,更推荐的写法:
115+
94116
```elixir
95117
File.read!(file)
96118
```
97119

98-
## 12.3-路径模块
99-
文件模块中绝大多数函数都以路径作为参数。通常这些路径都是二进制,可以被路径模块提供的函数操作:
120+
## ```Path```模块
121+
122+
```File```模块中的绝大多数函数都以各种路径作为参数。
123+
通常,这些路径都是二进制串(binaries)。
124+
```Path```模块提供了操作这些路径的函数:
125+
100126
```elixir
101127
iex> Path.join("foo", "bar")
102128
"foo/bar"
103129
iex> Path.expand("~/hello")
104130
"/Users/jose/hello"
105131
```
106132

133+
推荐使用```Path```模块提供的函数而不是直接手动操作代表路径的二进制串。
134+
因为```Path```模块考虑了不同操作系统的区别,使得各种处理是对“操作系统”透明的。
135+
最后,记住在Windows平台上处理文件操作时,Elixir会自动将斜杠(/)转换成反斜杠(\)。
136+
137+
有了上面介绍的模块和函数,我们已经能对文件系统进行基本的操作。
138+
下面将讨论有关IO的一些高级话题。这部分并不是写Elixir程序必须掌握的,可以跳过不看。
139+
但是如果你浏览一下,可以大致了解IO是如何在VM上实现的,以及其它一些有趣的内容。
107140

108-
有了以上介绍的几个模块和函数,我们已经能对文件系统进行基本的IO操作。
109-
下面将讨论I/O模块中令人好奇的高级话题。这部分不是写Elixir程序必须掌握的,可以跳过不看。
110-
但是如果你大概地浏览一下,可以了解IO是如何在VM上实现以及其它一些有趣的内容。
141+
## 进程(Processes)和组长(group leaders)
142+
143+
你可能已经注意到```File.open/2```函数返回类似```{:ok, pid}```的元祖:
111144

112-
## 12.4-进程和组长
113-
你可能已经发现,```File.open/2```函数返回了一个包含PID的元祖:
114145
```elixir
115146
iex> {:ok, file} = File.open "hello", [:write]
116147
{:ok, #PID<0.47.0>}
117-
```
148+
```
149+
150+
```IO```模块实际上是和进程(Process)一起协同工作的。
151+
当你调用```IO.write(pid, binary)```时,```IO```模块将发送一条消息给```pid```标示的进程,
152+
附上所期望进行的操作。
153+
154+
如果我们自己用进程来描述这个过程:
118155

119-
I/O模块实际上是同进程协同工作的。当你调用```IO.write(pid, binary)```时,
120-
I/O模块将发送一条消息给执行操作的进程。
121-
让我们用自己的代码表述下这个过程:
122156
```elixir
123157
iex> pid = spawn fn ->
124158
...> receive do: (msg -> IO.inspect msg)
125159
...> end
126160
#PID<0.57.0>
127161
iex> IO.write(pid, "hello")
128-
{:io_request, #PID<0.41.0>, #PID<0.57.0>, {:put_chars, :unicode, "hello"}}
162+
{:io_request, #PID<0.41.0>, #Reference<0.0.8.91>, {:put_chars, :unicode, "hello"}}
129163
** (ErlangError) erlang error: :terminated
130164
```
131165

132-
调用```IO.write/2```之后,可以看见打印出了发给IO模块的请求。
133-
然而因为我们没有提供某些东西,这个请求失败了。
166+
调用```IO.write/2```之后,可以看到```IO```模块发出的请求(四个元素的元祖)被打印了出来。
167+
不久后,因为我们并未提供```IO```模块期待的某种结果,这个请求失败了。
168+
169+
[```StringIO```模块](http://elixir-lang.org/docs/stable/elixir/StringIO.html)
170+
提供了基于字符串的```IO```设备消息处理功能(将字符串看做IO设备):
134171

135-
[StringIO模块](http://elixir-lang.org/docs/stable/elixir/StringIO.html)
136-
提供了一个基于字符串的IO实现:
137172
```elixir
138173
iex> {:ok, pid} = StringIO.open("hello")
139174
{:ok, #PID<0.43.0>}
140175
iex> IO.read(pid, 2)
141176
"he"
142177
```
143178

144-
Erlang虚拟机用进程给I/O设备建模,允许同一个网络中的不同节点通过交换文件进程,
145-
实现节点间的文件读写。在所有IO设备之中,有一个特殊的进程,称作组长(group leader)。
179+
Erlang虚拟机通过利用进程给IO设备建模,使得同一个网络中的不同节点可以通过交换文件进程,
180+
实现跨节点的文件读写。而在所有IO设备之中,有一个特殊的进程,称作*组长(group leader)*
181+
182+
当你写东西到标准输入输出(```:stdio```),实际上是发送了一条消息给进程组长,
183+
它把内容写给标准输出的文件描述符:
146184

147-
当你写东西到标准输出,实际上是发送了一条消息给组长,它把内容写给*STDIO文件表述者*
148185
```elixir
149186
iex> IO.puts :stdio, "hello"
150187
hello
@@ -154,16 +191,19 @@ hello
154191
:ok
155192
```
156193

157-
组长可为每个进程做相应配置,用于处理不同的情况
158-
例如,当在远程终端执行代码时,它能保证远程机器的消息可以被重定向到发起操作的终端上
194+
在不同的应用场景下,让哪个进程作为组长是可以配置的
195+
例如,当在远程终端执行代码时,通过配置组长,可以使得远程节点上打印的消息可以被重定向到发起操作(你)的终端上
159196

160-
## 12.5-*iodata**chardata*
161-
在以上所有例子中,我们都用的是二进制/字符串方式读写文件。
162-
在“二进制、字符串和字符列表”那章里,我们注意到字符串就是普通的bytes,
163-
而字符列表是code points(字符码)的列表。
197+
## ```iodata``````chardata```
198+
199+
在以上所有例子中,我们都用的是二进制串格式写入文件。
200+
在“二进制串、字符串和字符列表”那章里,我们提到过字符串(string)就是普通的bytes,
201+
而字符列表(char list)就是字符编码(code point,如“Uxxxx”、“049”)的列表。
202+
203+
```IO```模块和```File```模块中的函数还可以接受列表作为参数。
204+
不光如此,它们还接受混合类型的列表,里面内容可以是列表(如```'ab c'```)、
205+
整形(如```49``````?A``` --- 返回65)和二进制串(如```"ab c"```):
164206

165-
I/O模块和文件模块中的函数接受列表作为参数。
166-
还可以接受混合类型的列表,里面内容是整形、二进制都行:
167207
```elixir
168208
iex> IO.puts 'hello world'
169209
hello world
@@ -173,12 +213,18 @@ hello world
173213
:ok
174214
```
175215

216+
>```?```返回后面字符的编码(code point),如```?A```默认情况下返回65。
217+
176218
尽管如此,有些地方还是要注意。一个列表可能表示一串byte,或者一串字符。
177-
用哪一种需要看I/O设备是怎么编码的。
219+
用哪一种需要看IO设备是怎么编码的。
220+
如果不指明编码,文件就以raw模式打开,
221+
这时候只能用```IO```模块里```bin*```开头(二进制版本)的函数对其进行操作。
222+
这些函数接受```iodata```作为参数。也就是说,它们期待一个整数值的列表,用来表示bytes或二进制串。
223+
224+
On the other hand, :stdio and files opened with :utf8 encoding work with the remaining functions in the IO module. Those functions expect a char_data as an argument, that is, a list of characters or strings.
178225

179-
如果不指明编码,文件就以raw模式打开,这时候只能用文件模块里bin*开头(二进制版)
180-
的函数对其进行操作。
181-
这些函数接受*iodata*作为参数,即,它们期待一个整数值的列表,用来表示byte或二进制。
226+
另一边,使用```:utf8```打开的```:stdio```和文件使用```IO```模块里剩下来其它的函数。
227+
这些函数期待```char_data```作为参数,即一串字符或字符串的列表。
182228

183-
尽管只是细微的差别,如果你打算传递列表给那些函数,你需要考虑那些细节
184-
底层的bytes可以表示二进制,这种表示就是raw的
229+
尽管差别比较精妙,但只是当你想要传递列表给那些函数的时候,才用担心一下细节问题
230+
而二进制串(binaries)表示了底层的bytes字节列表,这种表示已经是raw的了

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
Elixir编程入门
22
=============
33

4-
Elixir,[ɪ'lɪksər],意为灵丹妙药、圣水,其logo是一枚紫色水滴:
4+
Elixir,[ɪ'lɪksər],意为灵丹妙药、圣水,其logo是一枚紫色水滴:
55

6-
![logo](http://elixir-lang.org/images/logo/logo.png)
6+
![logo](http://elixir-lang.org/images/logo/logo.png)
77

88
Elixir是一门建立在Erlang虚拟机上的
99
[**函数式**](http://baike.baidu.com/view/3476448.htm?fr=aladdin)的系统编程语言,
1010
支持元编程。
11-
创始人[José Valim](https://github.com/josevalim)是ruby界的知名人士。
12-
私以为,可以把Elixir看作函数式的ruby语言,或者是语法类似ruby的Erlang。Elixir受瞩目的原因,
13-
是因为它结合了Erlang作为系统编程语言的各种优点,以及ruby那样简单易懂的语法
14-
(Erlang语法比较晦涩)。
11+
创始人[José Valim](https://github.com/josevalim)是ruby界的知名人士。
12+
我觉得可以把Elixir看作函数式的ruby语言,或者是语法类似ruby的Erlang。
13+
Elixir受瞩目的主要原因,是因为它较好地结合了Erlang编程语言的各种优点,以及ruby那样简单易懂的语法
14+
(Erlang语法比较晦涩)。
1515

16-
Elixir还是一门初出茅庐的语言:
17-
2014年8月31日,1.0.0发布
18-
2014年9月1日临晨,1.0.0rc1发布
19-
2014年9月7日晚,1.0.0rc2发布
20-
2014年9月10日,1.0.0正式发布
21-
2015年9月28日,1.1发布
22-
2016年1月1日,v1.2.0发布
16+
Elixir还是一门初出茅庐的语言:
17+
- 2014年9月1日临晨,1.0.0rc1发布
18+
- 2014年9月7日晚,1.0.0rc2发布
19+
- 2014年9月18日,[v1.0正式发布](http://elixir-lang.org/blog/2014/09/18/elixir-v1-0-0-released/)
20+
- 2015年9月28日,[v1.1发布](http://elixir-lang.org/blog/2015/09/28/elixir-v1-1-0-released/)
21+
- 2016年1月1日,[v1.2发布](http://elixir-lang.org/blog/2016/01/03/elixir-v1-2-0-released/)
22+
- 2016年6月2日,[v1.3发布](http://elixir-lang.org/blog/2016/06/21/elixir-v1-3-0-released/)
2323

24-
本文主要框架为Elixir官方的入门教程,辅以网上其它Elixir资源的内容,以及花钱:sob:购买的原版书籍(Dave Thomas的《Programming Elixir》,Progmatic)
24+
本文主要框架为Elixir官方的入门教程,辅以网上其它Elixir资源的内容,以及花钱:sob:购买的原版书籍(Dave Thomas的《Programming Elixir》,Progmatic)
2525

2626
>请帮助更新文档(发个pr)。讨论问题可发issue。
2727
@@ -38,7 +38,7 @@ Elixir还是一门初出茅庐的语言:
3838
[9-递归](../master/9-recursion.md) <br/>
3939
[10-枚举类型和流](../master/10-enum-stream.md) <br/>
4040
[11-进程](../master/11-process.md) <br/>
41-
[12-IO](../master/12-io.md) <br/>
41+
[12-IO和文件系统](../master/12-io.md) <br/>
4242
[13-别名和程序导入](../master/13-alias-req-imp.md) <br/>
4343
[14-模块属性](../master/14-mod-attr.md) <br/>
4444
[15-结构体](../master/15-structs.md) <br/>

0 commit comments

Comments
 (0)