πŸ“œ ⬆️ ⬇️

[Translation] Working with files in the programming language D

This is a translation of Gary Willoughby's article β€œ Working with files in the D programming language, ” published September 28, 2015.

This post was inspired by an article written a few weeks ago and titled Working with Files in Go . In this article, the author describes many ways to interact with files, dwelling in detail on the features of the Go language. And I thought to write a companion post, this time describing how to work with files in the D language.

Interaction with files is one of the fundamental tasks of any programming language, and although the solution of such problems is a common phenomenon, it may not always be obvious exactly how some problems are solved in the D language. .
')
Some of the code examples below use the so-called Uniform Function Call Syntax (UFCS). Let him not confuse you: his simple explanation can be found here (for the time being, too, English - approx. Transl. ).

Read and write


Open and close files


The following code shows how to open and close files in a safe way. Generally speaking, D provides thin wrappers around the functions of the standard C library; however, working with file descriptors is not safe and leads to errors for many reasons. File type provides safe operation, automatic file closing and other convenient features. The underlying descriptor is stored with reference counting , so when the last File variable is out of scope, the descriptor is automatically closed.

 import core.stdc.errno; import std.exception; import std.stdio; void main(string[] args) { try { //   File β€”     ,   //    fopen   C. // // r β€”    .   . // w β€”     .      //   ,    ,  //   . // a β€”       .   //      ,   . //    . //    ,  . // r+ β€”     (  ). //   . // w+ β€”         (  //  ).       , //   ,    . // a+ β€”     (  ),   //       . //       , //        . //    ,  . auto file = File("test.txt", "r"); //   ,     , //  . file.close(); // . .:  -      ,   //     ,     // Go' defer: // scope(exit) file.close(); } catch (ErrnoException ex) { switch(ex.errno) { case EPERM: case EACCES: //   break; case ENOENT: //    break; default: //    break; } } } 

If an exception is thrown, an error has occurred with access to the file, and to find out what went wrong, you can access the errno property of this exception.

Since the File type is a wrapper around a C language function, the returned error code will be one of the constants defined in core.stdc.errno . This is the main way to access files and handle errors. Extended information can be collected using the std.file.getAttributes function, which returns an unsigned integer. This number contains several bit flags that are set differently depending on the operating system. More information about these flags can be found here .

Documentation

Looking for position in file


Sometimes before you start reading or writing, you need to go to a specific position in the file. The following example shows how to change a position from different starting points.

 import std.exception; import std.stdio; void main(string[] args) { try { auto file = File("test.txt", "r"); //   10    . file.seek(10, SEEK_SET); //   2     . file.seek(-2, SEEK_CUR); //   4     . file.seek(-4, SEEK_END); //    . auto pos = file.tell(); //     . file.rewind(); } catch (ErrnoException ex) { //   } } 

Documentation

Write the bytes to the file.


This example shows how to write bytes to a file.

 import std.exception; import std.stdio; void main(string[] args) { try { byte[] data = [0x68, 0x65, 0x6c, 0x6c, 0x6f]; auto file = File("test.txt", "w"); file.rawWrite(data); } catch (ErrnoException ex) { //   } } 

Documentation

Quick write to file


Sometimes it’s great to just dump the data buffer to a file and leave the library open and close. The following example shows how this is done.

 import std.file; void main(string[] args) { try { write("test.txt", [0x68, 0x65, 0x6c, 0x6c, 0x6f]); } catch (FileException ex) { //   } } 

This is writing a byte array to a file, but it could just as easily have been a string.

Documentation

Write lines to file


When working with files, there is always a lot of writing and reading lines from a file. The following are various ways to write a string to a file.

 import std.exception; import std.stdio; void main(string[] args) { try { auto file = File("test.txt", "w"); //  . file.write("1: Lorem ipsum\n"); //  ,      . file.writeln("2: Lorem ipsum"); //   . file.writef("3: %s", "Lorem ipsum\n"); //   ,      . file.writefln("4: %s", "Lorem ipsum"); } catch (ErrnoException ex) { //   } } 

These methods may be more convenient depending on different scenarios.

Documentation

Using I / O Buffer Before Writing To File


As an optimization technique, it is sometimes necessary to write data to a buffer in RAM before writing to a file in order to save on I / O to disk . The following example shows one of the many ways to create and write such a buffer.

 import std.file; import std.outbuffer; void main(string[] args) { auto buffer = new OutBuffer(); ubyte[] data = [0x68, 0x65, 0x6c, 0x6c, 0x6f]; buffer.write(data); buffer.write(' '); buffer.write("world"); try { write("test.txt", buffer.toBytes()); } catch (FileException ex) { //   } } 

Using the buffer in this way allows you to quickly write data to memory before writing to disk. This saves time in the absence of small stages of writing to a disc and reduces wear on the disc itself.

Documentation

Read bytes from the file


The following example shows how to read bytes from a file.

 import std.exception; import std.stdio; void main(string[] args) { try { byte[] buffer; buffer.length = 1024; auto file = File("test.txt", "r"); auto data = file.rawRead(buffer); } catch (ErrnoException ex) { //   } } 

When reading bytes in this way, you must provide a buffer that will accept read data. In this example, a dynamic array is used for such a buffer and 1024 bytes are allocated before reading. The rawRead method fills the buffer and returns a slice of this buffer. The buffer length is the maximum number of bytes that will be read.

Documentation

Quick read from file


Sometimes it is great to just read the data from the file and leave the library to open and close the file. Below is how to do it.

 import std.file; void main(string[] args) { try { auto data = cast(byte[]) read("test.txt"); } catch (FileException ex) { //   } } 

As a result, an array of type void is returned. It can be reduced to some more useful type. In this example, it has been cast to a "byte array" type.

Documentation

Read n bytes from file


In this example, the read function is used again, but this time with the second parameter specifying the maximum number of bytes to read. If the file is less than the specified limit, only the data from the file will be returned.

 import std.file; void main(string[] args) { try { auto data = cast(byte[]) read("test.txt", 5); } catch (FileException ex) { //   } } 

As in the previous cases, the result can be converted to other types of arrays.

Documentation

Reading a file in chunks


In the following example, the file is read in portions of 1024 bytes.

 import std.exception; import std.stdio; void main(string[] args) { try { auto file = File("test.txt", "r"); foreach (buffer; file.byChunk(1024)) { //   buffer } } catch (ErrnoException ex) { //   } } 

The byChunk method returns an input byte range that reads only a specified portion of a file descriptor at a time. In this case, each call will return a maximum of 1024 bytes. For each call, the buffer is used again, so if you need to save data between calls, you need to copy it.

Documentation

We read lines from the file


These examples show how to read lines from a file.

 import std.exception; import std.stdio; void main(string[] args) { try { auto file = File("test.txt", "r"); string line; while ((line = file.readln()) !is null) { //   line } } catch (ErrnoException ex) { //   } } 

Although the above example is convenient for reading lines from a file, it has one drawback: the readln function allocates a new buffer to read each line.

Because of this, due to a possible performance problem, there is an overloaded method that takes a buffer as a parameter:

 import std.exception; import std.stdio; void main(string[] args) { try { auto file = File("test.txt", "r"); char[] buffer; while (file.readln(buffer)) { //   buffer } } catch (ErrnoException ex) { //   } } 

This buffer can then be used again for each row read (which improves performance). The disadvantage of this method is that if you need to save data between calls, you have to copy it. D lets you decide which is best.

Documentation

We read the file as a range of lines


Reading a file as a range allows you to use a variety of typical algorithms defined in the Phobos library. The example below shows how this is done.

 import std.exception; import std.stdio; void main(string[] args) { try { auto file = File("test.txt", "r"); foreach (line; file.byLine) { //   line } } catch (ErrnoException ex) { //   } } 

The byLine method returns an input range that reads one line at a time from a file descriptor. With each call, the buffer is used again, so if you need to save data between calls, you must copy it. However, there is a convenient byLineCopy method that does this automatically.

Documentation

Fast reading of the whole file as one line


The following shows how to read the entire file into a single variable of type "string".

 import std.file; import std.utf; void main(string[] args) { try { //    UTF8-. auto utf8Data = readText("test.txt"); //    UTF16-. auto utf16Data = readText!(wstring)("test.txt"); //    utf32-.. auto utf32Data = readText!(dstring)("test.txt"); } catch (UTFException ex) { //    } catch (FileException ex) { //   } } 

This code reads and checks the text file. Conversion of encodings (character widths) is not performed. If the width of characters in the file does not match the specified string type, validation will fail.

Documentation

Basic operations


Creating an empty file


The following code creates an empty file (if it does not already exist) when initializing a File type structure. If a file with the same name already exists, its contents are deleted, and the file is considered empty.

 import std.exception; void main(string[] args) { try { File("test.txt", "w"); } catch (ErrnoException ex) { //   } } 

Documentation

Check for the existence of a file


This code simply checks if the file exists.

 import std.file; void main(string[] args) { if (exists("test.txt")) { //   } } 

Documentation

Rename and move file


This code renames and / or moves the file. If the target file exists, it will be overwritten.

 import std.file; void main(string[] args) { try { rename("source.txt", "destination.txt"); } catch (FileException ex) { //   } } 

Documentation

Copy file


This code copies the file. If the target file exists, it will be overwritten.

 import std.file; void main(string[] args) { try { copy("source.txt", "destination.txt"); } catch (FileException ex) { //   } } 

Documentation

Delete file


This code simply deletes the file.

 import std.file; void main(string[] args) { try { remove("test.txt"); } catch (FileException ex) { //   } } 

Documentation

Getting file information


This code receives information about the file just as you would have done with the stat command ( English: sorry, there is no Russian-language article in Wikipedia ) in a POSIX-compatible operating system. Below is shown getting only cross-platform information. Other information is available depending on the operating system; it can be obtained by decoding the attributes property.

 import std.file; import std.stdio : writefln; void main(string[] args) { try { auto file = DirEntry("test.txt"); writefln(" : %s", file.name); writefln(" : %s", file.isDir); writefln(" : %s", file.isFile); writefln("  : %s", file.isSymlink); writefln("  : %s", file.size); writefln("  : %s", file.timeLastAccessed); writefln("  : %s", file.timeLastModified); writefln(": %b", file.attributes); } catch (FileException ex) { //   } } 

Documentation

Truncate existing file


This code truncates the existing file to 100 bytes. If the source file is smaller, truncation does not occur.

 import std.file; void main(string[] args) { auto file = "test.txt"; auto size = 100; try { if (file.exists() && file.isFile()) { write(file, read(file, size)); } } catch (FileException ex) { //   } } 

Documentation

Archiving


Create zip archive


Based on the following examples, this code shows how to create a zip archive.

 import std.file; import std.outbuffer; import std.string; import std.zip; void main(string[] args) { try { auto file = new ArchiveMember(); file.name = "test.txt"; auto data = new OutBuffer(); data.write("Lorem ipsum"); file.expandedData = data.toBytes(); auto zip = new ZipArchive(); zip.addMember(file); write("test.zip", zip.build()); } catch (ZipException ex) { //   } } 

Documentation

Zip archive reading


The following example shows how to read a zip archive.

 import std.file; import std.zip; void main(string[] args) { try { auto zip = new ZipArchive(read("test.zip")); foreach (filename, member; zip.directory) { auto data = zip.expand(member); //   data } } catch (ZipException ex) { //   } } 

Documentation

Data compression


Write compressed data to file


The following example shows how to compress data before sending it to a file.

 import std.file; import std.zlib; void main(string[] args) { try { auto data = compress("Lorem ipsum dolor sit amet"); write("test.dat", data); } catch (ZlibException ex) { //   } } 

In the previous example, the string is compressed, but you can compress any data. The std.zlib module uses the C language Zlib library.

Documentation

Reading compressed data from a file


The following shows how to read compressed data from a file.

 import std.file; import std.zlib; void main(string[] args) { try { auto data = uncompress(read("test.dat")); //    } catch (ZlibException ex) { //   } } 

Documentation

POSIX operations


Change file permissions


This code modifies file permissions on POSIX-compatible operating systems, such as Linux or Mac OS . The Phobos library does not have a cross-platform solution for this task, so we can only use POSIX-specific system calls .

 import core.stdc.errno; import core.sys.posix.sys.stat; import std.conv; import std.string; void main(string[] args) { auto file = "test.txt"; auto result = chmod(file.toStringz(), octal!(666)); if (result != 0) { switch(errno) { case EPERM: case EACCES: //   break; case ENOENT: //    break; default: //    break; } } } 

The chmod system call works exactly the same as the chmod from the Unix shell . The file name and its new permissions (expressed as an octal number) are indicated. To modify a file this way, you need permissions on the operation itself. To do this, you need to be the owner of the file or superuser .

Documentation

File ownership change


This code changes the ownership of the file on POSIX-compatible systems. When you become the owner, you can change the permissions of the file without being the superuser.

 import core.stdc.errno; import core.sys.posix.pwd; import core.sys.posix.unistd; import std.string; void main(string[] args) { auto username = "gary"; auto file = "test.txt"; auto record = getpwnam(username.toStringz()); if (record !is null) { auto user = record.pw_uid; auto group = record.pw_gid; auto result = chown(file.toStringz(), user, group); if (result != 0) { switch(errno) { case EPERM: //   break; default: //    break; } } } } 

The chown system call works exactly the same as the Unix shell's chown command. The file name and its new owner and group are indicated. To change the owner of the file, your program must have superuser rights.

Documentation

Creating hard and symbolic links


Sometimes in POSIX-compatible systems, you may need to create a hard or symbolic link. The following example shows how to create a hard link.

 import core.stdc.errno; import core.sys.posix.unistd; import std.string; void main(string[] args) { auto file = "test.txt"; auto linked = "link.txt"; auto result = link(file.toStringz(), linked.toStringz()); if (result != 0) { switch(errno) { case EPERM: case EACCES: //   break; case EEXIST: //       break; case ENOENT: //    default: //    break; } } } 

To create a symbolic link, replace the line
auto result = link(file.toStringz(), linked.toStringz());
by string
auto result = symlink(file.toStringz(), linked.toStringz());

Conclusion


Rarely, there is one canonical way of working with files, and developers like to perform different file tasks in their own, special way. I hope this article showed the power and convenience of the D language and allowed us to select the convenient functions of the standard library for working with files.

Source: https://habr.com/ru/post/270189/


All Articles