Files
We have seen in the previous chapter that the standard input and output streams can be redirected to and from files and other programs with the >
, <
, and |
operators on the terminal. Despite being very powerful, these tools are not suitable in every situation because in many cases programs can not complete their tasks simply by reading from their input and writing to their output.
For example, a program that deals with student records may use its standard output to display the program menu. Such a program would need to write the student records to an actual file instead of to stdout
.
In this chapter, we will cover reading from and writing to files of file systems.
Fundamental concepts
Files are represented by the File
struct of the std.stdio
module. Since I haven't introduced structs yet, we will have to accept the syntax of struct construction as is for now.
Before getting to code samples we have to go through fundamental concepts about files.
The producer and the consumer
Files that are created on one platform may not be readily usable on other platforms. Merely opening a file and writing data to it may not be sufficient for that data to be available on the consumer's side. The producer and the consumer of the data must have already agreed on the format of the data that is in the file. For example, if the producer has written the id and the name of the student records in a certain order, the consumer must read the data back in the same order.
Additionally, the code samples below do not write a byte order mark (BOM) to the beginning of the file. This may make your files incompatible with systems that require a BOM. (The BOM specifies in what order the UTF code units of characters are arranged in a file.)
Access rights
File systems present files to programs under certain access rights. Access rights are important for both data integrity and performance.
When it comes to reading, allowing multiple programs to read from the same file can improve performance, because the programs will not have to wait for each other to perform the read operation. On the other hand, when it comes to writing, it is often beneficial to prevent concurrent accesses to a file, even when only a single program wants to write to it. By locking the file, the operating system can prevent other programs from reading partially written files, from overwriting each other's data and so on.
Opening a file
The standard input and output streams stdin
and stdout
are already open when programs start running. They are ready to be used.
On the other hand, normal files must first be opened by specifying the name of the file and the access rights that are needed. As we will see in the examples below, creating a File
object is sufficient to open the file specified by its name:
File file = File("student_records", "r");
Closing a file
Any file that has been opened by a program must be closed when the program finishes using that file. In most cases the files need not be closed explicitly; they are closed automatically when File
objects are terminated automatically:
if (aCondition) { // Assume a File object has been created and used here. // ... } // ← The actual file would be closed automatically here // when leaving this scope. No need to close explicitly.
In some cases a file object may need to be re-opened to access a different file or the same file with different access rights. In such cases the file must be closed and re-opened:
file.close(); file.open("student_records", "r");
Writing to and reading from files
Since files are character streams, input and output functions writeln
, readf
, etc. are used exactly the same way with them. The only difference is that the name of the File
variable and a dot must be typed:
writeln("hello"); // writes to the standard output stdout.writeln("hello"); // same as above file.writeln("hello"); // writes to the specified file
eof()
to determine the end of a file
The eof()
member function determines whether the end of a file has been reached while reading from a file. It returns true
if the end of the file has been reached.
For example, the following loop will be active until the end of the file:
while (!file.eof()) { // ... }
The std.file
module
The std.file
module contains functions and types that are useful when working with contents of directories. For example, exists
can be used to determine whether a file or a directory exists on the file systems:
import std.file; // ... if (exists(fileName)) { // there is a file or directory under that name } else { // no file or directory under that name }
std.stdio.File
struct
The File
struct is included in the std.stdio
module. To use it you specify the name of the file you want to open and the desired access rights, or mode. It uses the same mode characters that are used by fopen
of the C programming language:
Mode | Definition |
---|---|
r | read access the file is opened to be read from the beginning |
r+ | read and write access the file is opened to be read from and written at the beginning |
w | write access if the file does not exist, it is created as empty if the file already exists, its contents are cleared |
w+ | read and write access if the file does not exist, it is created as empty if the file already exists, its contents are cleared |
a | append access if the file does not exist, it is created as empty if the file already exists, its contents are preserved and it is opened to be written at the end |
a+ | read and append access if the file does not exist, it is created as empty if the file already exists, its contents are preserved and the file is opened to be read from the beginning and written at the end |
A 'b' character may be added to the mode string, as in "rb". This may have an effect on platforms that support the binary mode, but it is ignored on all POSIX systems.
Writing to a file
The file must have been opened in one of the write modes first:
import std.stdio; void main() { File file = File("student_records", "w"); file.writeln("Name : ", "Zafer"); file.writeln("Number: ", 123); file.writeln("Class : ", "1A"); }
As you remember from the Strings chapter, the type of literals like "student_records"
is string
, consisting of immutable characters. For that reason, it is not possible to construct File
objects by using mutable text to specify the file name (e.g. char[]
). When needed, call the .idup
property of the mutable string to get an immutable copy.
The program above creates or overwrites the contents of a file named student_records
in the directory that it has been started under (in the program's working directory).
Note: File names can contain any character that is legal for that file system. To be portable, I will use only the commonly supported ASCII characters.
Reading from a file
To read from a file the file must first have been opened in one of the read modes:
import std.stdio; import std.string; void main() { File file = File("student_records", "r"); while (!file.eof()) { string line = strip(file.readln()); writeln("read line -> |", line); } }
The program above reads all of the lines of the file named student_records
and prints those lines to its standard output.
Exercise
Write a program that takes a file name from the user, opens that file, and writes all of the non-empty lines of that file to another file. The name of the new file can be based on the name of the original file. For example, if the original file is foo.txt
, the new file can be foo.txt.out
.