跳过正文
  1. 文章/
  2. GoLang/
  3. 常用包/
  4. 标准包/

9、os

·2864 字·6 分钟· loading · loading · ·
GoLang 常用包 标准包
GradyYoung
作者
GradyYoung
标准包 - 点击查看当前系列文章
§ 9、os 「 当前文章 」

os包是Go语言标准库中一个非常重要的包,它提供了一系列用于操作系统交互的功能,使开发者可以方便地进行文件和目录操作、环境变量管理、进程管理、信号处理等。

环境变量
#

环境变量是操作系统用于传递配置信息的一种机制。在Go语言中,os包提供了一些函数用于读取、设置和删除环境变量。

读取环境变量
#

os.Getenv函数可以读取指定的环境变量。如果环境变量不存在,返回空字符串。

os.LookupEnv函数除了返回环境变量的值,还返回一个布尔值,表示环境变量是否存在。

package main

import (
    "fmt"
    "os"
)

func main() {
    // 读取环境变量
    value := os.Getenv("PATH")
    fmt.Println("PATH:", value)

    // 查找环境变量
    value, exists := os.LookupEnv("PATH")
    if exists {
        fmt.Println("PATH exists:", value)
    } else {
        fmt.Println("PATH does not exist")
    }
}

设置环境变量
#

使用os.Setenv函数可以设置指定的环境变量。如果环境变量已存在,其值将被更新:

package main

import (
    "fmt"
    "os"
)

func main() {
    // 设置环境变量
    err := os.Setenv("MY_VAR", "Hello, World!")
    if err != nil {
        fmt.Println("Error setting environment variable:", err)
        return
    }

    // 读取设置的环境变量
    value := os.Getenv("MY_VAR")
    fmt.Println("MY_VAR:", value)
}

删除环境变量
#

使用os.Unsetenv函数可以删除指定的环境变量:

package main

import (
    "fmt"
    "os"
)

func main() {
    // 设置环境变量
    err := os.Setenv("MY_VAR", "Hello, World!")
    if err != nil {
        fmt.Println("Error setting environment variable:", err)
        return
    }

    // 删除环境变量
    err = os.Unsetenv("MY_VAR")
    if err != nil {
        fmt.Println("Error unsetting environment variable:", err)
        return
    }

    // 读取删除的环境变量
    value := os.Getenv("MY_VAR")
    fmt.Println("MY_VAR:", value)  // 应该输出空字符串
}

os/exec
#

查找可执行文件
#

在环境变量PATH指定的目录中搜索可执行文件,如file中有斜杠,则只在当前目录搜索。返回完整路径或者相对于当前目录的一个相对路径。

func LookPath(file string) (string, error) 

示例

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	// 查找可执行文件 go 的位置
	path, err := exec.LookPath("go")
	if err != nil {
		panic(err)
	}
	fmt.Println(path)

	// 下面的内容就是执行命令
	command := exec.Command(path, "version")
	output, err := command.Output()
	if err != nil {
		panic(err)
	}
	fmt.Println(string(output))
}

Command
#

type Cmd
func Command(name string, arg ...string) *Cmd
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd
func (c *Cmd) CombinedOutput() ([]byte, error)
func (c *Cmd) Environ() []string
func (c *Cmd) Output() ([]byte, error)
func (c *Cmd) Run() error
func (c *Cmd) Start() error
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
func (c *Cmd) String() string
func (c *Cmd) Wait() error
  • Cmd 结构体表示一个准备或正在执行的外部命令。
  • 调用函数 CommandCommandContext 可以构造一个 *Cmd 对象。
  • 调用 RunStartOutputCombinedOutput 方法可以运行 *Cmd 对象所代表的命令。
  • 调用 Environ 方法可以获取命令执行时的环境变量。
  • 调用 StdinPipeStdoutPipeStderrPipe 方法用于获取管道对象。
  • 调用 Wait 方法可以阻塞等待命令执行完成。
  • 调用 String 方法返回命令的字符串形式。

Run 和 Start
#

Start方法调用后开始执行命令,但是不会阻塞当前 goroutine,代码会继续往下执行。

Run方法会阻塞等待命令执行完成,实际上 Run 方法就等于 Start + Wait 方法,如下是 Run 方法源码的实现:

func (c *Cmd) Run() error {
 if err := c.Start(); err != nil {
  return err
 }
 return c.Wait()
}

