GoLanguage Core 36 Speech (GoLanguage Practice and Application 22) --Learning Notes

44 | Use the API in the os package (above)

What we're going to talk about today is the API in the os code package. This code package gives us the ability to operate a computer's operating system.

Leading content: API s in os packages

This code package provides platform-independent APIs. So, what is a platform-independent API?

It means that these API s are based (or abstracted from) the operating system and provide high-level support for the functionality of the operating system, but they are not dependent on a specific operating system.

Whether it's Linux, macOS, Windows, or FreeBSD, OpenBSD, Plan9, the os code package provides a unified interface for its use. This allows us to manipulate different operating systems in the same way and achieve similar results.

The API s in the os package primarily help us use the file system, permission system, environment variables, system processes, and system signals in the operating system.

Among them, APIs for manipulating file systems are the most abundant. Not only can we use these APIs to create and delete files and directories, but we can also get a variety of information about them, modify their contents, change their access rights, and so on.

When it comes to this, you have to mention a very useful data type: os.File.

Literally, the os.File type represents files in the operating system. But in fact, it can represent much more than that. Perhaps you already know that for Unix-like operating systems (including Linux, macOS, FreeBSD, and so on), everything can be viewed as a file.

In addition to the common forms of text files, binaries, compressed files, directories, there are symbolic links, various physical devices (including built-in or external block-or character-oriented devices), named pipes, socket s, and so on.

So, there are so many things we can manipulate with the os.File type. However, in order to focus on os.File itself, and also to make the content described in this article more general, we will mainly apply the os.File type to regular files here.

The following question starts with the most basic content represented by the os.File type. Our question today is: What interfaces in the io package do the os.File types implement?

The typical answer to this question is this.

The os.File type has pointer methods, so it does not implement any interfaces except empty ones. Its pointer type implements interfaces in many io code packages.

First, the *os.File type implements the three core simple interfaces, io.Reader, io.Writer, and io.Closer, in the IO package.

Second, it implements three other simple interfaces: io.ReaderAt, io.Seeker, and io.WriterAt.

Because the *os.File type implements these simple interfaces, it also conveniently implements seven of the nine extension interfaces of the io package.

However, since it does not implement the simple interfaces io.ByteReader and io.RuneReader, it does not implement io.ByteScanner and io.RuneScanner, which are the extension interfaces for both.

In summary, the values of os.File type and its pointer type can not only read and write the contents of a file in a variety of ways, but also find and set the starting index position for the next read or write, and also close the file at any time.

However, they do not specifically read the next byte in the file, or the next Unicode character, nor do they perform any read-back operations.

However, the ability to read the next byte or character individually can be accomplished in other ways, such as by calling its Read method and passing in the appropriate parameter values.

Problem resolution

This question actually indirectly asks, "How can the os.File type manipulate files?" I also gave a brief answer in the typical answer above.

Before I go any further, let's see how you can get a pointer value of type os.File (hereinafter referred to as the File value).

In the os package, there are several functions: Create, NewFile, Open, and OpenFile.

The os.Create function is used to create a new file based on a given path.   It returns a File value and an error value. We can read and write files on top of the File value returned by this function.

What's more, the files we create with this function are readable and writable to all users of the operating system.

In other words, once such a file is created, anyone who can log on to the operating system to which it belongs can read or write to it at any time.

Note that if a file already exists on the path we give the os.Create function, it will empty the entire contents of the existing file before returning it as the first result value.

In addition, it is possible for the os.Create function to return non-nil error values. For example, if a parent directory on a given path does not exist, the function returns an error value of type *os.PathError to indicate "a file or directory that does not exist".

Let's look at the os.NewFile function again.   When called, the function accepts a value of type uintptr representing the file descriptor and a string value representing the file name.

If the given file descriptor is not valid, this function will return nil, otherwise it will return a File value representing the corresponding file.

Note that don't be misled by the name of this function; instead of creating a new file, it creates a new File value that wraps the file based on the descriptor of an existing file.

For example, we can get a File value that wraps the standard error output like this:

