• No results found

Interfacing with the operating system

In document Nim in Action (Page 137-142)

A tour through the standard library

4.5 Interfacing with the operating system

The programs that you create will usually require an OS to function. The OS manages your computer’s hardware and software and provides common services for computer programs.

These services are available through a number of OSAPIs, and many of the mod-ules in Nim’s standard library abstract these APIs to provide a single cross-platform Nim API that’s easy to use in Nim code. Almost all of the modules that do so are listed under the “Generic Operating System Services” category in the standard library mod-ule list (https://nim-lang.org/docs/lib.html). These modmod-ules implement a range of OS services, including the following:

Accessing the filesystem

Manipulating file and folder paths

Retrieving environment variables

Reading command-line arguments

Executing external processes

Accessing the current system time and date

Manipulating the time and date

Many of these services are essential to successfully implementing some applications.

In the previous chapter, I showed you how to read command-line arguments and com-municate with applications over a network. Both of these are services provided by the OS, but communicating with applications over a network isn’t in the preceding list because it has its own category in the standard library. I’ll talk about modules that deal with networks and internet protocols in section 4.7.

4.5.1 Working with the filesystem

A typical filesystem consists primarily of files and folders. This is something that the three major OSs thankfully agree on, but you don’t need to look far to start seeing dif-ferences. Even something as simple as a file path isn’t consistent. Take a look at table 4.2, which shows the file path to a file.txt file in the user’s home directory.

Note both the different directory separators and the different locations of what’s known as the home directory. This inconsistency proves problematic when you want to write software that works on all three of these OSs.

The os module defines constants and procedures that allow you to write cross-platform code. The following example shows how to create and write to a new file at each of the file paths defined in table 4.2, without having to write different code for each of the OSs.

import os

let path = getHomeDir() / "file.txt"

writeFile(path, "Some Data")

To give you a better idea of how a path is computed, take a look at table 4.3.

Table 4.2 File paths on different operating systems Operating system Path to file in home directory

Windows C:\Users\user\file.txt Mac OS /Users/user/file.txt Linux /home/user/file.txt

Listing 4.18 Write "Some Data" to file.txt in the home directory

Table 4.3 The results of path-manipulation procedures

Expression Operating system Result

getHomeDir() Windows

Mac OS Linux

C:\Users\username\

/Users/username/

/home/username/

getHomeDir() / "file.txt" Windows Mac OS Linux

C:\Users\username\file.txt /Users/username/file.txt /home/username/file.txt The os module defines the getHomeDir procedure

as well as the / operator used on the second line.

The getHomeDir proc returns the appropriate path to the home directory, depending on the current OS. The / operator is like the & concatenation operator, but it adds a path separator between the home directory and file.txt.

The writeFile procedure is defined in the system module. It simply writes the specified data to the file at the path specified.

THE JOINPATH PROCEDURE You can use the equivalent joinPath instead of the / operator if you prefer; for example, joinPath(getHomeDir(),

"file.txt").

The os module includes other procedures for working with file paths including splitPath, parentDir, tailDir, isRootDir, splitFile, and others. The code in list-ing 4.19 shows how some of them can be used. In each doAssert line, the right side of the == shows the expected result.

import os

doAssert(splitPath("usr/local/bin") == ("usr/local", "bin")) doAssert(parentDir("/Users/user") == "/Users")

doAssert(tailDir("usr/local/bin") == "local/bin") doAssert(isRootDir("/"))

doAssert(splitFile("/home/user/file.txt") == ("/home/user", "file", ".txt"))

The os module also defines the existsDir and existsFile procedures for determin-ing whether a specified directory or file exists. There are also a number of iterators that allow you to iterate over the files and directories in a specified directory path.

import os

for kind, path in walkDir(getHomeDir()):

case kind

of pcFile: echo("Found file: ", path) of pcDir: echo("Found directory: ", path)

of pcLinkToFile, pcLinkToDir: echo("Found link: ", path) Listing 4.19 Path-manipulation procedures

Listing 4.20 Displaying the contents of the home directory Imports the os module to access

the procedures used next. Splits the path into

a tuple containing a head and a tail

Removes the first directory specified in the path and returns the rest Returns the path to the parent directory of the path specified

Splits the specified file path into a tuple containing the directory, filename, and file extension Returns true if the specified directory is a root directory

Imports the os module to access the walkDir iterator and the getHomeDir procedure

Uses the walkDir iterator to go through each of the files in your home directory. The iterator will yield a value whenever a new file, directory, or link is found.

When the path references a file, displays the message "Found file: " together with the file path

Checks what the path variable references:

a file, a directory, or a link

When the path references either a link to a file or a link to a directory, displays the message "Found link: "

together with the link path When the path references a directory, displays the message "Found directory: "

together with the directory path

The os module also implements many more procedures, iterators, and types for deal-ing with the filesystem. The Nim developers have ensured that the implementation is flexible and that it works on all OSs and platforms. The amount of functionality implemented in this module is too large to fully explore in this chapter, so I strongly recommend that you look at the os module’s documentation yourself (http://nim-lang.org/docs/os.html). The documentation includes a list of all the procedures defined in the module, together with examples and explanations of how those proce-dures can be used effectively.

4.5.2 Executing an external process

You may occasionally want your application to start up another program. For example, you may wish to open your website in the user’s default browser. One important thing to keep in mind when doing this is that the execution of your application will be blocked until the execution of the external program finishes. Executing processes is currently completely synchronous, just like reading standard input, as discussed in the previous chapter.

The osproc module defines multiple procedures for executing a process, and some of them are simpler than others. The simpler procedures are very convenient, but they don’t always allow much customization regarding how the external process should be executed, whereas the more complex procedures do provide this.

The simplest way to execute an external process is using the execCmd procedure. It takes a command as a parameter and executes it. After the command completes exe-cuting, execCmd returns the exit code of that command. The standard output, stan-dard error, and stanstan-dard input are all inherited from your application’s process, so you have no way of capturing the output from the process.

The execCmdEx procedure is almost identical to the execCmd procedure, but it returns both the exit code of the process and the output. The following listing shows how it can be used.

import osproc

when defined(windows):

let (ver, _) = execCmdEx("cmd /C ver") else:

let (ver, _) = execCmdEx("uname -sr") echo("My operating system is: ", ver)

Listing 4.21 Using execCmdEx to determine some information about the OS Imports the osproc module where

the execCmdEx proc is defined Checks whether this Nim code is being compiled on Windows

If this Nim code is being compiled on Windows, executes cmd /C ver using execCmdEx and unpacks the tuple it returns into two variables If this Nim code is not being compiled on Windows, executes uname -sr using execCmdEx and unpacks the tuple it returns into two variables

Displays the output from the executed command

You can compile and run this application and see what’s displayed. Figure 4.7 shows the output of listing 4.21 on my MacBook.

Keep in mind that this probably isn’t the best way to determine the current OS version.

GETTING THE CURRENT OS There’s an osinfo package available online that uses the OSAPI directly to get OS information (https://github.com/nim-lang/osinfo).

Listing 4.21 also shows the use of an underscore as one of the identifiers in the unpacked tuple; it tells the compiler that you’re not interested in a part of the tuple.

This is useful because it removes warnings the compiler makes about unused variables.

That’s the basics of executing processes using the osproc module, together with a bit of new Nim syntax and semantics. The osproc module contains other procedures that allow for more control of processes, including writing to the process’s standard input and running more than one process at a time. Be sure to look at the documen-tation for the osproc module to learn more.

Figure 4.7 The output of listing 4.21

The compile-time if statement

In Nim, the when statement (introduced in chapter 2) is similar to an if statement, with the main difference being that it’s evaluated at compile time instead of at runtime.

In listing 4.21, the when statement is used to determine the OS for which the current module is being compiled. The defined procedure checks at compile time whether the specified symbol is defined. When the code is being compiled for Windows, the windows symbol is defined, so the code immediately under the when statement is compiled, whereas the code in the else branch is not. On other OSs, the code in the else branch is compiled and the preceding code is ignored.

The scope rules for when are also a bit different from those for if. A when statement doesn’t create a new scope, which is why it’s possible to access the ver variable outside it.

4.5.3 Other operating system services

There are many other modules that allow you to use the services provided by OSs, and they’re part of the “Generic Operating System Services” category of the standard library. Some of them will be used in later chapters; others, you can explore on your own. The documentation for these modules is a good resource for learning more:

http://nim-lang.org/docs/lib.html#pure-libraries-generic-operating-system-services

In document Nim in Action (Page 137-142)