创建带有 context 的命令
#

os/exec 还提供了一个 exec.CommandContext 构造函数可以创建一个带有 context 的命令。那么我们就可以利用 context 的特性来控制命令的执行了。

package main

import (
	"context"
	"log"
	"os/exec"
	"time"
)

func main() {
	// 创建一个超时控制的 ctx
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()
	// 当命令执行超时会收到 killed 信号自动取消。
	cmd := exec.CommandContext(ctx, "sleep", "5")

	if err := cmd.Run(); err != nil {
		log.Fatalf("Command failed: %v\n", err) // Command failed: signal: killed
	}
}

获取命令的输出
#

无论是调用 *Cmd.Run 还是 *Cmd.Start 方法,默认情况下执行命令后控制台不会得到任何输出。

我们可以使用 *Cmd.Output 方法来执行命令,以此来获取命令的标准输出:

package main

import (
	"fmt"
	"log"
	"os/exec"
)

func main() {
	// 创建一个命令
	cmd := exec.Command("echo", "Hello, World!")

	// 执行命令,并获取命令的输出,Output 内部会调用 Run 方法
	output, err := cmd.Output()
	if err != nil {
		log.Fatalf("Command failed: %v", err)
	}

	fmt.Println(string(output)) // Hello, World!
}

获取组合的标准输出和错误输出
#

*Cmd.CombinedOutput 方法能够在运行命令后,返回其组合的标准输出和标准错误输出:

package main

import (
	"fmt"
	"log"
	"os/exec"
)

func main() {
	// 使用一个命令,既产生标准输出,也产生标准错误输出
	cmd := exec.Command("sh", "-c", "echo 'This is stdout'; echo 'This is stderr' >&2")

	// 获取 标准输出 + 标准错误输出 组合内容
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatalf("Command execution failed: %v", err)
	}

	// 打印组合输出
	fmt.Printf("Combined Output:\n%s", string(output))
}

设置标准输出和错误输出
#

我们可以利用 *Cmd 对象的 StdoutStderr 属性,重定向标准输出和标准错误输出到当前进程:

package main

import (
	"log"
	"os"
	"os/exec"
)

func main() {
	cmd := exec.Command("ls", "-l")

	// 设置标准输出和标准错误输出到当前进程,执行后可以在控制台看到命令执行的输出
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Fatalf("Command failed: %v", err)
	}
}

使用标准输入传递数据
#

package main

import (
	"bytes"
	"fmt"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("grep", "hello")

	// 通过标准输入传递数据给命令
	cmd.Stdin = bytes.NewBufferString("hello world!")

	// 获取标准输出
	output, err := cmd.Output()
	if err != nil {
		log.Fatalf("Command failed: %v", err)
		return
	}

	fmt.Println(string(output)) // hello world!
}

设置和使用环境变量
#

*CmdEnviron 方法可以获取环境变量,Env 属性则可以设置环境变量:

package main

import (
	"fmt"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("printenv", "ENV_VAR")

	log.Printf("ENV: %+v\n", cmd.Environ())

	// 设置环境变量
	cmd.Env = append(cmd.Environ(), "ENV_VAR=HelloWorld")

	log.Printf("ENV: %+v\n", cmd.Environ())

	// 获取输出
	output, err := cmd.Output()
	if err != nil {
		log.Fatalf("Command failed: %v", err)
	}

	fmt.Println(string(output)) // HelloWorld
}

管道
#

os/exec 支持管道功能,*Cmd 对象提供的 StdinPipeStdoutPipeStderrPipe 三个方法用于获取管道对象。故名思义,三者分别对应标准输入、标准输出、标准错误输出的管道对象。

package main

import (
	"fmt"
	"log"
	"os/exec"
)

func main() {
	// 命令中使用了管道
	cmdEcho := exec.Command("echo", "hello world\nhi there")

	outPipe, err := cmdEcho.StdoutPipe()
	if err != nil {
		log.Fatalf("Command failed: %v", err)
	}

	// 注意,这里不能使用 Run 方法阻塞等待,应该使用非阻塞的 Start 方法
	if err := cmdEcho.Start(); err != nil {
		log.Fatalf("Command failed: %v", err)
	}

	cmdGrep := exec.Command("grep", "hello")
	cmdGrep.Stdin = outPipe
	output, err := cmdGrep.Output()
	if err != nil {
		log.Fatalf("Command failed: %v", err)
	}

	fmt.Println(string(output)) // hello world
}

