File Input/Output
11.1 Introduction
Until this point, all of our programs have taken a data set of values from user input. Any given program took in one data set, which consisted of one or more related values from the user, and then performed a computation based on that data set. Imagine a program that has many data sets, each consisting of multiple values. Entering all the data sets by hand could be somewhat time-consuming. Also, some data sets may come from external sources and not directly from the user.
The techniques presented in this chapter will allow you to manipulate files and utilize their data within your software. File input/output (I/O) allows software to perform computations on sets of data until a desired end result is achieved.
11.2 File Access: Reading and Writing
To access files, the built-in Ruby File
class is used. This class contains multiple
methods. We use the following: open
,
close
, gets
, and puts
. The method File.open
instantiates a new object that enables
Ruby to read from or write to an existing or new file. The object returned
by File.open
can then be used by Ruby
to access the file. The following code shows how to open a file:
myfile
=
File
.
open
(
file_name
,
access_mode
)
The myfile
variable is a File
object that can now be used to interact
with the file’s contents, depending on what access mode is used. Note that
file name
and access mode
are strings. The variable file_name
is a representation of a path to a
file, such as /home/ruby/file.txt
. The myfile
variable in our program is local to only
our program. Two of the most basic access modes and what they do are shown
in Table 11-1.
Mode | Description |
---|---|
| Read access only. Points to start of the file. This is the default, but it is good programming style to specify it anyway. |
| Write access only. Points to the beginning of the file that will overwrite the file’s content if it already exists. |
Gem of Wisdom
Files provide access to data resident in the computer system’s long-term memory. Storage in long-term memory, such as disks, provides data resiliency, namely, permanence. That is, disks work even without power, so they store things far longer than the internal random access memory, which works only if the power is on.
To read a line of characters from a file, call the gets
method on the myfile
object. When the gets
method is called, it reads characters until
it reaches a newline character (\n
),
and then it returns what it read. The File
object keeps track of what has been read
in, so each successive call to gets
will always return the next line, until it reaches the end of the file and
returns nil
. nil
has the logical truth value of false
. Every other value returned by gets
has the logical value of true
. Consider the code provided in Example 11-1, which reads in a file foo.txt
and then prints it out.
Sample code for file reading
1
myfile
=
File
.
open
(
"foo.txt"
,
"r"
)
2
whole_file
=
""
3
4
while
(
input_line
=
myfile
.
gets
)
5
whole_file
+=
input_line
6
end
7
8
puts
"Contents of input file:"
9
puts
whole_file
10
myfile
.
close
()
Line 1 opens
foo.txt
for read access.Line 2 instantiates an empty string variable,
whole_file
, which will be used to store the file to display.Lines 4–6 contain a loop that reads the file into
input_line
, one line at a time, and appends each line intowhole_file
.Line 9 prints out the file.
Sequentially writing to a file is similar to sequentially reading
from a file. Instead of opening the file in read mode, the file needs to
be opened with w
for writing access. To
write text to a file, use the local variable myfile
to call the puts
method:
myfile
.
puts
(
"text goes here!"
)
The code shown in Example 11-2 opens a file for write access, accepts user input, and writes that input to the file. It will then close the file, reopen it, and print the contents to verify correctness.
File read/write example
1
file_a
=
File
.
open
(
"bar.txt"
,
"w"
)
2
3
puts
"Please enter a line of text"
4
line
=
gets
()
5
file_a
.
puts
(
line
)
6
file_a
.
close
()
7
8
file_b
=
File
.
open
(
"bar.txt"
,
"r"
)
9
puts
"Contents of file:"
10
puts
file_b
.
gets
()
11
file_b
.
close
()
Line 1 opens
bar.txt
as file objectfile_a
with write access.Lines 3–4 take text input from the user.
Line 5 writes the user input into
"bar.txt"
.Line 6 closes the
File
objectfile_a
(this saves the text inside it by closing the file and preventing further access to it).Line 8 instantiates a new
File
objectfile_b
. This object will be used for the purpose of reading the content ofbar.txt
. Note the file access moder
.Line 10 outputs the contents of the newly created file to the console, illustrating that the code in Example 11-2 behaves in the desired fashion.
11.2.1 File Reader Class
We will now define two classes that encapsulate reading in a file and writing out a file. Then we will use the two classes to create a file copy program that will read text from a file and then copy the text into a new file.
The class presented in Example 11-3 encapsulates reading and displaying a file.
FileReader class
1
class
FileReader
2
3
def
initialize
(
file_name
)
4
@file
=
File
.
open
(
file_name
,
"r"
)
5
end
6
7
def
read_file
8
whole_file
=
""
9
while
(
input_line
=
@file
.
gets
)
10
whole_file
+=
input_line
11
end
12
13
return
whole_file
14
end
15
16
def
display
17
puts
"Contents of input file:"
18
puts
read_file
19
end
20
21
def
close
22
@file
.
close
()
23
end
24
end
Lines 3–5 define the constructor. The variable
@file
references theFile
object that has been opened.Line 4 opens the file for the purpose of reading the contents. The
r
indicates that this file is to be open for reading.Lines 7–14 define the
read file
method. This method incorporates a basic loop that goes through a given file usinggets
for the purpose of reading one line at a time.Line 10 appends (adds to the end of the string) to
whole_file
the contents of the currently read line of the file.Line 13 returns the contents of the file that is now stored in
whole_file
.Lines 16–19 define a display method that outputs the contents of the file.
Lines 21–23 define a close method that closes the opened file,
@file
.
11.2.2 FileWriter Class
The class in Example 11-4 encapsulates writing to and closing a file.
FileWriter class
1
class
FileWriter
2
3
def
initialize
(
file_name
)
4
@file
=
File
.
open
(
file_name
,
"w"
)
5
end
6
7
def
write_line
(
output_line
)
8
@file
.
puts
(
output_line
)
9
end
10
11
def
close
12
@file
.
close
()
13
end
14
end
Lines 3–5 define the constructor.
Line 4 opens the file
file_name
usingw
for write access mode.Lines 7–9 define the
write_line
method, which outputs a single line,output_line
, to a file associated with theFileWriter
object.Line 8 makes use of the
puts
method to output the contents of a givenoutput_line
to the file referenced by@file
.Lines 11–13 close the file. This ensures that all the data that have been written to the file are actually written to it. That is, data that are temporarily buffered in intermediate storage are actually written to secondary (permanent) storage. Buffering is commonly used to reduce output writing delays.
The above use of buffers may seem obscure for a reader who is not
familiar with the actual mechanisms employed in writing a file. In
reality, the puts
method does not actually write to a
file; it actually fills a holding area commonly referred to as a
buffer.
11.2.3 File Reader/Writer Example
The code in Example 11-5
will read in a file and write the contents out to a new file. This code
assumes you have written out the class definitions for FileReader
and FileWriter
given in the preceding section as files named file_reader.rb
and file_writer.rb
.
FileReader/FileWriter example
1
require_relative
"file_reader.rb"
2
require_relative
"file_writer.rb"
3
4
fr
=
FileReader
.
new
(
"input.txt"
)
5
fw
=
FileWriter
.
new
(
"output.txt"
)
6
7
input
=
fr
.
read_file
()
8
fw
.
write_line
(
input
)
9
10
fw
.
close
()
11
fr
.
close
()
Lines 1 and 2 import the class definitions of
FileReader
andFileWriter
.Lines 4 and 5 create instances of our two classes, the first for input, the second for output.
Line 7 reads the file into a string called
input
.Line 8 writes the string
input
out to the file held by the instance ofFileWriter
.Lines 10 and 11 close both of the open files.
To verify that the code presented in Example 11-5 works correctly, use
the input text file with the content shown in Example 11-6. After running Example 11-5’s code with the input
from Example 11-6, ensure that output.txt
is the same as input.txt
.
Sample input file
Hello
world!
A
mighty
fine
day
for
ruby
programming!
Computer
Science
is
the
best!
11.3 Summary
We described basic file input and output operations.
11.3.1 Key Concepts
Text files in Ruby are contiguous streams of data in the form of characters.
Data can be entered into a program using file access; this is done through file input/output or I/O.
To read from or write to a file, one must access the file through Ruby’s
File
class.
11.3.2 Key Definitions
File access: Opening a file for reading or writing.
Input: Reading information from a stored file.
Output: Writing information to a file for storage.
11.4 Exercises
The game of Go is often played online. Many users save their game data for later analysis. The game is played by two players who take turns placing stones (one player using black stones and the other using white stones) on the intersections of a 19 × 19 board. Create a program to enter each move of a game and save that information to a file.
Write a program that quizzes the user on her or his vocabulary. Make the program read a set of words and definitions from a file; display definitions one at a time, in random order; and prompt for the appropriate word.
Write a program that will keep track of the mileage for oil changes for your car.
Write a program that reads the content of a file and outputs it to the screen.
Write a program that takes input from the user and saves it to a file.
Write a program that creates and stores a simple address book.