Go語言核心36講(Go語言實戰與應用二十三)–學習筆記

45 | 使用os包中的API (下)

我們在上一篇文章中。從「os.File類型都實現了哪些io包中的接口」這一問題出發,介紹了一系列的相關內容。今天我們繼續圍繞這一知識點進行擴展。

知識擴展

問題 1:可應用於File值的操作模式都有哪些?

針對File值的操作模式主要有隻讀模式、只寫模式和讀寫模式。

這些模式分別由常量os.O_RDONLY、os.O_WRONLY和os.O_RDWR代表。在我們新建或打開一個文件的時候,必須把這三個模式中的一個設定為此文件的操作模式。

除此之外,我們還可以為這裡的文件設置額外的操作模式,可選項如下所示。

  • os.O_APPEND:當向文件中寫入內容時,把新內容追加到現有內容的後邊。
  • os.O_CREATE:當給定路徑上的文件不存在時,創建一個新文件。
  • os.O_EXCL:需要與os.O_CREATE一同使用,表示在給定的路徑上不能有已存在的文件。
  • os.O_SYNC:在打開的文件之上實施同步 I/O。它會保證讀寫的內容總會與硬盤上的數據保持同步。
  • os.O_TRUNC:如果文件已存在,並且是常規的文件,那麼就先清空其中已經存在的任何內容。

對於以上操作模式的使用,os.Create函數和os.Open函數都是現成的例子。

func Create(name string) (*File, error) {
 return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

os.Create函數在調用os.OpenFile函數的時候,給予的操作模式是os.O_RDWR、os.O_CREATE和os.O_TRUNC的組合。

這就基本上決定了前者的行為,即:如果參數name代表路徑之上的文件不存在,那麼就新建一個,否則,先清空現存文件中的全部內容。

並且,它返回的File值的讀取方法和寫入方法都是可用的。這裡需要注意,多個操作模式是通過按位或操作符|組合起來的。

func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}

我在前面說過,os.Open函數的功能是:以只讀模式打開已經存在的文件。其根源就是它在調用os.OpenFile函數的時候,只提供了一個單一的操作模式os.O_RDONLY。

以上,就是我對可應用於File值的操作模式的簡單解釋。在 demo88.go 文件中還有少許示例,可供你參考。

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
)

type flagDesc struct {
	flag int
	desc string
}

