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
是一个更一般性的文件打开函数,大多数调用者都应用 Open
或 Create
代替本函数。它会使用指定的选项(如 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_RDONLY
、O_WRONLY
、O_RDWR
应该只指定一个,剩下的通过 |
操作符来指定。该函数内部会给 flags
加上 syscall.O_CLOEXEC
,在 fork 子进程时会关闭通过 OpenFile
打开的文件,即子进程不会重用该文件描述符。
注意:由于历史原因,O_RDONLY | O_WRONLY
并非等于 O_RDWR
,它们的值一般是 0、1、2。
使用方法
打开一个文件,一般通过 Open
或 Create
,我们看这两个函数的实现。
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
对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个 Read
或 Write
操作的文件其实位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为 0。
文件打开时,会将文件偏移量设置为指向文件开始,以后每次 Read
或 Write
调用将自动对其进行调整,以指向已读或已写数据后的下一个字节。因此,连续的 Read
和 Write
调用将按顺序递进,对文件进行操作。
而 Seek
可以调整文件偏移量。方法定义如下:
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek
设置下一次读 / 写的位置。offset 为相对偏移量,而 whence 决定相对位置:0 为相对文件开头,1 为相对当前位置,2 为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。使用中,whence 应该使用 os
包中的常量:SEEK_SET
、SEEK_CUR
和 SEEK_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
接口获取。函数 Stat
、Lstat
和 File.Stat
可以得到该接口的实例。这三个函数对应三个系统调用:stat
、lstat
和 fstat
。
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 而不返回错误。