详解Go语言中用 os/exec 执行命令的五种方法

完整系列教程详见:http://golang.iswbm.com

在 Golang 中用于执行命令的库是 os/exec,exec.Command 函数返回一个 Cmd 对象,根据不同的需求,可以将命令的执行分为三种情况

  • 只执行命令,不获取结果
  • 执行命令,并获取结果(不区分 stdout 和 stderr)
  • 执行命令,并获取结果(区分 stdout 和 stderr)

第一种:只执行命令,不获取结果

直接调用 Cmd 对象的 Run 函数,返回的只有成功和失败,获取不到任何输出的结果。

package main

import (
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("ls", "-l", "/var/log/")
	err := cmd.Run()
	if err != nil {
		log.Fatalf("cmd.Run() failed with %s\n", err)
	}
}

第二种:执行命令,并获取结果

有时候我们执行一个命令就是想要获取输出结果,此时你可以调用 Cmd 的 CombinedOutput 函数。

package main

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

func main() {
	cmd := exec.Command("ls", "-l", "/var/log/")
	out, err := cmd.CombinedOutput()
	if err != nil {
 fmt.Printf("combined out:\n%s\n", string(out))
		log.Fatalf("cmd.Run() failed with %s\n", err)
	}
	fmt.Printf("combined out:\n%s\n", string(out))
}

CombinedOutput 函数,只返回 out,并不区分 stdout 和 stderr。如果你想区分他们,可以直接看第三种方法。

$ go run demo.go
combined out:
total 11540876
-rw-r--r-- 2 root root  4096 Oct 29 2018 yum.log
drwx------ 2 root root  94 Nov 6 05:56 audit
-rw-r--r-- 1 root root 185249234 Nov 28 2019 message
-rw-r--r-- 2 root root 16374 Aug 28 10:13 boot.log

不过在那之前,我却发现一个小问题:有时候,shell 命令能执行,并不代码 exec 也能执行。

比如我只想查看 /var/log/ 目录下的 log 后缀名的文件呢?有点 Linux 基础的同学,都会用这个命令

