Go 語言標准庫之 os 包


os 包提供了平台無關的操作系統功能接口,主要是文件相關的I/O,本文會重點對文件操作進行介紹。


文件 I/O

在 Go 中,文件描述符封裝在os.File結構中。os.File代表一個打開的文件對象,可以使用該對象進行文件讀寫操作。

type File struct {
    *file // os specific
}

type file struct {
    pfd        poll.FD
    name       string
    dirinfo    *dirInfo // nil unless directory being read
    appendMode bool     // whether file is opened for appending
}

打開和關閉文件 OpenFile/Open/Close

☕️ OpenFile函數

OpenFile()是一個一般性的文件打開函數,大多數調用都應該使用Open()Create()代替本函數。它會使用指定的選項(如O_RDONLY等)、指定的模式(如 0666 等)打開指定名稱的文件。如果操作成功,返回的文件對象可用於 I/O。如果出錯,錯誤底層類型是*PathError

func OpenFile(name string, flag int, perm FileMode) (file *File, err error)

此處需要特別介紹openFile()函數參數:

  • name:要打開的文件名,可以是一個絕對路徑或相對路徑,也可以是一個符號鏈接。

  • flag:指定文件的訪問模式,可用值已在 os 包中定義為常量。模式有以下幾種:

模式 含義
os.O_RDONLY 只讀
os.O_WRONLY 只寫(覆蓋方式寫)
os.O_RDWR 讀寫(覆蓋方式寫)
os.O_APPEND 往文件尾部追加方式寫
os.O_CREATE 如果文件不存在則先創建
os.O_EXCL O_CREATE配合使用,文件必須不存在
os.O_SYNC 以同步I/O的方式打開文件
os.O_TRUNC 打開時清空文件

多種訪問模式可以使用|操作符來連接,例如:O_RDWR|O_CREATE|O_TRUNC。其中,O_RDONLYO_WRONLYO_RDWR三種模式只能指定一個。

  • perm:指定了文件的模式和權限位,類型是os.FileMode

這些字位在所有的操作系統都有相同的含義,因此文件的信息可以在不同的操作系統之間安全的移植。不是所有的位都能用於所有的系統,唯一共有的是用於表示目錄的ModeDir位。

type FileMode uint32

const (
    // 單字符是被 String 方法用於格式化的屬性縮寫。
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: 目錄
    ModeAppend                                     // a: 只能寫入,且只能寫入到末尾
    ModeExclusive                                  // l: 用於執行
    ModeTemporary                                  // T: 臨時文件(非備份文件)
    ModeSymlink                                    // L: 符號鏈接(不是快捷方式文件)
    ModeDevice                                     // D: 設備
    ModeNamedPipe                                  // p: 命名管道(FIFO)
    ModeSocket                                     // S: Unix 域 socket
    ModeSetuid                                     // u: 表示文件具有其創建者用戶 id 權限
    ModeSetgid                                     // g: 表示文件具有其創建者組 id 的權限
    ModeCharDevice                                 // c: 字符設備,需已設置 ModeDevice
    ModeSticky                                     // t: 只有 root/ 創建者能刪除 / 移動文件

    // 覆蓋所有類型位(用於通過 & 獲取類型位),對普通文件,所有這些位都不應被設置
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
    ModePerm FileMode = 0777 // 覆蓋所有 Unix 權限位(用於通過 & 獲取類型位)
)

文件的權限打印出來一共十個字符。文件有三種基本權限:r(read,讀權限)、w(write,寫權限)、x(execute,執行權限),具體如下:

                                 - rwx rw- r--
  • 第 1 位:文件類型(d為目錄,-為普通文件)

  • 第 2-4 位:所屬用戶權限,用 u(user)表示

  • 第 5-7 位:所屬組權限,用 g(group)表示

  • 第 8-10 位:其他用戶權限,用 o(other)表示

文件的權限還可以用八進制表示:r 表示為 4,w 表示為 2,x 表示為 1,- 表示為 0。例如:

  • 0777:權限- rwx rwx rwx的八進制表示,任何人都可讀寫,可執行
  • 0666:權限- rw- rw- rw-的八進制表示,任何人都可讀寫,但不可執行