file3 := os.NewFile(uintptr(syscall.Stderr), "/dev/stderr")

Then, write something to the standard error output through this File value:

if file3 != nil {
 defer file3.Close()
 file3.WriteString(
  "The Go language program writes the contents into stderr.\n")
}

The os.Open function opens a file and returns the File value that wraps it.   However, the function can only open files in read-only mode. In other words, we can only read from the File value returned by the function, not write anything to it.

If we invoke any of the write methods for this File value, we will get an error value indicating a "bad file descriptor". In fact, the read-only mode we just mentioned applies to the file descriptors held by the File value.

The so-called file descriptor is represented by usually small, non-negative integers. It is typically returned by I/O-related system calls and exists as an identity of a file.

At the operating system level, this file descriptor is required for I/O operations on any file. However, some data types in the Go language hide this descriptor for us so that we don't have to keep an eye on and identify it (like the os.File type).

In fact, when we call the os.Create, os.Open, and os.OpenFile functions described earlier, they all execute the same system call and get such a file descriptor after success. This file descriptor will be stored in the File value they return.

The os.File type has a pointer method called Fd. It returns a value of type uintptr after being called. This value represents the file descriptor held by the current File value.

However, in the os package, there is no use for it other than the NewFile function. So if you're only working with regular files or directories, you don't need to care about them in particular.

Finally, let's talk about the os.OpenFile function.   This function is actually the underlying support for the os.Create and os.Open functions, and it is the most flexible.

This function has three parameters, named name, flag, and perm. The name refers to the path of the file. The flag parameter refers to the pattern that needs to be imposed on the file descriptor, and the read-only pattern I mentioned earlier is one of the options here.

In the Go language, this read-only mode is made up of the constant os.O_RDONLY stands for, it is of type int. Of course, there are several other modes to choose from here besides the read-only mode, which we will discuss later.

The parameter perm of the os.OpenFile function also represents a pattern, and its type is os.FileMode, which is a redefined type based on uint32.

To make a difference, we call the mode that the parameter flag refers to an operation mode, and the mode that the parameter perm refers to a permission mode. In other words, the operation mode restricts how the file is manipulated, while the permission mode controls access to the file. More details about the permission mode will be discussed later.

(Several ways to get pointer values of type os.File)

Here, you need to remember that with values of type os.File, we can not only read, write, close files, but also set the starting index position for the next read or write.

In addition, the os package includes Create functions for creating new files, NewFile functions for wrapping existing files, and Open and OpenFile functions for opening existing files.

package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"reflect"
	"syscall"
)

// ioTypes represent the reflection type of all interfaces in the io code package.
var ioTypes = []reflect.Type{
	reflect.TypeOf((*io.Reader)(nil)).Elem(),
	reflect.TypeOf((*io.Writer)(nil)).Elem(),
	reflect.TypeOf((*io.Closer)(nil)).Elem(),

	reflect.TypeOf((*io.ByteReader)(nil)).Elem(),
	reflect.TypeOf((*io.RuneReader)(nil)).Elem(),
	reflect.TypeOf((*io.ReaderAt)(nil)).Elem(),
	reflect.TypeOf((*io.Seeker)(nil)).Elem(),
	reflect.TypeOf((*io.WriterTo)(nil)).Elem(),
	reflect.TypeOf((*io.ByteWriter)(nil)).Elem(),
	reflect.TypeOf((*io.WriterAt)(nil)).Elem(),
	reflect.TypeOf((*io.ReaderFrom)(nil)).Elem(),

	reflect.TypeOf((*io.ByteScanner)(nil)).Elem(),
	reflect.TypeOf((*io.RuneScanner)(nil)).Elem(),
	reflect.TypeOf((*io.ReadSeeker)(nil)).Elem(),
	reflect.TypeOf((*io.ReadCloser)(nil)).Elem(),
	reflect.TypeOf((*io.WriteCloser)(nil)).Elem(),
	reflect.TypeOf((*io.WriteSeeker)(nil)).Elem(),
	reflect.TypeOf((*io.ReadWriter)(nil)).Elem(),
	reflect.TypeOf((*io.ReadWriteSeeker)(nil)).Elem(),
	reflect.TypeOf((*io.ReadWriteCloser)(nil)).Elem(),
}

