os


os 包提供了平台无关的操作系统功能接口。尽管错误处理是 go 风格的,但设计是 Unix 风格的;所以,失败的调用会返回 error 而非错误码。通常 error 里会包含更多信息。例如,如果使用一个文件名的调用(如 Open、Stat)失败了,打印错误时会包含该文件名,错误类型将为 *PathError,其内部可以解包获得更多信息。

file, err := os.Open("file.go") // For read access.
if err != nil {
    log.Fatal(err)
}

如果打开失败,错误字符串是自解释的,例如:

open file.go: no such file or directory

文件 I/O

在 Go 中,文件描述符封装在 os.File 结构中,通过 File.Fd() 可以获得底层的文件描述符:fd

打开一个文件:OpenFile

OpenFile 既能打开一个已经存在的文件,也能创建并打开一个新文件。

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

OpenFile 是一个更一般性的文件打开函数,大多数调用者都应用 OpenCreate 代替本函数。它会使用指定的选项(如 O_RDONLY 等)、指定的模式(如 0666 等)打开指定名称的文件。如果操作成功,返回的文件对象可用于 I/O。如果出错,错误底层类型是*PathError

要打开的文件由参数 name 指定,它可以是绝对路径或相对路径(相对于进程当前工作目录),也可以是一个符号链接(会对其进行解引用)。

位掩码参数 flag 用于指定文件的访问模式,可用的值在 os 中定义为常量(以下值并非所有操作系统都可用):

const (
    O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
    O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
    O_RDWR   int = syscall.O_RDWR   // 读写模式打开文件
    O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
    O_CREATE int = syscall.O_CREAT  // 如果不存在将创建一个新文件
    O_EXCL   int = syscall.O_EXCL   // 和 O_CREATE 配合使用,文件必须不存在
    O_SYNC   int = syscall.O_SYNC   // 打开文件用于同步 I/O
    O_TRUNC  int = syscall.O_TRUNC  // 如果可能,打开时清空文件
)

其中,O_RDONLYO_WRONLYO_RDWR 应该只指定一个,剩下的通过 | 操作符来指定。该函数内部会给 flags 加上 syscall.O_CLOEXEC,在 fork 子进程时会关闭通过 OpenFile 打开的文件,即子进程不会重用该文件描述符。

注意:由于历史原因,O_RDONLY | O_WRONLY 并非等于 O_RDWR,它们的值一般是 0、1、2。

使用方法

打开一个文件,一般通过 OpenCreate,我们看这两个函数的实现。

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

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

读取文件内容:Read

func (f *File) Read(b []byte) (n int, err error)

Read 方法从 f 中读取最多 len(b) 字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取 0 个字节且返回值 err 为 io.EOF

数据写入文件:Write

func (f *File) Write(b []byte) (n int, err error)

Write 向文件中写入 len(b) 字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值 n!=len(b),本方法会返回一个非 nil 的错误。为了方便,还提供了 WriteString 方法,它实际是对 Write 的封装。

关闭文件:Close

close() 系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。当进程终止时,将自动关闭其已打开的所有文件描述符。

func (f *File) Close() error

os.File.Close() 是对 close() 的封装。我们应该养成关闭不需要的文件的良好编程习惯。文件描述符是资源,Go 的 gc 是针对内存的,并不会自动回收资源,如果不关闭文件描述符,长期运行的服务可能会把文件描述符耗尽。

改变文件偏移量:Seek

对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个 ReadWrite 操作的文件其实位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为 0。

文件打开时,会将文件偏移量设置为指向文件开始,以后每次 ReadWrite 调用将自动对其进行调整,以指向已读或已写数据后的下一个字节。因此,连续的 ReadWrite 调用将按顺序递进,对文件进行操作。

Seek 可以调整文件偏移量。方法定义如下:

func (f *File) Seek(offset int64, whence int) (ret int64, err error)

Seek 设置下一次读 / 写的位置。offset 为相对偏移量,而 whence 决定相对位置:0 为相对文件开头,1 为相对当前位置,2 为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。使用中,whence 应该使用 os 包中的常量:SEEK_SETSEEK_CURSEEK_END

注意Seek 只是调整内核中与文件描述符相关的文件偏移量记录,并没有引起对任何物理设备的访问。

file.Seek(0, os.SEEK_SET)    // 文件开始处
file.Seek(0, SEEK_END)        // 文件结尾处的下一个字节
file.Seek(-1, SEEK_END)        // 文件最后一个字节
file.Seek(-10, SEEK_CUR)     // 当前位置前 10 个字节
file.Seek(1000, SEEK_END)    // 文件结尾处的下 1001 个字节

文件属性

文件属性,也即文件元数据。在 Go 中,文件属性具体信息通过 os.FileInfo 接口获取。函数 StatLstatFile.Stat 可以得到该接口的实例。这三个函数对应三个系统调用:statlstatfstat

FileInfo 接口如下:

type FileInfo interface {
    Name() string       // 文件的名字(不含扩展名)
    Size() int64        // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
    Mode() FileMode     // 文件的模式位
    ModTime() time.Time // 文件的修改时间
    IsDir() bool        // 等价于 Mode().IsDir()
    Sys() interface{}   // 底层数据来源(可以返回 nil)
}

目录与链接

更改文件名

系统调用 rename 既可以重命名文件,又可以将文件移至同一个文件系统中的另一个目录。该系统调用既可以用于文件,也可以用于目录。相关细节,请查阅相关资料。

Go 中的 os.Rename 是对应的封装函数。

func Rename(oldpath, newpath string) error

Rename 修改一个文件的名字或移动一个文件。如果 newpath 已经存在,则替换它。注意,可能会有一些个操作系统特定的限制。

创建和移除目录

mkdir 系统调用创建一个新目录,Go 中的 os.Mkdir 是对应的封装函数。

func Mkdir(name string, perm FileMode) error

Mkdir 使用指定的权限和名称创建一个目录。如果出错,会返回 *PathError 类型的错误。

name 参数指定了新目录的路径名,可以是相对路径,也可以是绝对路径。如果已经存在,则调用失败并返回 os.ErrExist 错误。

perm 参数指定了新目录的权限。对该位掩码值的指定方式和 os.OpenFile 相同,也可以直接赋予八进制数值。注意,perm 值还将于进程掩码相与(&)。如果 perm 中设置了 sticky 位,那么将对新目录设置该权限。

因为 Mkdir 所创建的只是路径名中的最后一部分,如果父目录不存在,创建会失败。os.MkdirAll 用于递归创建所有不存在的目录。

rmdir 系统调用移除一个指定的目录,目录可以是绝对路径或相对路径。在讲解 unlink 时,已经介绍了 Go 中的 os.Remove。注意,这里要求目录必须为空。为了方便使用,Go 中封装了一个 os.RemoveAll 函数:

func RemoveAll(path string) error

RemoveAll 删除 path 指定的文件,或目录及它包含的任何下级对象。它会尝试删除所有东西,除非遇到错误并返回。如果 path 指定的对象不存在,RemoveAll 会返回 nil 而不返回错误。


文章作者: ffacs
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ffacs !
  目录