⭐️ Open 函數

// 打開一個文件只能用於讀取
func Open(name string) (file *File, err error)

該函數內部實際調用openFile()函數,源碼如下:

func Open(name string) (*File, error) {
    // 以 os.O_RDONLY(只讀)模式打開文件
    return OpenFile(name, O_RDONLY, 0)
}

✏️ Close 函數

// 關閉文件 f,使文件不能用於讀寫。它返回可能出現的錯誤
func (f *File) Close() error

close()用於關閉一個打開的文件描述符,並將其釋放回調用進程,供該進程繼續使用。當進程終止時,也會自動關閉其已打開的所有文件描述符。通常情況下,我們應該主動關閉文件描述符,如果不關閉,長期運行的服務可能會把文件描述符耗盡。關於返回值 error,以下兩種情況會導致Close()返回錯誤:

  • 關閉一個未打開的文件
  • 兩次關閉同一個文件

因此,通常我們不會去檢查Close()返回的錯誤。

📚 示例代碼

package main

import (
    "os"
)

func main() {
    // 以只讀的方式打開文件
    file1, err := os.Open("./test1.txt")
    if err != nil {
        panic(err)
    }
    defer file1.Close()

    // 自定義方式打開文件
    file2, err := os.OpenFile("./test2.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
    if err != nil {
        panic(err)
    }
    defer file2.Close()
}

創建文件 Create

// 采用 0666 權限(任何人都可讀寫,不可執行)創建一個名為 name 的文件,如果文件已存在會截斷它(為空文件)
// 如果成功,返回的文件對象可用於 I/O;對應的文件描述符具有 O_RDWR 模式。如果出錯,錯誤底層類型是 *PathError
func Create(name string) (file *File, err error)

Create()函數本質上是調用OpenFile()函數,源碼如下:

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

✌ 示例代碼

package main

import (
    "os"
)

func main() {
    // 以 O_RDWR|O_CREATE|O_TRUNC 模式,0666 權限打開文件
    newFile, err := os.Create("./test.txt")
    if err != nil {
        panic(err)
    }
    defer newFile.Close()
}

讀文件 Read/ReadAt

// 從 f 中讀取最多 len(b) 字節數據並寫入 b。它返回讀取的字節數和可能遇到的任何錯誤
// 文件終止標志是讀取 0 個字節,且返回值 err 為 io.EOF
func (f *File) Read(b []byte) (n int, err error)

// 從指定的位置(相對於文件開始位置)讀取 len(b) 字節數據並寫入 b。它返回讀取的字節數和可能遇到的任何錯誤
// 當 n < len(b) 時,本方法總是會返回錯誤;如果是因為到達文件結尾,返回值 err 會是 io.EOF
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

Read()ReadAt()的區別:前者從文件當前偏移量處讀,且會改變文件當前的偏移量;而后者從 off 指定的位置開始讀,且不會改變文件當前偏移量。

✍ 示例代碼

package main

import (
    "fmt"
    "os"
)

func main() {
    // 打開文件,只讀
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 從文件當前偏移量讀取 len(b) 字節,會改變文件當前的偏移量
    b := make([]byte, 5)
    n, err := file.Read(b)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Number of bytes read: %d\n", n)
    fmt.Printf("Data read: %s\n", b)

    // 從文件指定偏移量讀取 len(b) 字節,不會改變文件當前偏移量
    n, err = file.ReadAt(b, 0)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Number of bytes read: %d\n", n)
    fmt.Printf("Data read: %s\n", b)

    // 從當前文件偏移量繼續讀
    n, err = file.Read(b)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Number of bytes read: %d\n", n)
    fmt.Printf("Data read: %s\n", b)
}

// 文件 test.txt 內容:
// hello World

// 控制台輸出:
// Number of bytes read: 5
// Data read: hello       
// Number of bytes read: 5
// Data read: hello       
// Number of bytes read: 5
// Data read:  Worl  

寫文件 Write/WriteAt/WriteString/Sync

// 向文件中寫入 len(b) 字節數據。它返回寫入的字節數和可能遇到的任何錯誤。如果返回值 n != len(b),本方法會返回一個非 nil 的錯誤
func (f *File) Write(b []byte) (n int, err error)

// 在指定的位置(相對於文件開始位置)寫入 len(b) 字節數據
// 它返回寫入的字節數和可能遇到的任何錯誤。如果返回值 n != len(b),本方法會返回一個非 nil 的錯誤
func (f *File) WriteAt(b []byte, off int64) (n int, err error)

// 類似 Write,但接受一個字符串參數
func (f *File) WriteString(s string) (ret int, err error)

Write()WriteAt()的區別:前者從文件當前偏移量處寫,且會改變文件當前的偏移量;而后者從 off 指定的位置開始寫,且不會改變文件當前偏移量。

寫操作調用成功並不能保證數據已經寫入磁盤,因為內核會緩存磁盤的I/O操作。如果希望立刻將數據寫入磁盤(一般場景不建議這么做,因為會影響性能),有兩種辦法:

  • 打開文件時指定os.O_SYNC模式;
  1. 調用File.Sync()方法,該方法底層是 fsync 系統調用,這會將數據和元數據都刷到磁盤。
// 遞交文件的當前內容進行穩定的存儲。一般來說,這表示將文件系統的最近寫入的數據在內存中的拷貝刷新到硬盤中穩定保存
func (f *File) Sync() (err error)

✍ 示例代碼

package main

import (
    "fmt"
    "os"
)

func main() {
    // 可寫方式打開文件
    file, err := os.Create("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 在文件當前偏移量寫入字節切片,會改變文件當前文件的偏移量
    n, err := file.Write([]byte("Hello World!\n"))
    if err != nil {
        panic(err)
    }
    fmt.Printf("Wrote %d bytes.\n", n)

    // 在文件當前偏移量寫入字節切片,不會改變當前文件的偏移量
    n, err = file.WriteAt([]byte("gophers\n"), 0)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Wrote %d bytes.\n", n)

    // 在文件當前偏移量繼續寫入字符串
    n, err = file.WriteString("您好!")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Wrote %d bytes.\n", n)

    // 將數據刷新到磁盤
    file.Sync()
}

// 控制台輸出:
// Wrote 13 bytes.
// Wrote 7 bytes.
// Wrote 9 bytes.

// 文件 text.txt 內容:
// gophersorld!
// 您好!

改變文件偏移量 Seek

於每個打開的文件,系統內核會記錄其文件偏移量,有時也將文件偏移量稱為讀寫偏移量或指針。文件偏移量是指執行下一個 Read 或 Write 操作的文件真實位置,會以相對於文件頭部起始點的文件當前位置來表示。文件第一個字節的偏移量為 0。

// 設置下一次讀/寫的文件偏移量。offset 為相對偏移量,而 whence 決定相對位置:0 為相對文件開頭,1 為相對當前位置,2 為相對文件結尾
// 它返回新的偏移量(相對開頭)和可能的錯誤。
func (f *File) Seek(offset int64, whence int) (ret int64, err error)

// whence 的值,在 io 包中定義了相應的常量,推薦使用這些常量
const (
  SeekStart   = 0 // seek relative to the origin of the file
  SeekCurrent = 1 // seek relative to the current offset
  SeekEnd     = 2 // seek relative to the end
)

💡 示例代碼

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 相對偏移量,可以是正數也可以是負數
    var offset int64 = 5

    // 相對位置
    // io.SeekStart = 0   // 文件開頭
    // io.SeekCurrent = 1 // 當前位置
    // io.SeekEnd = 2     // 文件結尾
    var whence = io.SeekStart
    newPosition, err := file.Seek(offset, whence)
    if err != nil {
        panic(err)
    }
    fmt.Println("Just moved to 5:", newPosition)

    // 從當前位置回退兩個字節
    newPosition, err = file.Seek(-2, io.SeekCurrent)
    if err != nil {
        panic(err)
    }
    fmt.Println("Just moved back two:", newPosition)

    // 使用下面的技巧得到當前的位置
    currentPosition, err := file.Seek(0, io.SeekCurrent)
    fmt.Println("Current position:", currentPosition)

    // 轉到文件開始處
    newPosition, err = file.Seek(0, io.SeekStart)
    if err != nil {
        panic(err)
    }
    fmt.Println("Position after seeking 0,0:", newPosition)
}
// 文件 test.txt 內容:
// hello world

// 控制台輸出:
// Just moved to 5: 5
// Just moved back two: 3
// Current position: 3
// Position after seeking 0,0: 0

裁剪文件 Truncate

Truncate()用於將文件的長度設置為 size 參數指定的值。如果文件當前長度大於參數 size,將丟棄超出部分;若小於參數 size,將在文件尾部添加一系列 null 字節或是一個文件空洞進行填充。

// 修改 name 指定的文件的大小
// 如果該文件為一個符號鏈接,將修改鏈接指向的文件的大小。如果出錯,會返回 *PathError 底層類型的錯誤
func Truncate(name string, size int64) error

// 改變文件的大小,它不會改變 I/O 的當前位置
// 如果截斷文件,多出的部分就會被丟棄。如果出錯,錯誤底層類型是 *PathError
func (f *File) Truncate(size int64) error

它們之間的區別在於如何指定操作文件:

  • Truncate()以路徑名稱字符串來指定文件,並要求可訪問該文件(即對組成路徑名的各目錄擁有可執行 (x) 權限),且對文件擁有寫權限。若文件名為符號鏈接,那么調用將對其進行解引用。
  • 很明顯,調用File.Truncate()前,需要先以可寫方式打開操作文件,該方法不會修改文件偏移量。

☕️ 示例代碼

package main

import (
   "os"
)

func main() {
   // 裁剪一個文件到 100 個字節
   // 如果文件本來就少於 100 個字節,則文件中原始內容得以保留,剩余的字節以 null 字節填充
   // 如果文件本來超過 100 個字節,則超過的字節會被拋棄
   // 傳入 0 則會清空文件

   // 方式一:
   err := os.Truncate("./test1.txt", 20)
   if err != nil {
       panic(err)
   }

   // 方式二:
   // 打開文件模式必須包括 os.O_WRONLY 或者 os.O_RDWR,即可寫
   file, err := os.OpenFile("./test2.txt", os.O_WRONLY, 0666)
   if err != nil {
       panic(err)
   }
   defer file.Close()
   err = file.Truncate(20)
   if err != nil {
       panic(err)
   }
}

文件屬性 os.FileInfo

文件屬性,也即文件元數據。在 Go 中,文件屬性具體信息通過os.FileInfo接口獲取,文件的信息包括文件名、文件大小、修改權限、修改時間等。

type FileInfo interface {
    Name() string       // 文件的名字(不含擴展名)
    Size() int64        // 普通文件返回值表示其大小;其他文件的返回值含義各系統不同
    Mode() FileMode     // 文件的權限
    ModTime() time.Time // 文件的修改時間
    IsDir() bool        // 等價於 Mode().IsDir()
    Sys() interface{}   // 底層數據來源(可以返回 nil)
}

os.FileMode類型表示文件的模式和權限位,在介紹OpenFile()函數的時候提及過,其方法如下:

type FileMode uint32

// 報告 m 是否是一個目錄
func (m FileMode) IsDir() bool

// 報告 m 是否是一個普通文件
func (m FileMode) IsRegular() bool

// 返回 m 的 Unix 權限位
func (m FileMode) Perm() FileMode

// 打印出文件權限
func (m FileMode) String() string

// 返回文件類型
func (m FileMode) Type() FileMode {

獲取文件信息 Stat/Lstat

// 返回一個描述 name 指定的文件對象的 FileInfo
// 如果指定的文件對象是一個符號鏈接,返回的 FileInfo 描述該符號鏈接指向的文件的信息,本函數會嘗試跳轉該鏈接。如果出錯,返回的錯誤值為 *PathError 類型
func Stat(name string) (fi FileInfo, err error)

// 返回一個描述 name 指定的文件對象的 FileInfo
// 如果指定的文件對象是一個符號鏈接,返回的 FileInfo 描述該符號鏈接的信息,本函數不會試圖跳轉該鏈接。如果出錯,返回的錯誤值為 *PathError 類型
func Lstat(name string) (fi FileInfo, err error)

// 返回描述文件 f 的 FileInfo 類型值。如果出錯,錯誤底層類型是 *PathError
func (f *File) Stat() (fi FileInfo, err error)

函數Stat()Lstat()File.Stat()可以得到os.FileInfo接口的實例,分別對應三個系統調用:stat、lstat 和 fstat。它們區別在於:

  • Stat()會返回所命名文件的相關信息;

  • Lstat()Stat()類似,區別在於如果文件是符號鏈接,那么所返回的信息針對的是符號鏈接自身(而非符號鏈接所指向的文件);

  • File.Stat()則會返回由某個打開文件描述符(Go 中則是當前打開文件 File)所指代文件的相關信息。

Stat()Lstat()無需對其所操作的文件本身擁有任何權限,但針對指定 name 的父目錄要有執行(搜索)權限。而只要 File 對象 ok,File.Stat()總是成功。

⭐️ 示例代碼

package main

import (
    "fmt"
    "os"
)

func main() {
    // 方式一:
    fileInfo1, err := os.Stat("./test.txt")
    if err != nil {
        panic(err)
    }
    fmt.Println("文件名:", fileInfo1.Name())         // test.txt
    fmt.Println("文件大小:", fileInfo1.Size())        // 11
    fmt.Println("文件權限:", fileInfo1.Mode())        // -rw-rw-rw-
    fmt.Println("文件最后修改時間:", fileInfo1.ModTime()) // 2021-12-21 19:11:15.686021 +0800 CST
    fmt.Println("是否為目錄:", fileInfo1.IsDir())      // false

    // 方式二:
    fileInfo2, err := os.Lstat("./test.txt")
    if err != nil {
        panic(err)
    }
    fmt.Println("文件名:", fileInfo2.Name())         // test.txt
    fmt.Println("文件大小:", fileInfo2.Size())        // 11
    fmt.Println("文件權限:", fileInfo2.Mode())        // -rw-rw-rw-
    fmt.Println("文件最后修改時間:", fileInfo2.ModTime()) // 2021-12-21 19:11:15.686021 +0800 CST
    fmt.Println("是否為目錄:", fileInfo2.IsDir())      // false

    // 方式三:
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    fileInfo3, err := file.Stat()
    if err != nil {
        panic(err)
    }
    fmt.Println("文件名:", fileInfo3.Name())         // test.txt
    fmt.Println("文件大小:", fileInfo3.Size())        // 11
    fmt.Println("文件權限:", fileInfo3.Mode())        // -rw-rw-rw-
    fmt.Println("文件最后修改時間:", fileInfo3.ModTime()) // 2021-12-21 19:11:15.686021 +0800 CST
    fmt.Println("是否為目錄:", fileInfo3.IsDir())      // false
}

改變時間戳 Chtimes

// 修改 name 指定的文件對象的訪問時間和修改時間,類似 Unix 的 utime() 或 utimes() 函數
// 底層的文件系統可能會截斷/舍入時間單位到更低的精確度。如果出錯,會返回 *PathError 底層類型的錯誤
func Chtimes(name string, atime time.Time, mtime time.Time) error

📚 示例代碼

package main

import (
    "fmt"
    "os"
    "time"
)

func main() {
    fileInfo, err := os.Stat("./test.txt")
    if err != nil {
        panic(err)
    }
    fmt.Println("最后修改時間:", fileInfo.ModTime())

    // 改變文件時間戳為兩天前
    twoDaysFromNow := time.Now().Add(48 * time.Hour)
    lastAccessTime := twoDaysFromNow
    lastModifyTime := twoDaysFromNow
    err = os.Chtimes("./test.txt", lastAccessTime, lastModifyTime)
    if err != nil {
        panic(err)
    }
    fileInfo, err = os.Stat("./test.txt")
    if err != nil {
        panic(err)
    }
    fmt.Println("最后修改時間:", fileInfo.ModTime())
}

改變擁有者 Chown

// 修改 name 指定的文件對象的用戶 id 和組 id
// 如果 name 指定的文件是一個符號鏈接,它會修改該鏈接的目的地文件的用戶 id 和組 id。如果出錯,會返回 *PathError 底層類型的錯誤
func Chown(name string, uid, gid int) error

// 修改 name 指定的文件對象的用戶 id 和組 id
// 如果 name 指定的文件是一個符號鏈接,它會修改該符號鏈接自身的用戶 id 和組 id。如果出錯,會返回 *PathError 底層類型的錯誤
func Lchown(name string, uid, gid int) error

// 修改文件的用戶 ID 和組 ID。如果出錯,錯誤底層類型是 *PathError
func (f *File) Chown(uid, gid int) error

它們的區別和上文提到的 Stat 相關函數類似。

✏️ 示例代碼

package main

import (
    "os"
)

func main() {
    // 方式一
    err := os.Chown("./test.txt", os.Getuid(), os.Getgid())
    if err != nil {
        panic(err)
    }

    // 方式二
    err = os.Lchown("./test.txt", os.Getuid(), os.Getgid())
    if err != nil {
        panic(err)
    }

    // 方式三
    file, err := os.Open("./test2.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    err = file.Chown(os.Getuid(), os.Getgid())
    if err != nil {
        panic(err)
    }
}

改變文件權限 Chmod

// 修改 name 指定的文件對象的 mode
// 如果 name 指定的文件是一個符號鏈接,它會修改該鏈接的目的地文件的 mode。如果出錯,會返回 *PathError 底層類型的錯誤。
func Chmod(name string, mode FileMode) error

// 修改文件的模式。如果出錯,錯誤底層類型是 *PathError
func (f *File) Chmod(mode FileMode) error

✌ 示例代碼

package main

import (
    "os"
)

func main() {
    // 方式一:
    // 改變文件權限(windows不支持該函數)
    err := os.Chmod("./test.txt", 0777)
    if err != nil {
        panic(err)
    }

    // 方式二:
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    // 改變文件權限(windows不支持該函數)
    err = file.Chmod(0777)
    if err != nil {
        panic(err)
    }
}

檢查讀寫權限 IsPermission

// 返回一個布爾值說明該錯誤是否表示因權限不足要求被拒絕。ErrPermission 和一些系統調用錯誤會使它返回真
func IsPermission(err error) bool

✍ 示例代碼

package main

import (
    "fmt"
    "os"
)

func main() {
    // 這個例子測試寫權限,如果沒有寫權限則返回 error
    // 注意文件不存在也會返回 error,需要檢查 error 的信息來獲取到底是哪個錯誤導致
    file1, err := os.OpenFile("./test.txt", os.O_RDONLY, 0666)
    if err != nil {
        panic(err)
    }
    defer file1.Close()
    _, err = file1.WriteString("hello world")
    if os.IsPermission(err) {
        fmt.Println(err) // write ./test.txt: Access is denied.
    }

    // 測試讀權限
    file2, err := os.OpenFile("./test.txt", os.O_WRONLY, 0666)
    if err != nil {
        panic(err)
    }
    defer file2.Close()
    _, err = file2.Read(make([]byte, 10))
    if os.IsPermission(err) {
        fmt.Println(err) // read ./test.txt: Access is denied.
    }
}

其它文件操作

創建目錄 Mkdir/MkdirAll

// 使用指定的權限和名稱創建一個目錄。如果出錯,會返回 *PathError 底層類型的錯誤
func Mkdir(name string, perm FileMode) error

// 使用指定的權限和名稱創建一個目錄,包括任何必要的上級目錄,並返回 nil,否則返回錯誤。
// 權限位 perm 會應用在每一個被本函數創建的目錄上。如果 path 指定了一個已經存在的目錄,MkdirAll 不做任何操作並返回nil
func MkdirAll(path string, perm FileMode) error

💡 示例代碼

package main

import (
    "os"
)

func main() {
    // 只能創建單級目錄
    err := os.Mkdir("./test", os.ModePerm)
    if err != nil {
        panic(err)
    }

    // 能創建多級目錄
    err = os.MkdirAll("./aa/bb", os.ModePerm)
    if err != nil {
        panic(err)
    }
}

刪除文件或目錄 Remove/RemoveAll

// 刪除 name 指定的文件或目錄(必須為空目錄)。如果出錯,會返回 *PathError 底層類型的錯誤
func Remove(name string) error

// 刪除 path 指定的文件或目錄及它包含的任何下級對象。它會嘗試刪除所有東西,除非遇到錯誤並返回
// 如果 path 指定的對象不存在,RemoveAll 會返回 nil 而不返回錯誤
func RemoveAll(path string) error

☕️ 示例代碼

package main

import (
    "os"
)

func main() {
    // 刪除文件或目錄(必須為空目錄)
    err := os.Remove("./test.txt")
    if err != nil {
        panic(err)
    }

    // 刪除文件或者目錄
    err = os.RemoveAll("./aa")
    if err != nil {
        panic(err)
    }
}

重命名和移動 Rename

// Rename 修改一個文件的名字,移動一個文件。可能會有一些操作系統特定的限制
func Rename(oldpath, newpath string) error

⭐️ 示例代碼

package main

import (
    "os"
)

func main() {
    originalPath := "./test1.txt"
    newPath := "./test2.txt"
    err := os.Rename(originalPath, newPath)
    if err != nil {
        panic(err)
    }
}

檢查文件是否存在 IsExist/IsNotExist

// 返回一個布爾值說明該錯誤是否表示一個文件或目錄已經存在。ErrExist 和一些系統調用錯誤會使它返回真
func IsExist(err error) bool

// 返回一個布爾值說明該錯誤是否表示一個文件或目錄不存在。ErrNotExist 和一些系統調用錯誤會使它返回真
func IsNotExist(err error) bool

✏️ 示例代碼

package main

import (
    "fmt"
    "os"
)

func main() {
    // 文件不存在則返回 error
    _, err := os.Open("./test1.txt")
    if os.IsNotExist(err) {
        fmt.Println("File does not exist.")
    }
}

硬鏈接:文件是通過索引節點(Inode)來識別文件,硬鏈接可以認為是一個指針,指向文件索引節點的指針,每添加一個一個硬鏈接,文件的鏈接數就加 1,只有所有的硬鏈接被刪除后文件才會被刪除。只有在同一文件系統中的文件之間才能創建硬鏈接,不能對目錄進行創建,但是這個硬鏈接又可以建立多個,也就是可以有多個文件指向同一個索引節點,或者說一個文件可以擁有多個路徑名,因此一個文件可以對應多個文件名。

軟鏈接:和硬鏈接有點不一樣,它不直接指向索引節點,而是通過名字引用其它文件,類似 Windows 的快捷方式。軟鏈接可以指向不同的文件系統中的不同文件,但是並不是所有的操作系統都支持軟鏈接。

// 創建一個名為 newname 指向 oldname 的硬鏈接。如果出錯,會返回 *LinkError 底層類型的錯誤
func Link(oldname, newname string) error

// 創建一個名為 newname 指向 oldname 的軟鏈接。如果出錯,會返回 *LinkError 底層類型的錯誤
func Symlink(oldname, newname string) error

📚 示例代碼

package main

import (
    "fmt"
    "os"
)

func main() {
    // 創建一個硬鏈接
    // 創建后同一個文件內容會有兩個文件名,改變一個文件的內容會影響另一個
    // 刪除和重命名不會影響另一個
    err := os.Link("./test1.txt", "./test1_also.txt")
    if err != nil {
        panic(err)
    }

    // 創建軟鏈接(Windows不支持軟鏈接)
    err = os.Symlink("./test1.txt", "./test1_sym.txt")
    if err != nil {
        panic(err)
    }

    // Lstat 返回一個文件的信息,但是當文件是一個軟鏈接時,它返回軟鏈接的信息,而不是引用的文件的信息
    fileInfo, err := os.Lstat("./test1_sym.txt")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Link info: %+v", fileInfo)

    // 改變軟鏈接的擁有者不會影響原始文件
    err = os.Lchown("./test1_sym.txt", os.Getuid(), os.Getgid())
    if err != nil {
        panic(err)
    }
}

參考

  1. os — 平台無關的操作系統功能實現
  2. Go語言的30個常用文件操作,總有一個你會用到


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM