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

MingsonZheng發表於2021-12-08

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 建立和操縱一個系統程式?

筆記原始碼

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

知識共享許可協議

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

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

相關文章