当前位置: 代码网 > it编程>前端脚本>Golang > golang封装一个执行命令行的函数(return stderr/stdout/exitcode)示例代码

golang封装一个执行命令行的函数(return stderr/stdout/exitcode)示例代码

2024年07月05日 Golang 我要评论
启动shell来执行命令行在 go 语言中,您可以使用 os/exec 包来执行外部命令,不通过调用 shell,并且能够获得进程的退出码、标准输出和标准错误输出。下面是一个封装好的函数 runcom

启动shell来执行命令行

在 go 语言中,您可以使用 os/exec 包来执行外部命令,不通过调用 shell,并且能够获得进程的退出码、标准输出和标准错误输出。下面是一个封装好的函数 runcommand,它符合您的需求:

package main
import (
	"bytes"
	"fmt"
	"os/exec"
)
// runcommand 函数接受一个命令行字符串,执行它并返回退出码、标准输出和标准错误输出
func runcommand(cmdline string) (exitcode int, stdout string, stderr string, err error) {
	// 解析命令行并准备执行
	cmd := exec.command("sh", "-c", cmdline)
	var outbuffer, errbuffer bytes.buffer
	cmd.stdout = &outbuffer
	cmd.stderr = &errbuffer
	// 执行命令
	err = cmd.run()
	stdout = outbuffer.string()
	stderr = errbuffer.string()
	// 获取退出码。如果命令执行出错,尝试获取退出码
	if err != nil {
		if exiterror, ok := err.(*exec.exiterror); ok {
			exitcode = exiterror.exitcode()
		} else {
			// 如果出现非 exiterror 类型错误,如执行文件不存在或无执行权限
			return 0, stdout, stderr, err
		}
	} else {
		// 命令成功执行,退出码为 0
		exitcode = 0
	}
	return exitcode, stdout, stderr, nil
}
func main() {
	// 测试 runcommand 函数
	exitcode, stdout, stderr, err := runcommand("ls -a")
	if err != nil {
		fmt.printf("命令执行出错: %v\n", err)
	}
	fmt.printf("退出码: %d\n标准输出: %s\n标准错误输出: %s\n", exitcode, stdout, stderr)
}

这里的 runcommand 函数接受一个命令行字符串,执行这条命令,并返回相关运行结果。这个函数使用 exec.command 来避免调用 shell。我们通过将命令传给 sh-c 参数,间接地执行命令,但绕过了可执行文件的正常寻找机制来防止 shell 注入问题。

结果输出会分别写入到 bytes.buffer,这样您就可以读取并将其转换为字符串。如果存在错误,且错误为 *exec.exiterror 类型,那么说明命令返回了非零退出码,这时我们可以获取并返回这个退出码;如果是其他类型的错误,则直接返回错误。

这个函数的返回值包含了进程的退出码,标准输出和标准错误输出的字符串表示,以及一个可能的错误值。如果命令成功执行(即便其退出码非零),则错误值为 nil。如果函数中出现了 go 运行时错误,比如命令不存在或无法启动,那么会返回这个错误。

不启动shell的方式

确实,前面的示例通过调用 sh 间接运行命令,依然会启动 shell。若要避免这种情况,我们会直接分解命令行字符串为命令名和参数,然后不通过 shell 直接执行这个命令。以下是经过修改的 runcommand 函数,它将使用 strings.fields 来解析传入的命令行字符串,并且不会启动 shell。

