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结构体表示一个准备或正在执行的外部命令。- 调用函数
Command或CommandContext可以构造一个*Cmd对象。 - 调用
Run、Start、Output、CombinedOutput方法可以运行*Cmd对象所代表的命令。 - 调用
Environ方法可以获取命令执行时的环境变量。 - 调用
StdinPipe、StdoutPipe、StderrPipe方法用于获取管道对象。 - 调用
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 对象的 Stdout 和 Stderr 属性,重定向标准输出和标准错误输出到当前进程:
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!
}
设置和使用环境变量 #
*Cmd 的 Environ 方法可以获取环境变量,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 对象提供的 StdinPipe、StdoutPipe、StderrPipe 三个方法用于获取管道对象。故名思义,三者分别对应标准输入、标准输出、标准错误输出的管道对象。
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包来进行更多的时间操作。