$ ls -l /var/log/*.log
total 11540
-rw-r--r-- 2 root root  4096 Oct 29 2018 /var/log/yum.log
-rw-r--r-- 2 root root 16374 Aug 28 10:13 /var/log/boot.log

按照这个写法将它放入到 exec.Command

package main

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

func main() {
	cmd := exec.Command("ls", "-l", "/var/log/*.log")
	out, err := cmd.CombinedOutput()
	if err != nil {
 fmt.Printf("combined out:\n%s\n", string(out))
		log.Fatalf("cmd.Run() failed with %s\n", err)
	}
	fmt.Printf("combined out:\n%s\n", string(out))
}

什么情况?居然不行,报错了。

$ go run demo.go
combined out:
ls: cannot access /var/log/*.log: No such file or directory

2020/11/11 19:46:00 cmd.Run() failed with exit status 2
exit status 1

为什么会报错呢?shell 明明没有问题啊

其实很简单,原来 ls -l /var/log/*.log 并不等价于下面这段代码。

exec.Command("ls", "-l", "/var/log/*.log")

上面这段代码对应的 Shell 命令应该是下面这样,如果你这样子写,ls 就会把参数里的内容当成具体的文件名,而忽略通配符 *

$ ls -l "/var/log/*.log"
ls: cannot access /var/log/*.log: No such file or directory

第三种:执行命令,并区分stdout 和 stderr

上面的写法,无法实现区分标准输出和标准错误,只要换成下面种写法,就可以实现。

package main

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

func main() {
	cmd := exec.Command("ls", "-l", "/var/log/*.log")
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout // 标准输出
	cmd.Stderr = &stderr // 标准错误
	err := cmd.Run()
	outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
	fmt.Printf("out:\n%s\nerr:\n%s\n", outStr, errStr)
	if err != nil {
		log.Fatalf("cmd.Run() failed with %s\n", err)
	}
}

输出如下,可以看到前面的报错内容被归入到标准错误里

$ go run demo.go
out:

err:
ls: cannot access /var/log/*.log: No such file or directory

2020/11/11 19:59:31 cmd.Run() failed with exit status 2
exit status 1

第四种:多条命令组合,请使用管道

将上一条命令的执行输出结果,做为下一条命令的参数。在 Shell 中可以使用管道符 | 来实现。

比如下面这条命令,统计了 message 日志中 ERROR 日志的数量。

$ grep ERROR /var/log/messages | wc -l
19

类似的,在 Golang 中也有类似的实现。

package main
import (
 "os"
 "os/exec"
)
func main() {
 c1 := exec.Command("grep", "ERROR", "/var/log/messages")
 c2 := exec.Command("wc", "-l")
 c2.Stdin, _ = c1.StdoutPipe()
 c2.Stdout = os.Stdout
 _ = c2.Start()
 _ = c1.Run()
 _ = c2.Wait()
}

输出如下

$ go run demo.go
19

第五种:设置命令级别的环境变量

使用 os 库的 Setenv 函数来设置的环境变量,是作用于整个进程的生命周期的。

package main
import (
	"fmt"
	"log"
	"os"
	"os/exec"
)
func main() {
	os.Setenv("NAME", "wangbm")
	cmd := exec.Command("echo", os.ExpandEnv("$NAME"))
	out, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatalf("cmd.Run() failed with %s\n", err)
	}
	fmt.Printf("%s", out)
}

只要在这个进程里,NAME 这个变量的值都会是 wangbm,无论你执行多少次命令

$ go run demo.go
wangbm

如果想把环境变量的作用范围再缩小到命令级别,也是有办法的。

为了方便验证,我新建个 sh 脚本,内容如下

$ cat /home/wangbm/demo.sh
echo $NAME
$ bash /home/wangbm/demo.sh # 由于全局环境变量中没有 NAME,所以无输出

另外,demo.go 里的代码如下

package main
import (
	"fmt"
	"os"
	"os/exec"
)

func ChangeYourCmdEnvironment(cmd * exec.Cmd) error {
	env := os.Environ()
	cmdEnv := []string{}

	for _, e := range env {
		cmdEnv = append(cmdEnv, e)
	}
	cmdEnv = append(cmdEnv, "NAME=wangbm")
	cmd.Env = cmdEnv

	return nil
}

func main() {
	cmd1 := exec.Command("bash", "/home/wangbm/demo.sh")
 ChangeYourCmdEnvironment(cmd1) // 添加环境变量到 cmd1 命令: NAME=wangbm
	out1, _ := cmd1.CombinedOutput()
	fmt.Printf("output: %s", out1)

	cmd2 := exec.Command("bash", "/home/wangbm/demo.sh")
	out2, _ := cmd2.CombinedOutput()
	fmt.Printf("output: %s", out2)
}

执行后,可以看到第二次执行的命令,是没有输出 NAME 的变量值。

$ go run demo.go
output: wangbm
output: 

到此这篇关于Go语言中用 os/exec 执行命令的五种方法的文章就介绍到这了,更多相关Go语言执行命令内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Django如何使用asyncio协程和ThreadPoolExecutor多线程

    Django视图函数执行,不在主线程中,直接loop = asyncio.new_event_loop() # 不能loop = asyncio.get_event_loop() 会触发RuntimeError: There is no current event loop in thread 因为asyncio程序中的每个线程都有自己的事件循环,但它只会在主线程中为你自动创建一个事件循环.所以如果你asyncio.get_event_loop在主线程中调用一次,它将自动创建一个循环对象并将其设

  • 如何使用Go语言实现远程执行命令

    前言 远程执行命令有什么用?为什么要远程执行命令? 如果你只有2,3台服务器需要管理的时候,远程执行命令确实没有没多大作用,你可以登录到每台服务器上去完成各种操作. 当你的服务器大于3台的时候,远程执行的命令的方式就可以大大提高你的生产力了. 如果你有一个可以远程执行命令的工具,那么就可以像操作单台机器那样操作多台机器,机器越多,效率提高的越多. 远程执行命令最常用的方法就是利用 SSH 协议,将命令发送到远程机器上执行,并获取返回结果. 一般命令 所谓一般命令,就是在一定时间内会执行完的命令.

  • Failed to execute goal org...的解决办法

    背景:本项目使用JDK1.8 编译maven工程的时候出现如下错误: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1 pom中如下配置maven插件,配置中声明使用JDK1.8: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</

  • golang执行命令获取执行结果状态(推荐)

    这几天在用golang写一个工具,要执行外部命令工具,而且还要将外部命令工具输出的日志也要输出出来.网上找了一下,资料很多,关键是执行的结果成功或失败状态没找到好的方法获取到. 刚开始想的是看错误日志,如果有错误日志,那么就是执行失败.测试的时候发现这样不行,发现有些时候会用error输出日志,但不一定就是执行失败.后来想用日志中的关键字匹配,因为有些命令执行成功或失败都是有关键字输出的,测试发现也不太好. 最后没办法,看了一下Cmd.Wait()方法的实现,突然眼前一亮,找到方法了,有一个Cm

  • 详解Go语言中用 os/exec 执行命令的五种方法

    完整系列教程详见:http://golang.iswbm.com 在 Golang 中用于执行命令的库是 os/exec,exec.Command 函数返回一个 Cmd 对象,根据不同的需求,可以将命令的执行分为三种情况 只执行命令,不获取结果 执行命令,并获取结果(不区分 stdout 和 stderr) 执行命令,并获取结果(区分 stdout 和 stderr) 第一种:只执行命令,不获取结果 直接调用 Cmd 对象的 Run 函数,返回的只有成功和失败,获取不到任何输出的结果. pack

  • Shell.Application执行命令的五种方法小结

    当初老马写这个版本时,我测试过简繁两个2003版本的,当是是好用的.我猜测很可能与2003打了ms06014补丁有关.kyo在冰点论坛提出了解决方法,我今天下午研究了一下这个组件,发现它有5种方法可以执行命令.以下是vbs版本(asp版不提供了,如何利用asp版本在2003的webshell中运行命令,自己解决吧:-): 复制代码 代码如下: Set objShellApp = CreateObject("Shell.Application") Set objFolder = objS

  • 详解eclipse将项目打包成jar文件的两种方法及问题解决方法

    第一种:利用eclipse中自带的export功能 第一种方法分两种情况先来看第一种情况:没有引用外部jar的项目打包 步骤一:右键点击项目选择导出(export),选择java>jar文件(不是选择可运行jar文件) 步骤二:选择你要导出的项目以及文件,指定文件导出路径.连续点击两个下一步后到第四步. 步骤三:选择主类. 按照以上步骤即可完成对一个不引用外部jar项目的打包. 第二种情况:没有引用外部jar的项目打包 当我们引用了外部jar后,使用eclipse自带的export打包略显繁琐.

  • 详解在java中进行日期时间比较的4种方法

    1. Date.compareTo() java.util.Date提供了在Java中比较两个日期的经典方法compareTo(). 如果两个日期相等,则返回值为0. 如果Date在date参数之后,则返回值大于0. 如果Date在date参数之前,则返回值小于0. @Test void testDateCompare() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

  • 详解向scrapy中的spider传递参数的几种方法(2种)

    有时需要根据项目的实际需求向spider传递参数以控制spider的行为,比如说,根据用户提交的url来控制spider爬取的网站.在这种情况下,可以使用两种方法向spider传递参数. 第一种方法,在命令行用crawl控制spider爬取的时候,加上-a选项,例如: scrapy crawl myspider -a category=electronics 然后在spider里这样写: import scrapy class MySpider(scrapy.Spider): name = 'm

  • 详解Java Web如何限制访问的IP的两种方法

    前一阵子因为在做项目时碰到了这个功能,现在好好总结一下,至于为什么要限制IP访问,我就不多说了.然后百度了一下,现在主要有两种方式去限制IP访问,第一种是最简单的方便的,第二种是通过过滤器来限制访问.下面我简单介绍一下第一种方式,着重介绍第二种. 第一种方式(Tomcat配置项配置允许或限制IP访问) 这种是最简单的快捷的,主要就涉及Tomcat的server.xml配置. 第一步:找到server.xml文件在哪,在Tomcat的目录下的conf文件夹下. 第二步:打开server.xml文件

  • dos、bat批处理延时执行命令的两种方法

    来源:vbs小铺 第一种方法: ping 127.0.0.1 -n 5000>nul dir c:\ >c:\1.txt 延时5分钟后执行dir c:\ 第二种方法 :: Sleep.bat - Sleep/Delay/Wait n seconds :: Herbert Kleebauer(Germany) - 2005/05/29 :: Modified by Will Sort - 2005/06/02 @echo off echo q | debug>nul echo Bj@jzh

  • 批处理bat 延时执行命令的两种方法

    第一种方法: ping 127.0.0.1 -n 5000>nul dir c:\ >c:\1.txt 延时5分钟后执行dir c:\ 第二种方法 :: Sleep.bat - Sleep/Delay/Wait n seconds :: Herbert Kleebauer(Germany) - 2005/05/29 :: Modified by Will Sort - 2005/06/02 @echo off  echo q | debug>nul echo Bj@jzh`0X-`/PP

  • 详解C语言中的错误报告errno与其相关应用方法

    C语言标准库中的错误报告用法有三种形式. 1.errno errno在<errno.h>头文件中定义,如下 #ifndef errno extern int errno; #endif 外部变量errno保存库程序中实现定义的错误码,通常被定义为errno.h中以E开头的宏, 所有错误码都是正整数,如下例子 # define EDOM 33 /* Math argument out of domain of function. */ EDOM的意思是参数不在数学函数能接受的域中,稍后的例子中用

  • 详解在Node.js中发起HTTP请求的5种方法

    创建HTTP请求使现代编程语言的核心功能之一,也是很多程序员在接触到新的开发环境时最先遇到的技术之一.在Node.js中有相当多的解决方案,其中有语言内置功能,也有开源社区贡献的开发库.下面咱们来看一下比较流行的几种方式. 在开始之前,请先在自己的计算机上安装最新版的node.js和npm. HTTP - 标准库 首先是标准库中默认的 HTTP 模块.这个模块无需安装依赖外部即可使用,做到了真正的即插即用.缺点是与其他解决方案相比,用起来不是那么友好. 下面的代码将向NASA的API发送一个 G

随机推荐