You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rename chapter 12 and fix a bunch of errors in that chapter
Chapter 12 was called just "IO" but explained both how to do IO in Elixir as
well as how to interact with files and the file system (the File and Path
modules). The new title - "IO and the file system" - reflects
exactly that.
This chapter is a quick introduction to input/output mechanisms in Elixir and related modules, like [`IO`](/docs/stable/elixir/IO.html), [`File`](/docs/stable/elixir/File.html) and [`Path`](/docs/stable/elixir/Path.html).
11
+
This chapter is a quick introduction to input/output mechanisms and file-system-related tasks, as well as to related modules like [`IO`](/docs/stable/elixir/IO.html), [`File`](/docs/stable/elixir/File.html) and [`Path`](/docs/stable/elixir/Path.html).
12
12
13
13
We had originally sketched this chapter to come much earlier in the getting started guide. However, we noticed the IO system provides a great opportunity to shed some light on some philosophies and curiosities of Elixir and the VM.
14
14
15
-
## 12.1 The IO module
15
+
## 12.1 The `IO` module
16
16
17
-
The `IO` module in Elixir is the main mechanism for reading and writing to the standard io (`:stdio`), standard error (`:stderr`), files and other IO devices. Usage of the module is pretty straight-forward:
17
+
The `IO` module is the main mechanism in Elixir for reading and writing to standard input/output (`:stdio`), standard error (`:stderr`), files and other IO devices. Usage of the module is pretty straightforward:
18
18
19
19
```iex
20
20
iex> IO.puts "hello world"
@@ -25,17 +25,17 @@ yes or no? yes
25
25
"yes\n"
26
26
```
27
27
28
-
By default, the functions in the IO module use the standard input and output. We can pass the `:stderr` as argument to write to the standard error device:
28
+
By default, functions in the IO module read from the standard input and write to the standard output. We can change that by passing, for example, `:stderr` as an argument (in order to write to the standard error device):
29
29
30
30
```iex
31
31
iex> IO.puts :stderr, "hello world"
32
32
hello world
33
33
:ok
34
34
```
35
35
36
-
## 12.2 The File module
36
+
## 12.2 The `File` module
37
37
38
-
The [`File`](/docs/stable/elixir/File.html) module contains functions that allows us to open files as IO devices. By default, files are opened in binary mode, which requires developers to use the specific `IO.binread/2` and `IO.binwrite/2` functions from the `IO` module:
38
+
The [`File`](/docs/stable/elixir/File.html) module contains functions that allow us to open files as IO devices. By default, files are opened in binary mode, which requires developers to use the specific `IO.binread/2` and `IO.binwrite/2` functions from the `IO` module:
39
39
40
40
```iex
41
41
iex> {:ok, file} = File.open "hello", [:write]
@@ -48,16 +48,11 @@ iex> File.read "hello"
48
48
{:ok, "world"}
49
49
```
50
50
51
-
A file can also be opened with `:utf8` encoding which allows the remaining functions in the `IO` module to be used:
51
+
A file can also be opened with `:utf8` encoding, which tells the `File` module to interpret the bytes read from the file as UTF-8-encoded bytes.
Besides functions for opening, reading and writing files, the `File` module has many functions that work on the file system. Those functions are named after their UNIX equivalents. For example, `File.rm/1` can be used to remove files, `File.mkdir/1` to create directories, `File.mkdir_p/1` creates directories guaranteeing their parents exists and there is even `File.cp_r/2` and `File.rm_rf/2` which copy and remove files and directories recursively.
53
+
Besides functions for opening, reading and writing files, the `File` module has many functions to work with the file system. Those functions are named after their UNIX equivalents. For example, `File.rm/1` can be used to remove files, `File.mkdir/1` to create directories, `File.mkdir_p/1` to create directories and all their parent chain. There are even `File.cp_r/2` and `File.rm_rf/2` to respectively copy and remove files and directories recursively (i.e., copying and removing the contents of the directories too).
59
54
60
-
You will also notice that functions in the `File` module have two variants, one with `!` (bang) in its name and others without. For example, when we read the "hello" file above, we have used the one without `!`. Let's try some new examples:
55
+
You will also notice that functions in the `File` module have two variants: one "regular" variant and another variant which has the same name as the regular version but with a trailing bang (`!`). For example, when we read the `"hello"` file in the example above, we use `File.read/1`. Alternatively, we can use `File.read!/1`:
61
56
62
57
```iex
63
58
iex> File.read "hello"
@@ -70,30 +65,28 @@ iex> File.read! "unknown"
70
65
** (File.Error) could not read file unknown: no such file or directory
71
66
```
72
67
73
-
Notice that when the file does not exist, the version with `!` raises an error. That said, the version without `!` is preferred when you want to handle different outcomes with pattern matching. However, if you expect the file to be there, the bang variation is more useful as it raises a meaningful error message. That said, never write:
74
-
75
-
```elixir
76
-
{:ok, body} =File.read(file)
77
-
```
78
-
79
-
Instead write:
68
+
Notice that when the file does not exist, the version with `!` raises an error. The version without `!` is preferred when you want to handle different outcomes using pattern matching:
80
69
81
70
```elixir
82
71
caseFile.read(file) do
83
-
{:ok, body} ->#handle ok
84
-
{:error, r} -># handle error
72
+
{:ok, body} ->#do something with the `body`
73
+
{:error, reason} -># handle the error caused by `reason`
85
74
end
86
75
```
87
76
88
-
or
77
+
However, if you expect the file to be there, the bang variation is more useful as it raises a meaningful error message. Avoid writing:
89
78
90
79
```elixir
91
-
File.read!(file)
80
+
{:ok, body} =File.read(file)
92
81
```
93
82
83
+
as, in case of an error, `File.read/1` will return `{:error, reason}` and the pattern matching will fail. You will still get the desired result (a raised error), but the message will be about the pattern which doesn't match (thus being cryptic in respect to what the error actually is about).
84
+
85
+
If you don't want to handle a possible error (i.e., you want it to bubble up), prefer using `File.read!/1`.
86
+
94
87
## 12.3 The Path module
95
88
96
-
The majority of the functions in the File module expects paths as arguments. Most commonly, those paths will be binaries and they can be manipulated with the [`Path`](/docs/stable/elixir/Path.html) module:
89
+
The majority of the functions in the `File` module expect paths as arguments. Most commonly, those paths will be regular binaries. The [`Path`](/docs/stable/elixir/Path.html) module provides facilities for working with such paths:
97
90
98
91
```iex
99
92
iex> Path.join("foo", "bar")
@@ -102,18 +95,20 @@ iex> Path.expand("~/hello")
102
95
"/Users/jose/hello"
103
96
```
104
97
105
-
With this we have covered the main modules for doing IO and interacting with the file system. Next we will discuss some curiosities and advanced topics regarding IO. Those sections are not necessary to write Elixir code, so feel free to skip them, but they do provide an overview of how the IO system is implemented in the VM and other curiosities.
98
+
Using functions from the `Path` module as opposed to just manipulating binaries is preferred since the `Path` module takes care of different operating systems transparently. For example, `Path.join/2` joins a path with slashes (`/`) on Unix-like systems and with backslashes (`\\`) on Windows.
99
+
100
+
With this we have covered the main modules that Elixir provides for dealing with IO and interacting with the file system. In the next sections, we will discuss some advanced topics regarding IO. Those sections are not necessary in order to write Elixir code, so feel free to skip them, but they do provide a nice overview of how the IO system is implemented in the VM and other curiosities.
106
101
107
102
## 12.4 Processes and group leaders
108
103
109
-
You may have noticed that `File.open/2`returned a tuple containing a PID:
104
+
You may have noticed that `File.open/2`returns a tuple like `{:ok, pid}`:
110
105
111
106
```iex
112
107
iex> {:ok, file} = File.open "hello", [:write]
113
108
{:ok, #PID<0.47.0>}
114
109
```
115
110
116
-
That's because the IO module actually works with processes. When you say`IO.write(pid, binary)`, the IO module will send a message to the process with the desired operation. Let's see what happens if we use our own process:
111
+
That happens because the `IO` module actually works with processes (see [chapter 11](/getting_started/11.html)). When you write`IO.write(pid, binary)`, the `IO` module will send a message to the process identified by `pid` with the desired operation. Let's see what happens if we use our own process:
117
112
118
113
```iex
119
114
iex> pid = spawn fn ->
@@ -125,9 +120,9 @@ iex> IO.write(pid, "hello")
125
120
** (ErlangError) erlang error: :terminated
126
121
```
127
122
128
-
After `IO.write/2`, we can see the request sent by the IO module printed, which then fails since the IO module expected some kind of result that we did not supply.
123
+
After `IO.write/2`, we can see the request sent by the `IO` module (a four-elements tuple) printed out. Soon after that, we see that it fails since the `IO` module expected some kind of result that we did not supply.
129
124
130
-
The [`StringIO`](/docs/stable/elixir/StringIO.html) module provides an implementation of the IO device messages on top of a string:
125
+
The [`StringIO`](/docs/stable/elixir/StringIO.html) module provides an implementation of the `IO` device messages on top of strings:
131
126
132
127
```iex
133
128
iex> {:ok, pid} = StringIO.open("hello")
@@ -136,9 +131,9 @@ iex> IO.read(pid, 2)
136
131
"he"
137
132
```
138
133
139
-
By modelling IO devices with processes, the Erlang VM allows different nodes in the same network to exchange file processes to read/write files in between nodes. Of all IO devices, there is one that is special to each process, called group leader.
134
+
By modelling IO devices with processes, the Erlang VM allows different nodes in the same network to exchange file processes in order to read/write files in between nodes. Of all IO devices, there is one that is special to each process: the **group leader**.
140
135
141
-
When you write to `:stdio`, you are actually sending a message to the group leader, which writes to STDIO file descriptor:
136
+
When you write to `:stdio`, you are actually sending a message to the group leader, which writes to the standard-input file descriptor:
142
137
143
138
```iex
144
139
iex> IO.puts :stdio, "hello"
@@ -153,7 +148,7 @@ The group leader can be configured per process and is used in different situatio
153
148
154
149
## 12.5 `iodata` and `chardata`
155
150
156
-
In all examples above, we have used binaries/strings when writing to files. In the chapter "Binaries, strings and char lists", we mentioned how strings are simply bytes while char lists are lists with code points.
151
+
In all of the examples above, we used binaries when writing to files. In the chapter ["Binaries, strings and char lists"](/getting_started/6.html), we mentioned how strings are simply bytes while char lists are lists with code points.
157
152
158
153
The functions in `IO` and `File` also allow lists to be given as arguments. Not only that, they also allow a mixed list of lists, integers and binaries to be given:
159
154
@@ -166,10 +161,10 @@ hello world
166
161
:ok
167
162
```
168
163
169
-
However, this requires some attention. A list may represent either a bunch of bytes or a bunch of characters and which one to use depends on the encoding of the IO device. If the file is opened without encoding, the file is expected to be in raw mode, and the functions in the `IO` module starting with `bin*` must be used. Those functions expect an `iodata` as argument, i.e. it expects a list of integers representing bytes and binaries to be given.
164
+
However, this requires some attention. A list may represent either a bunch of bytes or a bunch of characters and which one to use depends on the encoding of the IO device. If the file is opened without encoding, the file is expected to be in raw mode, and the functions in the `IO` module starting with `bin*` must be used. Those functions expect an `iodata` as argument; i.e., they expect a list of integers representing bytes and binaries to be given.
170
165
171
-
On the other hand, `:stdio` and files opened with `:utf8` encoding work with the remaining functions in the `IO` module and those expect a `char_data` as argument, i.e. they expect a list of characters or strings to be given.
166
+
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 argument, that is, a list of characters or strings.
172
167
173
168
Although this is a subtle difference, you only need to worry about those details if you intend to pass lists to those functions. Binaries are already represented by the underlying bytes and as such their representation is always raw.
174
169
175
-
This finishes our tour of IO devices and IO related functionality. We have learned about four Elixir modules, [`IO`](/docs/stable/elixir/IO.html), [`File`](/docs/stable/elixir/File.html), [`Path`](/docs/stable/elixir/Path.html) and [`StringIO`](/docs/stable/elixir/StringIO.html), as well as how the VM uses processes for the underlying IO mechanisms and how to use (char and io) data for IO operations.
170
+
This finishes our tour of IO devices and IO related functionality. We have learned about four Elixir modules - [`IO`](/docs/stable/elixir/IO.html), [`File`](/docs/stable/elixir/File.html), [`Path`](/docs/stable/elixir/Path.html) and [`StringIO`](/docs/stable/elixir/StringIO.html) - as well as how the VM uses processes for the underlying IO mechanisms and how to use `chardata` and `iodata` for IO operations.
0 commit comments