func main() {
	fileName1 := "something2.txt"
	filePath1 := filepath.Join(os.TempDir(), fileName1)
	fmt.Printf("The file path: %s\n", filePath1)
	fmt.Println()

	// 示例1。
	contents0 := "OpenFile is the generalized open call."
	flagDescList := []flagDesc{
		{
			os.O_WRONLY | os.O_CREATE | os.O_TRUNC,
			"os.O_WRONLY|os.O_CREATE|os.O_TRUNC",
		},
		{
			os.O_WRONLY,
			"os.O_WRONLY",
		},
		{
			os.O_WRONLY | os.O_APPEND,
			"os.O_WRONLY|os.O_APPEND",
		},
	}

	for i, v := range flagDescList {
		fmt.Printf("Open the file with flag %s ...\n", v.desc)
		file1a, err := os.OpenFile(filePath1, v.flag, 0666)
		if err != nil {
			fmt.Printf("error: %v\n", err)
			continue
		}
		fmt.Printf("The file descriptor: %d\n", file1a.Fd())

		contents1 := fmt.Sprintf("[%d]: %s ", i+1, contents0)
		fmt.Printf("Write %q to the file ...\n", contents1)
		n, err := file1a.WriteString(contents1)
		if err != nil {
			fmt.Printf("error: %v\n", err)
			continue
		}
		fmt.Printf("The number of bytes written is %d.\n", n)

		file1b, err := os.Open(filePath1)
		fmt.Println("Read bytes from the file ...")
		bytes, err := ioutil.ReadAll(file1b)
		if err != nil {
			fmt.Printf("error: %v\n", err)
			continue
		}
		fmt.Printf("Read(%d): %q\n", len(bytes), bytes)
		fmt.Println()
	}

	// 示例2。
	fmt.Println("Try to create an existing file with flag os.O_TRUNC ...")
	file2, err := os.OpenFile(filePath1, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Printf("error: %v\n", err)
		return
	}
	fmt.Printf("The file descriptor: %d\n", file2.Fd())

	fmt.Println("Try to create an existing file with flag os.O_EXCL ...")
	_, err = os.OpenFile(filePath1, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
	fmt.Printf("error: %v\n", err)
}

問題 2:怎樣設定常規文件的訪問權限?

我們已經知道,os.OpenFile函數的第三個參數perm代表的是權限模式,其類型是os.FileMode。但實際上,os.FileMode類型能夠代表的,可遠不只權限模式,它還可以代表文件模式(也可以稱之為文件種類)。

由於os.FileMode是基於uint32類型的再定義類型,所以它的每個值都包含了 32 個比特位。在這 32 個比特位當中,每個比特位都有其特定的含義。

比如,如果在其最高比特位上的二進制數是1,那麼該值表示的文件模式就等同於os.ModeDir,也就是說,相應的文件代表的是一個目錄。

又比如,如果其中的第 26 個比特位上的是1,那麼相應的值表示的文件模式就等同於os.ModeNamedPipe,也就是說,那個文件代表的是一個命名管道。

實際上,在一個os.FileMode類型的值(以下簡稱FileMode值)中,只有最低的 9 個比特位才用於表示文件的權限。當我們拿到一個此類型的值時,可以把它和os.ModePerm常量的值做按位與操作。

這個常量的值是0777,是一個八進制的無符號整數,其最低的 9 個比特位上都是1,而更高的 23 個比特位上都是0。

所以,經過這樣的按位與操作之後,我們即可得到這個FileMode值中所有用於表示文件權限的比特位,也就是該值所表示的權限模式。這將會與我們調用FileMode值的Perm方法所得到的結果值是一致。

在這 9 個用於表示文件權限的比特位中,每 3 個比特位為一組,共可分為 3 組。

從高到低,這 3 組分別表示的是文件所有者(也就是創建這個文件的那個用戶)、文件所有者所屬的用戶組,以及其他用戶對該文件的訪問權限。而對於每個組,其中的 3 個比特位從高到低分別表示讀權限、寫權限和執行權限。

如果在其中的某個比特位上的是1,那麼就意味着相應的權限開啟,否則,就表示相應的權限關閉。

因此,八進制整數0777就表示:操作系統中的所有用戶都對當前的文件有讀、寫和執行的權限,而八進制整數0666則表示:所有用戶都對當前文件有讀和寫的權限,但都沒有執行的權限。

我們在調用os.OpenFile函數的時候,可以根據以上說明設置它的第三個參數。但要注意,只有在新建文件的時候,這裡的第三個參數值才是有效的。在其他情況下,即使我們設置了此參數,也不會對目標文件產生任何的影響。

package main

import (
	"fmt"
	"os"
	"path/filepath"
)

type argDesc struct {
	action string
	flag   int
	perm   os.FileMode
}

func main() {
	// 示例1。
	fmt.Printf("The mode for dir:\n%32b\n", os.ModeDir)
	fmt.Printf("The mode for named pipe:\n%32b\n", os.ModeNamedPipe)
	fmt.Printf("The mode for all of the irregular files:\n%32b\n", os.ModeType)
	fmt.Printf("The mode for permissions:\n%32b\n", os.ModePerm)
	fmt.Println()

	// 示例2。
	fileName1 := "something3.txt"
	filePath1 := filepath.Join(os.TempDir(), fileName1)
	fmt.Printf("The file path: %s\n", filePath1)

	argDescList := []argDesc{
		{
			"Create",
			os.O_RDWR | os.O_CREATE,
			0644,
		},
		{
			"Reuse",
			os.O_RDWR | os.O_TRUNC,
			0666,
		},
		{
			"Open",
			os.O_RDWR | os.O_APPEND,
			0777,
		},
	}

	defer os.Remove(filePath1)
	for _, v := range argDescList {
		fmt.Printf("%s the file with perm %o ...\n", v.action, v.perm)
		file1, err := os.OpenFile(filePath1, v.flag, v.perm)
		if err != nil {
			fmt.Printf("error: %v\n", err)
			continue
		}
		info1, err := file1.Stat()
		if err != nil {
			fmt.Printf("error: %v\n", err)
			continue
		}
		fmt.Printf("The file permissions: %o\n", info1.Mode().Perm())
	}
}

總結

為了聚焦於os.File類型本身,我在這兩篇文章中主要講述了怎樣把 os.File 類型應用於常規的文件。該類型的指針類型實現了很多io包中的接口,因此它的具體功用也就可以不言自明了。

通過該類型的值,我們不但可以對文件進行各種讀取、寫入、關閉等操作,還可以設定下一次讀取或寫入時的起始索引位置。

在使用這個類型的值之前,我們必須先要創建它。所以,我為你重點介紹了幾個可以創建,並獲得此類型值的函數。

包括:os.Create、os.NewFile、os.Open和os.OpenFile。我們用什麼樣的方式創建File值,就決定了我們可以使用它來做什麼。

利用os.Create函數,我們可以在操作系統中創建一個全新的文件,或者清空一個現存文件中的全部內容並重用它。

在相應的File值之上,我們可以對該文件進行任何的讀寫操作。雖然os.NewFile函數並不是被用來創建新文件的,但是它能夠基於一個有效的文件描述符包裝出一個可用的File值。

os.Open函數的功能是打開一個已經存在的文件。但是,我們只能通過它返回的File值對相應的文件進行讀操作。

os.OpenFile是這些函數中最為靈活的一個,通過它,我們可以設定被打開文件的操作模式和權限模式。實際上,os.Create函數和os.Open函數都只是對它的簡單封裝而已。

在使用os.OpenFile函數的時候,我們必須要搞清楚操作模式和權限模式所代表的真正含義,以及設定它們的正確方式。

我在本文的擴展問題中分別對它們進行了較為詳細的解釋。同時,我在對應的示例文件中也編寫了一些代碼。

你需要認真地閱讀和理解這些代碼,並在運行它們的過程當中悟出這兩種模式的真諦。

我在本文中講述的東西對於os包來說,只是海面上的那部分冰山而已。這個代碼包囊括的知識眾多,而且延展性都很強。

如果你想完全理解它們,可能還需要去參看操作系統等方面的文檔和教程。由於篇幅原因,我在這裡只是做了一個引導,幫助你初識該包中的一些重要的程序實體,並給予你一個可以深入下去的切入點,希望你已經在路上了。

思考題

今天的思考題是:怎樣通過os包中的 API 創建和操縱一個系統進程?

筆記源碼

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

知識共享許可協議

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

歡迎轉載、使用、重新發佈,但務必保留文章署名 鄭子銘 (包含鏈接: //www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。