Golang 调用系统命令
创建系统命令调用⌗
Command⌗
cmd := exec.Command("/bin/bash", "-c", "command")
方法原型
func Command(name string, arg ...string) *Cmd {}
CommandContext⌗
ctx, cancelFun := context.WithTimeout(context.Background, 5 * time.Second)
cmd := exec.CommandContext(ctx, "/bin/bash", "-c", "command")
方法原型
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd {}
这种方式,可以通过设置 context
来启用强行结束运行的命令。
你可能会感觉奇怪,为什么 name
参数是 /bin/bash
而不是直接输入命令?其实我们在终端输入命令按下回车键之后,是需要 Shell 去解析,而如今大多数的运行环境中都是 bash,也就是说当你在终端命令中输入 ls -la
时,系统真正执行的时 /bin/bash -c “ls -la”。
运行方式⌗
Run⌗
Run
方法时同步的,也就是说当代码执行到 Run
方法时会阻塞等待命令执行完成并返回。
command := exec.Command("ls", "-la")
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
command.Stdout = stdout
command.Stderr = stderr
if err := command.Run(); err != nil {
log.Println(err)
}
log.Println(stdout.String(), stderr.String())
Start⌗
Start
方法是异步的,会立即返回,可以调用 Wait
方法"等待"命令执行完成。Run 方法就是一个例子:
func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
}
return c.Wait()
}
Output⌗
这个方法只返回命令执行后的 stdout
也就是标准输出的内容。
stdout, err := cmd.Output()
CombinedOutput⌗
这个方法会将 stdout
和 stderr
合并到一起,并返回。
output, err := cmd.CombinedOutput()
使用管道链接多个命令⌗
ps := exec.Command("/bin/bash", "-c", "ps -ef")
grep := exec.Command("/bin/bash", "-c", "grep -i ssh")
// 创建管道
r, w := io.Pipe()
defer r.Close()
defer w.Close()
ps.Stdout = w // ps向管道的一端写
grep.Stdin = r // grep从管道的一端读
var buffer bytes.Buffer
grep.Stdout = &buffer // grep的输出为buffer
_ = ps.Start()
_ = grep.Start()
ps.Wait()
w.Close()
grep.Wait()
io.Copy(os.Stdout, &buffer) // buffer拷贝到系统标准输出
通过上面的代码可以看到,其原理就是将一个命令的 stdout
输入到另一个命令的 stdin
。
使用指定用户运行命令⌗
业务中我们可能需要以特定用户来运行一个命令,那么可以参照如下方法
import (
"os/user"
"strconv"
"syscall"
)
cmd := exec.CommandContext(ctx, "/bin/bash", "-c", "command")
cmd.Env = env
cmd.Dir = dir
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
sysuser, err := user.Lookup(username) // 通过用户名来获取用户信息
if err != nil {
return err
}
uid, err := strconv.Atoi(sysuser.Uid) // 将UID的类型转换成 uint32
gid, err := strconv.Atoi(sysuser.Gid) // 将GID的类型转换成 uint32
// 设置 Credential
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
Groups: nil,
NoSetGroups: false,
}
如果用户存在则会以指定的用户运行该命令。
强行结束命令⌗
cmd := exec.Command("/bin/bash", "-c", "sleep 5")
if err := cmd.Process.Kill(); err != nil {
return err
}
如果是在协程外部,则建议使用 CommandContext
方法来创建命令,这样可以通过 context
来控制命令的生命周期。
I hope this is helpful, Happy hacking…