2
2
======
3
3
4
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 ) 。
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
8
9
9
我们曾经在早期的文章中说现在介绍IO似乎有点早。
10
10
但是,我们注意到IO系统其实提供了一窥Elixir和虚拟机的设计哲学和精妙的绝佳机会。
11
11
12
12
> “早期的文章”:现在介绍I/O似乎有点早,但是I/O系统可以让我们一窥Elixir哲学,满足我们对该语言以及VM的好奇心。
13
13
14
- ##``` IO `` ` 模块
14
+ ##` IO ` 模块
15
15
16
- ``` IO ``` 模块提供了Elixir语言中读写标准输入 /输出(``` :stdio `` ` )、
17
- 标准错误(``` :stderr `` ` )、文件和其他IO设备的主要机制。
16
+ 模块 ` IO ` 提供了Elixir语言中读写标准输入 /输出(` :stdio ` )、
17
+ 标准错误(` :stderr ` )、文件和其他IO设备的主要机制。
18
18
19
19
该模块的使用方法颇为直白:
20
20
@@ -36,9 +36,9 @@ iex> IO.puts :stderr, "hello world"
36
36
:ok
37
37
```
38
38
39
- ##``` File `` ` 模块
39
+ ##` File ` 模块
40
40
41
- ``` File `` ` 模块包含的函数可以让我们打开文件作为IO设备。
41
+ ` File ` 模块包含的函数可以让我们打开文件作为IO设备。
42
42
默认情况下文件是以二进制模式打开,
43
43
它要求程序员使用特定的``` IO.binread/2 ``` 和``` IO.binwrite/2 ``` 函数来读写文件:
44
44
@@ -53,7 +53,7 @@ iex> File.read "hello"
53
53
{:ok ," world" }
54
54
```
55
55
56
- 文件可以使用``` :utf8 ``` 编码方式打开,这让``` File `` ` 模块以UTF-8编码方式解析文件中读取的字节:
56
+ 文件可以使用` :utf8 ` 编码方式打开,这让` File ` 模块以UTF-8编码方式解析文件中读取的字节:
57
57
58
58
``` elixir
59
59
iex> {:ok , file}= File .open " another" , [:write ,:utf8 ]
@@ -62,13 +62,13 @@ iex> {:ok, file} = File.open "another", [:write, :utf8]
62
62
63
63
除了打开、读写文件的函数,文件模块还提供了许多函数来操作文件系统。
64
64
这些函数根据Unix平台上功能相对应的命令来命名。
65
- 如``` File.rm/1 ``` 用来删除文件,``` File.mkdir/1 `` ` 用来创建目录,
66
- ``` File.mkdir_p/1 `` ` 创建目录并保证其父目录一并创建。
67
- 甚至还有``` File.cp_r/2 ``` 和 ``` File.rm_rf/1 `` ` 用来递归地复制或删除整个目录。
65
+ 如` File.rm/1 ` 用来删除文件,` File.mkdir/1 ` 用来创建目录,
66
+ ` File.mkdir_p/1 ` 创建目录并保证其父目录一并创建。
67
+ 甚至还有` File.cp_r/2 ` 和 ` File.rm_rf/1 ` 用来递归地复制或删除整个目录。
68
68
69
- 你会注意到``` File ``` 模块的函数有两种变体,一个普通的和一个名称末尾有``` ! `` ` (bang)的。
70
- 例如在上面的例子里,我们在读取“hello”文件时,用的是不带``` ! `` ` 的版本。
71
- 相对地,我们也可以使用``` File.read!/1 `` ` :
69
+ 你会注意到` File ` 模块的函数有两种变体,一个普通的和一个名称末尾有` ! ` (bang)的。
70
+ 例如在上面的例子里,我们在读取“hello”文件时,用的是不带` ! ` 的版本。
71
+ 相对地,我们也可以使用` File.read!/1 ` :
72
72
73
73
``` elixir
74
74
iex> File .read " hello"
98
98
{:ok , body}= File .read (file)
99
99
```
100
100
101
- 当遇到文件不存在的情况时,函数返回``` {:error, reason} `` ` ,然后导致在跟左侧元祖做模式匹配时失败。
101
+ 当遇到文件不存在的情况时,函数返回` {:error, reason} ` ,然后导致在跟左侧元祖做模式匹配时失败。
102
102
失败依然会抛出一个异常,但是该异常的错误信息是描述一次模式匹配失败,而不是文件的什么错误。
103
103
从而在一定程度上掩盖了真正的失败原因。
104
104
@@ -119,9 +119,9 @@ File.read!(file)
119
119
120
120
##``` Path ``` 模块
121
121
122
- ``` File ``` 模块中的绝大多数函数都以各种路径作为参数 。
122
+ 模块 ` File ` 中的绝大多数函数都以各种路径作为参数 。
123
123
通常,这些路径都是二进制串(binaries)。
124
- ``` Path ``` 模块提供了操作这些路径的函数 :
124
+ 模块 ` Path ` 提供了操作这些路径的函数 :
125
125
126
126
``` elixir
127
127
iex> Path .join (" foo" ," bar" )
@@ -130,8 +130,8 @@ iex> Path.expand("~/hello")
130
130
" /Users/jose/hello"
131
131
```
132
132
133
- 推荐使用``` Path `` ` 模块提供的函数而不是直接手动操作代表路径的二进制串。
134
- 因为``` Path `` ` 模块考虑了不同操作系统的区别,使得各种处理是对“操作系统”透明的。
133
+ 推荐使用` Path ` 模块提供的函数而不是直接手动操作代表路径的二进制串。
134
+ 因为` Path ` 模块考虑了不同操作系统的区别,使得各种处理是对“操作系统”透明的。
135
135
最后,记住在Windows平台上处理文件操作时,Elixir会自动将斜杠(/)转换成反斜杠(\)。
136
136
137
137
有了上面介绍的模块和函数,我们已经能对文件系统进行基本的操作。
@@ -140,15 +140,15 @@ iex> Path.expand("~/hello")
140
140
141
141
##进程(Processes)和组长(group leaders)
142
142
143
- 你可能已经注意到``` File.open/2 ``` 函数返回类似``` {:ok, pid} `` ` 的元祖:
143
+ 你可能已经注意到` File.open/2 ` 函数返回类似` {:ok, pid} ` 的元祖:
144
144
145
145
``` elixir
146
146
iex> {:ok , file}= File .open " hello" , [:write ]
147
147
{:ok ,# PID<0.47.0>}
148
148
```
149
149
150
- ``` IO ``` 模块实际上是和进程 (Process)一起协同工作的。
151
- 当你调用``` IO.write(pid, binary) ``` 时,``` IO ``` 模块将发送一条消息给``` pid `` ` 标示的进程,
150
+ 模块 ` IO ` 实际上是和进程 (Process)一起协同工作的。
151
+ 当你调用` IO.write(pid, binary) ` 时,` IO ` 模块将发送一条消息给` pid ` 标示的进程,
152
152
附上所期望进行的操作。
153
153
154
154
如果我们自己用进程来描述这个过程:
@@ -163,11 +163,11 @@ iex> IO.write(pid, "hello")
163
163
** (ErlangError ) erlangerror: :terminated
164
164
```
165
165
166
- 调用``` IO.write/2 ``` 之后,可以看到``` IO `` ` 模块发出的请求(四个元素的元祖)被打印了出来。
167
- 不久后,因为我们并未提供``` IO `` ` 模块期待的某种结果,这个请求失败了。
166
+ 调用` IO.write/2 ` 之后,可以看到` IO ` 模块发出的请求(四个元素的元祖)被打印了出来。
167
+ 不久后,因为我们并未提供` IO ` 模块期待的某种结果,这个请求失败了。
168
168
169
- [ ``` StringIO `` ` 模块] ( http://elixir-lang.org/docs/stable/elixir/StringIO.html )
170
- 提供了基于字符串的``` IO `` ` 设备消息处理功能(将字符串看做IO设备):
169
+ [ ` StringIO ` 模块] ( http://elixir-lang.org/docs/stable/elixir/StringIO.html )
170
+ 提供了基于字符串的` IO ` 设备消息处理功能(将字符串看做IO设备):
171
171
172
172
``` elixir
173
173
iex> {:ok , pid}= StringIO .open (" hello" )
@@ -179,7 +179,7 @@ iex> IO.read(pid, 2)
179
179
Erlang虚拟机通过利用进程给IO设备建模,使得同一个网络中的不同节点可以通过交换文件进程,
180
180
实现跨节点的文件读写。而在所有IO设备之中,有一个特殊的进程,称作* 组长(group leader)* 。
181
181
182
- 当你写东西到标准输入输出(``` :stdio `` ` ),实际上是发送了一条消息给进程组长,
182
+ 当你写东西到标准输入输出(` :stdio ` ),实际上是发送了一条消息给进程组长,
183
183
它把内容写给标准输出的文件描述符:
184
184
185
185
``` elixir
@@ -194,15 +194,15 @@ hello
194
194
在不同的应用场景下,让哪个进程作为组长是可以配置的。
195
195
例如,当在远程终端执行代码时,通过配置组长,可以使得远程节点上打印的消息可以被重定向到发起操作(你)的终端上。
196
196
197
- ##``` iodata ``` 和 ``` chardata `` `
197
+ ##` iodata ` 和 ` chardata `
198
198
199
199
在以上所有例子中,我们都用的是二进制串格式写入文件。
200
200
在“二进制串、字符串和字符列表”那章里,我们提到过字符串(string)就是普通的bytes,
201
201
而字符列表(char list)就是字符编码(code point,如“Uxxxx”、“049”)的列表。
202
202
203
- ``` IO ``` 模块和``` File `` ` 模块中的函数还可以接受列表作为参数。
204
- 不光如此,它们还接受混合类型的列表,里面内容可以是列表(如``` 'ab c' `` ` )、
205
- 整形(如``` 49 ``` , ``` ?A ``` --- 返回65)和二进制串(如``` "ab c" `` ` ):
203
+ ` IO ` 模块和` File ` 模块中的函数还可以接受列表作为参数。
204
+ 不光如此,它们还接受混合类型的列表,里面内容可以是列表(如` 'ab c' ` )、
205
+ 整形(如` 49 ` , ` ?A ` --- 返回65)和二进制串(如` "ab c" ` ):
206
206
207
207
``` elixir
208
208
iex> IO .puts ' hello world'
@@ -213,18 +213,16 @@ hello world
213
213
:ok
214
214
```
215
215
216
- > ``` ? ``` 返回后面字符的编码(code point),如``` ?A `` ` 默认情况下返回65。
216
+ > ` ? ` 返回后面字符的编码(code point),如` ?A ` 默认情况下返回65。
217
217
218
218
尽管如此,有些地方还是要注意。一个列表可能表示一串byte,或者一串字符。
219
219
用哪一种需要看IO设备是怎么编码的。
220
220
如果不指明编码,文件就以raw模式打开,
221
- 这时候只能用``` IO ``` 模块里``` bin* `` ` 开头(二进制版本)的函数对其进行操作。
222
- 这些函数接受``` iodata `` ` 作为参数。也就是说,它们期待一个整数值的列表,用来表示bytes或二进制串。
221
+ 这时候只能用` IO ` 模块里` bin* ` 开头(二进制版本)的函数对其进行操作。
222
+ 这些函数接受` iodata ` 作为参数。也就是说,它们期待一个整数值的列表,用来表示bytes或二进制串。
223
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.
225
-
226
- 另一边,使用``` :utf8 ``` 打开的``` :stdio ``` 和文件使用``` IO ``` 模块里剩下来其它的函数。
227
- 这些函数期待``` char_data ``` 作为参数,即一串字符或字符串的列表。
224
+ 另一边,使用` :utf8 ` 打开的` :stdio ` 和文件使用` IO ` 模块里剩下来其它的函数。
225
+ 这些函数期待` char_data ` 作为参数,即一串字符或字符串的列表。
228
226
229
227
尽管差别比较精妙,但只是当你想要传递列表给那些函数的时候,才用担心一下细节问题。
230
228
而二进制串(binaries)表示了底层的bytes字节列表,这种表示已经是raw的了。