os 操作系统接口

我们知道Linux操作系统的接口遵循POSIX规范,而Windows则自成一派,如果使用C/C++获取一些系统信息或是执行一些操作系统指令,在Linux下和Windows下的写法是不同的,因此大多数跨平台的编程语言标准库会对其进行统一的封装。Go语言中,我们可以使用os包进行跨平台的操作系统接口调用。这篇笔记我们简单介绍os包的使用。

文件系统操作

os包可用于文件和目录的创建、删除、移动、重命名等操作,也可以获取文件或目录的属性,以及对文件进行读写。

操作文件和文件夹

os包能够对文件和文件夹进行各种操作,比如重命名、删除、移动、修改权限等。

package main

import (
    "fmt"
    "os"
)

func main() {
    // 重命名文件
    err := os.Rename("D:\\1.txt", "D:\\1.csv")
    if err != nil {
        fmt.Println(err)
        return
    }

    // 移动文件
    err = os.Rename("D:\\2.txt", "D:\\workspace-go\\2.txt")
    if err != nil {
        fmt.Println(err)
        return
    }

    // 删除文件
    err = os.Remove("D:\\3.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
}

以上操作既可以用于文件也可以用于文件夹,这里注意Rename()方法同时兼具重命名和移动文件的功能。除此之外,对文件和文件夹的操作还有很多,这里就不逐一介绍了,都比较简单,具体参考文档即可。

获取文件属性

Stat()方法返回一个FileInfo结构体,其中包含了文件描述符的基本信息,包括文件名、文件大小、是否为目录等。

package main

import (
    "fmt"
    "os"
)

func main() {
    fileInfo, err := os.Stat("data.txt")
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Printf("Name %v\n", fileInfo.Name())
    fmt.Printf("Size %v\n", fileInfo.Size())
    fmt.Printf("IsDir %v\n", fileInfo.IsDir())
    fmt.Printf("Mode %v\n", fileInfo.Mode())
    fmt.Printf("ModTime %v\n", fileInfo.ModTime())
}

运行结果:

Name data.txt
Size 17
IsDir false
Mode -rw-rw-rw-
ModTime 2023-04-08 16:56:49.3889137 +0800 CST

文件IO

Go语言中,File结构体封装了操作系统的文件描述符,它实现了io包中的ReaderWriter接口,可用于文件内容的读写。os的Open()方法会返回*File类型的指针,读写文件等操作都需要通过*File指针来实现。

打开和关闭文件描述符

下面代码演示了如何打开和关闭文件描述符。

package main

import (
    "fmt"
    "os"
)

func main() {
    // 打开文件
    file, err := os.Open("data.txt")
    if err != nil {
        fmt.Println(err)
        return
    }

    // 关闭文件
    defer func(file *os.File) {
        err := file.Close()
        if err != nil {
            fmt.Println(err)
            return
        }
    }(file)
}

这里要注意的是,代码中关闭文件描述符使用了defer关键字确保其在函数运行结束后执行,而Close()方法本身也可能出错返回error,因此defer内部实际上是一个自执行的函数。

对于打开文件,上面使用的Open()方法默认以只读方式打开文件,Open()内部其实会调另一个方法OpenFile(),我们也可以直接调这个方法并传递打开方式,实现写、追加写等功能。

file, err := os.OpenFile("data.txt", os.O_CREATE|os.O_APPEND, 0666)

代码中,os.O_CREATE表示没有文件就创建,os.O_APPEND表示追加写,0666表示文件的操作权限,对应Linux下的8进制权限位rw-,对文件进行写操作时,我们应该设置w位,因此一般都传6

读取文件

下面例子代码中,我们以字节的方式对文件进行了读取。

package main

import (
    "fmt"
    "os"
)

func main() {
    // 打开文件
    file, err := os.Open("data.txt")
    if err != nil {
        fmt.Println(err)
        return
    }

    // 获取文件大小并创建对应大小的内存空间
    fileInfo, err := file.Stat()
    if err != nil {
        fmt.Println(err)
        return
    }
    bytes := make([]byte, fileInfo.Size())

    // 读取文件
    _, err = file.Read(bytes)
    if err != nil {
        fmt.Println(err)
        return
    }

    // 打印文件内容
    fmt.Println(string(bytes))

    // 关闭文件
    defer func(file *os.File) {
        err := file.Close()
        if err != nil {
            fmt.Println(err)
            return
        }
    }(file)
}

代码中,我们首先打开了一个文件描述符,然后获取了其大小(字节)。紧接着我们创建了一个相同大小的[]byte数组,然后将文件内容以字节方式读取到这个数组中。最后我们将其转化为字符串并打印,然后确保关闭了文件描述符。

注意对于较小的文本文件上面代码没有问题,但如果读取的是一个较大的二进制文件,我们对其进行操作时显然不可能将其一次性全部读入内存,此时一般都是采用循环的方式读取,Read()方法在读取后会返回当前读取了多少字节,同时也会修改当前读取的偏移量,这和大多数其它语言的标准库实现相同。此外,对于大文件我们可能还会更倾向于使用bufio来操作,它提供了内置的缓冲区,可以减少IO过程中系统调用的次数,提高读写效率。

写入数据到文件

下面代码演示了如何写入数据到文件中。

package main

import (
    "fmt"
    "os"
)

func main() {
    // 打开文件
    file, err := os.OpenFile("data.txt", os.O_RDWR|os.O_TRUNC, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }

    // 写入数据
    _, err = file.Write([]byte("Hello, world!"))
    if err != nil {
        fmt.Println(err)
        return
    }

    // 关闭文件
    defer func(file *os.File) {
        err := file.Close()
        if err != nil {
            fmt.Println(err)
            return
        }
    }(file)
}

代码中,我们将一个字符串转换为了[]byte数组形式,然后使用Write()方法将其写入文件。注意我们打开文件时指定了os.O_RDWR|os.O_TRUNC模式,表示文件内部会先被清空,然后写入数据。

读写环境变量

os包中,Environ()方法用于返回当前可执行程序的所有环境变量,Setenv()方法用于设置环境变量,而Getenv()方法用于获取环境变量。下面例子演示了如何使用这些方法操作环境变量。

package main

import (
    "fmt"
    "os"
)

func main() {
    // 获取所有环境变量
    envs := os.Environ()
    for _, env := range envs {
        fmt.Println(env)
    }
    // 设置环境变量
    err := os.Setenv("APP_KEY", "abc123")
    if err != nil {
        fmt.Println(err)
        return
    }
    // 获取环境变量
    env := os.Getenv("APP_KEY")
    fmt.Println(env)
}

执行外部命令

os/exec包用于创建进程并执行外部命令,下面例子中我们使用exec.Command()方法调用了curl程序。

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    cmd := exec.Command("curl", "https://www.bing.com")
    output, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(output))
}

代码中,Command()方法的参数为在PATH环境变量中的可执行程序以及执行时传入的命令行参数,其返回值是一个*Cmd类型的指针,我们在其上调用CombinedOutput()方法,启动curl程序并获取其在标准输出的结果作为返回值。

除此此外,os/exec还有一些其它的用法,具体可以参考SDK文档。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。