> For the complete documentation index, see [llms.txt](https://deepingo.run-linux.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://deepingo.run-linux.com/go_sys/os.md).

# OS API

* **os.File**

os.File类型代表了操作系统中的文件。但实际上，它可以代表的远不止于此。或许你已经知道，对于类 Unix 的操作系统（包括 Linux、macOS、FreeBSD 等），其中的一切都可以被看做是文件。

除了文本文件、二进制文件、压缩文件、目录这些常见的形式之外，还有符号链接、各种物理设备（包括内置或外接的面向块或者字符的设备）、命名管道，以及套接字（也就是 socket），等等。

因此，可以说，我们能够利用os.File类型操纵的东西太多了。不过，为了聚焦于os.File本身，同时也为了让本文讲述的内容更加通用，我们在这里主要把os.File类型应用于常规的文件。

### os.File类型实现 的io包中的接口

os.File类型拥有的都是指针方法，所以除了空接口之外，它本身没有实现任何接口。而它的指针类型则实现了很多io代码包中的接口。

首先，对于io包中最核心的 3 个简单接口io.Reader、io.Writer和io.Closer，\*os.File类型都实现了它们。

其次，该类型还实现了另外的 3 个简单接口，即：io.ReaderAt、io.Seeker和io.WriterAt。

正是因为\*os.File类型实现了这些简单接口，所以它也顺便实现了io包的 9 个扩展接口中的 7 个。

然而，由于它并没有实现简单接口 io.ByteReader 和 io.RuneReader，所以它没有实现分别作为这两者的扩展接口的io.ByteScanner 和 io.RuneScanner。

os.File类型及其指针类型的值，不但可以**通过各种方式读取和写入某个文件中的内容，还可以寻找并设定下一次读取或写入时的起始索引位置，另外还可以随时对文件进行关闭**。

但是，它们并不能专门地读取文件中的下一个字节，或者下一个 Unicode 字符，也不能进行任何的读回退操作。

不过，单独读取下一个字节或字符的功能也可以通过其他方式来实现，比如，调用它的Read方法并传入适当的参数值就可以做到这一点。

### os.File类型的指针值

获得一个os.File类型的指针值（以下简称File值），在os包中，有这样几个函数，即：Create、NewFile、Open和OpenFile。

* **os.Create**

**os.Create函数用于根据给定的路径创建一个新的文件**。 它会返回一个File值和一个错误值。我们可以在该函数返回的File值之上，对相应的文件进行读操作和写操作。

不但如此，我们使用这个函数创建的文件，**对于操作系统中的所有用户来说，都是可以读和写的**。

注意，如果在我们给予 os.Create 函数的路径之上，**已经存在了一个文件，那么该函数会先清空现有文件中的全部内容，然后再把它作为第一个结果值返回**。

此外，os.Create 函数是**有可能返回非 nil 的错误值的**。比如，如果我们给定的路径上的某一级父目录并不存在，那么该函数就会返回一个 \*os.PathError 类型的错误值，以表示“不存在的文件或目录”。

* **os.NewFile**

再来看**os.NewFile函数**。 该函数在被调用的时候，需要接受一个代表文件描述符的、uintptr类型的值，以及一个用于表示文件名的字符串值。

如果我们给定的文件描述符并不是有效的，那么这个函数将会返回nil，否则，它将会返回一个代表了相应文件的File值。它的**功能并不是创建一个新的文件，而是依据一个已经存在的文件的描述符，来新建一个包装了该文件的File值**。例如，我们可以像这样拿到一个包装了标准错误输出的File值：

```go
file3 := os.NewFile(uintptr(syscall.Stderr), "/dev/stderr")
```

通过这个File值向标准错误输出上写入一些内容：

```go
if file3 != nil {
 defer file3.Close()
 file3.WriteString(
  "The Go language program writes the contents into stderr.\n")
}
```

* **os.Open**

os.Open函数会打开**一个文件并返回包装了该文件的File值**。 然而，该**函数只能以只读模式打开文件**。换句话说，我们只能从该函数返回的File值中读取内容，而**不能向它写入任何内容**。

如果我们调用了这个File值的任何一个写入方法，那么都将会得到一个表示了“坏的文件描述符”的错误值。实际上，我们刚刚说的只读模式，正是应用在File值所持有的文件描述符之上的。

从操作系统的层面看，**针对任何文件的 I/O 操作都需要用到这个文件描述符**。只不过，Go 语言中的一些数据类型，为我们隐匿掉了这个描述符，如此一来我们就无需时刻关注和辨别它了（就像os.File类型这样）。

实际上，我们在调用前文所述的os.Create函数、os.Open函数以及将会提到的os.OpenFile函数的时候，它们**都会执行同一个系统调用，并且在成功之后得到这样一个文件描述符**。**这个文件描述符将会被储存在它们返回的File值中**。**os.File类型有一个指针方法，名叫Fd**。它在被调用之后将会**返回一个uintptr类型的值。这个值就代表了当前的File值所持有的那个文件描述符**。不过，在os包中，除了NewFile函数需要用到它，它也没有什么别的用武之地了。所以，如果你操作的只是常规的文件或者目录，那么就无需特别地在意它了。

* **os.OpenFile**

这个函数其实是os.Create函数和os.Open函数的底层支持，它最为灵活。

这个函数有 3 个参数，分别名为name、flag和perm。其中的name指代的就是文件的路径。而flag参数指的则是需要施加在文件描述符之上的模式，我在前面提到的只读模式就是这里的一个可选项。

在 Go 语言中，这个只读模式由常量os.O\_RDONLY代表，它是int类型的。当然了，这里除了只读模式之外，还有几个别的模式可选。os.OpenFile函数的参数perm代表的也是模式，它的类型是os.FileMode，此类型是一个基于uint32类型的再定义类型。

为了加以区别，我们把参数flag指代的模式叫做操作模式，而把参数perm指代的模式叫做权限模式。可以这么说，操作模式限定了操作文件的方式，而权限模式则可以控制文件的访问权限。

![d3414376a3343926a2b33cdeeb094893](https://static001.geekbang.org/resource/image/d3/93/d3414376a3343926a2b33cdeeb094893.png)

## 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函数都是现成的例子。

```go
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值的读取方法和写入方法都是可用的。这里需要注意，多个操作模式是通过按位或操作符|组合起来的。

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

os.Open函数的功能是：以只读模式打开已经存在的文件。其根源就是它在调用os.OpenFile函数的时候，只提供了一个单一的操作模式os.O\_RDONLY。

## 设定常规文件的访问权限

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函数的时候，可以根据以上说明设置它的第三个参数。但要注意，只有在新建文件的时候，这里的第三个参数值才是有效的。在其他情况下，即使我们设置了此参数，也不会对目标文件产生任何的影响。
