Go language core 36 lecture (go language practice and application 22) -- learning notes

44 | using API in os package (Part 1)

Today we are going to talk about the API in the os code package. This code package allows us to control the computer operating system.

Leading content: API in os package

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

It means that these API s are based on (or abstract from) the operating system and provide high-level support for us to use the functions of the operating system. However, they do not depend on the specific operating system.

The os code package can provide a unified interface for Linux, macOS, Windows, FreeBSD, OpenBSD and Plan9. This allows us to manipulate different operating systems in the same way and get similar results.

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

Among them, the API for manipulating the file system is the most abundant. We can not only use these APIs to create and delete files and directories, but also obtain their various information, modify their contents, change their access permissions, and so on.

At this point, I have to mention a very common data type: os.File.

Literally, the os.File type represents a file 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, etc.), everything can be regarded as files.

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

Therefore, it can be said that there are too many things we can manipulate with os.File type. However, in order to focus on os.File itself and make the content described in this article more general, we mainly apply os.File type to conventional files here.

The following problem starts with the most basic content represented by the OS. File type. Our question today is: what interfaces in the io package do 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 interface except the empty interface. Its pointer type implements the interfaces in many io code packages.

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

Secondly, this type also implements three other simple interfaces: io.ReaderAt, io.Seeker and io.WriterAt.

Because the * os.File type implements these simple interfaces, it also implements 7 of the 9 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 as their extension interfaces respectively.

In short, the values of os.File type and its pointer type can not only read and write the contents of a file in various ways, but also find and set the starting index position for the next reading or writing. In addition, the file can be closed at any time.

However, they cannot specifically read the next byte or the next Unicode character in the file, nor can they perform any read fallback operation.

However, the function of reading the next byte or character separately can also be realized in other ways. For example, this can be achieved by calling its Read method and passing in appropriate parameter values.

Problem analysis

In fact, this question indirectly asks "how can os.File type operate files?" I also gave a brief answer in the previous typical answer.

Before I explain some details further, let's see how to obtain a pointer value of os.File type (hereinafter referred to as 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 according to the given path. It returns a File value and an error value. We can read and write the corresponding File above the File value returned by the function.

Moreover, the files created by this function can be read and written by all users in the operating system.

In other words, once such a file is created, any user who can log in to its operating system can read the contents of the file or write contents to the file at any time.

Note that if a file already exists above the path we gave the os.Create function, the function will empty all the contents of the existing file first, and then return it as the first result value.

In addition, the os.Create function may return a non nil error value. For example, if a certain level of parent directory on the given path does not exist, the function will return an error value of * os.PathError to indicate "nonexistent file or directory".

Let's look at the os.NewFile function. When the function is called, it needs to accept a value of uintptr type 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: don't be misled by the name of this function. Its function is not to create a new File, but to create a new File value wrapping the File according to the descriptor of an existing File.

For example, we can get a File value wrapped with 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 the File. However, this function can only open files in read-only mode. In other words, we can only read from the File value returned by the function, and we can't write anything to it.

If we call any write method of this File value, we will get an error value indicating "bad File descriptor". In fact, the read-only mode we just mentioned is applied to the File descriptor held by the File value.

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

From the operating system level, this file descriptor is required for I/O operations on any file. However, some data types in Go language hide this descriptor for us, so we don't need to pay attention to and identify it all the time (like os.File type).

In fact, when we call the os.Create function, os.Open function and os.OpenFile function mentioned earlier, they will 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. After it is called, it will return a value of type uintptr. This value represents the File descriptor held by the current File value.

However, in the os package, it is not useful except for the NewFile function. Therefore, if you only operate on regular files or directories, you don't need to pay special attention to it.

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

This function has three parameters named name, flag and perm. Where name refers to the path of the file. The flag parameter refers to the mode that needs to be applied to the file descriptor. The read-only mode I mentioned earlier is an option here.

In the Go language, this read-only mode consists of the constant os.O_RDONLY represents, which is of type int. Of course, in addition to the read-only mode, there are several other modes available, which we will discuss in detail later.

The parameter perm of os.OpenFile function also represents the mode. Its type is os.FileMode, which is a redefinition type based on uint32 type.

In order to distinguish, we call the mode referred to by the parameter flag as the operation mode, and the mode referred to by the parameter perm as the permission mode. It can be said that the operation mode defines the way to operate the file, while the permission mode can control the access permission of the file. More details about the permission mode will be discussed later.

(several ways to obtain pointer value of os.File type)

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

In addition, the os package also has a Create function for creating new files, a NewFile function for wrapping existing files, and an Open function and an OpenFile function that can be used to Open existing files.

package main

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

// ioTypes represents the reflection types 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 the os code package and the program entities in it. We first discuss the significance of os package and its main uses. The API s contained in the code package are high-level abstractions of certain functions of the operating system, which enables us to manipulate different operating systems in a unified way and get similar results.

In this code package, the API for manipulating the file system is the most abundant, and the most representative is the data type os.File. The os.File type can represent not only files in the operating system, but also many other things. Especially in the Unix like operating system, it can represent almost all the software and hardware that can be manipulated.

Note source code

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

This work adopts Knowledge sharing Attribution - non-commercial use - sharing in the same way 4.0 international license agreement License.

Welcome to reprint, use and republish, but be sure to keep the signature Zheng Ziming (including link: http://www.cnblogs.com/MingsonZheng/ ), shall not be used for commercial purposes, and the works modified based on this article must be distributed under the same license.

Posted on Mon, 06 Dec 2021 13:52:54 -0500 by work_it_work