func main() {
	// Example 1.
	file1 := (*os.File)(nil)
	fileType := reflect.TypeOf(file1)
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "Type %T implements\n", file1)
	for _, t := range ioTypes {
		if fileType.Implements(t) {
			buf.WriteString(t.String())
			buf.WriteByte(',')
			buf.WriteByte('\n')
		}
	}
	output := buf.Bytes()
	output[len(output)-2] = '.'
	fmt.Printf("%s\n", output)

	// Example 2.
	fileName1 := "something1.txt"
	filePath1 := filepath.Join(os.TempDir(), fileName1)
	var paths []string
	paths = append(paths, filePath1)
	dir, _ := os.Getwd()
	paths = append(paths, filepath.Join(dir[:len(dir)-1], fileName1))
	for _, path := range paths {
		fmt.Printf("Create a file with path %s ...\n", path)
		_, err := os.Create(path)
		if err != nil {
			var underlyingErr string
			if _, ok := err.(*os.PathError); ok {
				underlyingErr = "(path error)"
			}
			fmt.Printf("error: %v %s\n", err, underlyingErr)
			continue
		}
		fmt.Println("The file has been created.")
	}
	fmt.Println()

	// Example 3.
	fmt.Println("New a file associated with stderr ...")
	file3 := os.NewFile(uintptr(syscall.Stderr), "/dev/stderr")
	if file3 != nil {
		file3.WriteString(
			"The Go language program writes something to stderr.\n")
	}
	fmt.Println()

	// Example 4.
	fmt.Printf("Open a file with path %s ...\n", filePath1)
	file4, err := os.Open(filePath1)
	if err != nil {
		fmt.Printf("error: %v\n", err)
		return
	}
	fmt.Println("Write something to the file ...")
	_, err = file4.WriteString("something")
	var underlyingErr string
	if _, ok := err.(*os.PathError); ok {
		underlyingErr = "(path error)"
	}
	fmt.Printf("error: %v %s\n", err, underlyingErr)
	fmt.Println()

	// Example 5.
	fmt.Printf("Open a file with path %s ...\n", filePath1)
	file5a, err := os.Open(filePath1)
	if err != nil {
		fmt.Printf("error: %v\n", err)
		return
	}
	fmt.Printf(
		"Is there only one file descriptor for the same file in the same process? %v\n",
		file5a.Fd() == file4.Fd())
	file5b := os.NewFile(file5a.Fd(), filePath1)
	fmt.Printf("Can the same file descriptor represent the same file? %v\n",
		file5b.Name() == file5a.Name())
	fmt.Println()

	// Example 6.
	fmt.Printf("Reuse a file on path %s ...\n", filePath1)
	file6, err := os.OpenFile(filePath1, os.O_WRONLY|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Printf("error: %v\n", err)
		return
	}
	contents := "something"
	fmt.Printf("Write %q to the file ...\n", contents)
	n, err := file6.WriteString(contents)
	if err != nil {
		fmt.Printf("error: %v\n", err)
	} else {
		fmt.Printf("The number of bytes written is %d.\n", n)
	}
}

summary

Today we are talking about os code packages and the program entities within them. We first discuss the significance of the existence of os packages and their main uses. The API s included in the code package are high-level abstractions of some aspect of the operating system functionality, which allow us to manipulate different operating systems in a unified way and achieve similar results.

In this code package, the API to manipulate the file system is the richest, most representative of which is the data type os.File. The os.File type can represent not only files in the operating system, but many other things as well. Especially in Unix-like operating systems, it represents almost all the software and hardware that can be manipulated.

Note Source

https://github.com/MingsonZheng/go-core-demo

 

Tags: Go

Posted on Mon, 06 Dec 2021 13:13:35 -0500 by nodster