首先创建一个用于执行 echo 命令的 *Cmd 对象 cmdEcho,并调用它的 StdoutPipe 方法获得标准输出管道对象 outPipe

然后调用 Start 方法非阻塞的方式执行 echo 命令;

接着创建一个用于执行 grep 命令的 *Cmd 对象 cmdGrep,将 cmdEcho 的标准输出管道对象赋值给 cmdGrep.Stdin 作为标准输入,这样,两个命令就通过管道串联起来了;

最终通过 cmdGrep.Output 方法拿到 cmdGrep 命令的标准输出。

指定工作目录
#

可以通过指定 *Cmd 对象的的 Dir 属性来指定工作目录:

package main

import (
	"log"
	"os"
	"os/exec"
)

func main() {
	cmd := exec.Command("cat", "demo.log")
	cmd.Stdout = os.Stdout // 获取标准输出
	cmd.Stderr = os.Stderr // 获取错误输出

	// cmd.Dir = "/tmp" // 指定绝对目录
	cmd.Dir = "." // 指定相对目录

	if err := cmd.Run(); err != nil {
		log.Fatalf("Command failed: %v", err)
	}
}

捕获退出状态
#

这里执行 ls 命令来查看一个不存在的目录 /nonexistent,程序退出状态码必然不为 0

package main

import (
	"errors"
	"log"
	"os/exec"
)

func main() {
	// 查看一个不存在的目录
	cmd := exec.Command("ls", "/nonexistent")

	// 运行命令
	err := cmd.Run()

	// 检查退出状态
	var exitError *exec.ExitError
	if errors.As(err, &exitError) {
		log.Fatalf("Process PID: %d exit code: %d", exitError.Pid(), exitError.ExitCode()) // 打印 pid 和退出码
	}
}
// 2023/05/26 10:10:43 Process PID: 62746 exit code: 1

os/signal
#

信号是操作系统用来通知进程发生异步事件的一种机制。Go语言的os包和os/signal包提供了处理系统信号的功能,允许程序捕获和响应各种系统信号。

捕获系统信号
#

使用os/signal包中的signal.Notify函数可以捕获系统信号(syscall包下定义)。

常见的信号包括:

SIGINT:中断信号

SIGTERM:终止信号

SIGHUP:挂起信号

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // 创建信号通道
    sigs := make(chan os.Signal, 1)

    // 注册要接收的信号
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    // 等待信号
    sig := <-sigs
    fmt.Println("Received signal:", sig)
}

自定义信号处理函数
#

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // 创建信号通道
    sigs := make(chan os.Signal, 1)

    // 注册要接收的信号
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    // 捕获信号并执行自定义处理函数
    go func() {
        sig := <-sigs
        fmt.Println("Received signal:", sig)
        cleanup()
        os.Exit(0)
    }()

    // 模拟长时间运行的任务
    fmt.Println("Running... Press Ctrl+C to exit.")
    select {}
}

// 自定义清理函数
func cleanup() {
    fmt.Println("Performing cleanup tasks...")
}

os/user
#

使用os/user包可以获取当前用户和组的信息,包括用户名、用户ID、组名和组ID等。

获取当前用户信息
#

使用user.Current函数可以获取当前用户的信息:

package main

import (
    "fmt"
    "os/user"
)

func main() {
    user, err := user.Current()
    if err != nil {
        fmt.Println("Error getting current user:", err)
        return
    }

    fmt.Println("Username:", user.Username)
    fmt.Println("User ID:", user.Uid)
    fmt.Println("Group ID:", user.Gid)
    fmt.Println("Home Directory:", user.HomeDir)
}

获取指定用户信息
#

使用user.Lookup函数可以根据用户名获取指定用户的信息:

package main

import (
    "fmt"
    "os/user"
)

func main() {
    user, err := user.Lookup("root")
    if err != nil {
        fmt.Println("Error looking up user:", err)
        return
    }

    fmt.Println("Username:", user.Username)
    fmt.Println("User ID:", user.Uid)
    fmt.Println("Group ID:", user.Gid)
    fmt.Println("Home Directory:", user.HomeDir)
}

系统时钟与时间管理
#

使用os包可以获取系统的当前时间,还可以通过time包来进行更多的时间操作。

标准包 - 点击查看当前系列文章
§ 9、os 「 当前文章 」