package main
import (
	"bytes"
	"fmt"
	"os/exec"
	"strings"
)
// runcommand 函数接受一个命令行字符串,执行它(无 shell 启动)并返回退出码、标准输出和标准错误输出
func runcommand(cmdline string) (exitcode int, stdout string, stderr string, err error) {
	// 使用 strings.fields 解析命令行,分离出命令和参数
	args := strings.fields(cmdline)
	if len(args) == 0 {
		return 0, "", "", fmt.errorf("no command provided")
	}
	cmdname := args[0]
	cmdargs := args[1:]
	// 准备执行不经过 shell 的命令
	cmd := exec.command(cmdname, cmdargs...)
	var outbuffer, errbuffer bytes.buffer
	cmd.stdout = &outbuffer
	cmd.stderr = &errbuffer
	// 执行命令
	err = cmd.run()
	stdout = outbuffer.string()
	stderr = errbuffer.string()
	// 获取退出码。如果命令执行出错,尝试获取退出码
	if exiterr, ok := err.(*exec.exiterror); ok {
		err = nil // 清理错误,我们已经获取了退出码
		exitcode = exiterr.exitcode()
	} else if err != nil {
		// 如果出现非 exiterror 类型错误,如执行文件不存在或无执行权限
		exitcode = -1
	} else {
		// 命令成功执行,退出码为 0
		exitcode = 0
	}
	return exitcode, stdout, stderr, err
}
func main() {
	// 测试 runcommand 函数
	exitcode, stdout, stderr, err := runcommand("ls -a -l -h")
	if err != nil {
		fmt.printf("命令执行出错: %v\n", err)
	}
	fmt.printf("退出码: %d\n标准输出: %s\n标准错误输出: %s\n", exitcode, stdout, stderr)
}

请注意,这个函数假设命令行参数不含有引号封闭的参数或其他需要 shell 解析的元素(如通配符 * 或重定向 >)。它简单地把空白符作为参数分隔符,这是一个限制。如果你需要解析复杂的命令行字符串(包含引号或特殊字符),那么你需要一个更强大的解析器,或者需要以某种方式预处理命令行字符串。

因为直接执行命令而不经由 shell,原本在 shell 中利用的特性(如通配符展开、环境变量替换等)将不再可用。确保你的命令行字符串直接传递给系统调用是安全的。如果需要 shell 特性,那么你可能还需要调用 shell,但这正是你想要避免的。

关于类型断言的表达式err.(*exec.exiterror)

在 go 语言中, err.(*exec.exiterror) 是一个类型断言的表达式。类型断言用于检查变量 err 是否为特定类型,或者能否转换为特定类型。在这种情况下,类型断言检查 err 是否可以被断言为 *exec.exiterror 类型。

exec.exiterroros/exec 包中的一个结构体类型,它实现了 error 接口,并包含了退出错误的相关信息。当一个外部命令运行结束并返回一个非零退出状态时,os/exec 包的 runwait 函数会返回一个 error 类型的值,这通常就是一个 *exec.exiterror 类型的实例。

一个类型断言通常具有以下两种形式:

  • x.(t):这种形式用于断言变量 x 是否为类型 t。如果断言成功,就会得到 x 的类型 t 的值。如果失败,则会产生一个运行时错误。因此,使用这种形式时必须非常确定 x 能够断言为类型 t
  • x, ok := x.(t):这种类型断言形式比较安全,因为如果断言失败,它不会抛出错误,而是将 ok 的值设为 false,同时 x 的值会被设为类型 t 的零值。在你不确定 x 的类型是否为 t 或者你想要安全地检查类型时,应该使用这种形式。

在提供的代码示例中,我们使用了第二种形式的类型断言:

if exiterr, ok := err.(*exec.exiterror); ok {
    exitcode = exiterr.exitcode()
    // ...
}

这里,我们尝试将 errorerr 断言为类型 *exec.exiterror。如果断言成功(表示外部命令执行失败,并返回了一个非零退出代码),变量 ok 的值将为 trueexiterr 将为 *exec.exiterror 类型,我们可以通过它的 exitcode() 方法获取实际的退出代码。如果断言失败(err 不是 *exec.exiterror 类型),那么 okfalse,这通常表示 err 是另一种类型的错误或 nil

到此这篇关于golang封装一个执行命令行的函数(return stderr/stdout/exitcode)的文章就介绍到这了,更多相关golang执行命令行的函数内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com