点击上方“朱小厮的博客”,选择“设为星标”

回复”1024“获取独家整理的学习资料

欢迎跳转到本文的原文链接:https://honeypps.com/golang/go-standard-lib-os-exec-guide/

有时我们在写程序的时候会需要调用系统的某个命令来完成一些任务。Go语言os/exec标准库就提供这种调用外部命令的功能。

如下面的代码调用ls命令来查看指定目录下面的文件:

import ("os""os/exec"
)func ls(path string) error {cmd := exec.Command("ls", path)cmd.Stdout = os.Stdoutreturn cmd.Run()
}func main() {if err := ls("/"); err != nil {panic(err)}
}

再举一个例子,将小写转成大写:

import ("bytes""fmt""log""os/exec""strings"
)func main() {cmd := exec.Command("tr", "a-z", "A-Z")cmd.Stdin = strings.NewReader("some input")var out bytes.Buffercmd.Stdout = &outif err := cmd.Run(); err != nil {log.Fatal(err)}fmt.Printf("in all caps: %q\n", out.String())
}

概述

golang下的os/exec包执行外部命令,它将os.StartProcess进行包装使得它更容易映射到stdin/stdout、管道I/O。

与C语言或者其他语言中的“系统”库调用不同,os/exec包并不调用系统shell,也不展开任何glob模式,也不处理通常由shell完成的其他扩展、管道或重定向。这个包的行为更像C语言的“exec”函数家族。要展开glob模式,可以直接调用shell,注意避免任何危险的输入,或者使用path/filepath包的glob函数。如果要展开环境变量,请使用package os的ExpandEnv。

所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*)匹配零个或多个任意字符;[abc]匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。

注意,这个包中的示例假设是Unix系统。它们不能在Windows上运行,也不能在golang.org和godoc.org使用的Go Playground上运行。

相关函数定义如下:

Variablesfunc LookPath(file string) (string, error)
type Cmd//方法返回一个*Cmd, 用于执行name指定的程序(携带arg参数)func Command(name string, arg ...string) *Cmd //func CommandContext(ctx context.Context, name string, arg ...string) *Cmd//执行Cmd中包含的命令,并返回标准输出与标准错误合并后的切片func (c *Cmd) CombinedOutput() ([]byte, error)//执行Cmd中包含的命令,并返回标准输出的切片func (c *Cmd) Output() ([]byte, error)//执行Cmd中包含的命令,阻塞直到命令执行完成func (c *Cmd) Run() error//执行Cmd中包含的命令,该方法立即返回,并不等待命令执行完成func (c *Cmd) Start() error//返回一个管道,该管道会在Cmd中的命令被启动后连接到其标准输入func (c *Cmd) StderrPipe() (io.ReadCloser, error)//返回一个管道,该管道会在Cmd中的命令被启动后连接到其标准输出func (c *Cmd) StdinPipe() (io.WriteCloser, error)//返回一个管道,该管道会在Cmd中的命令被启动后连接到其标准错误func (c *Cmd) StdoutPipe() (io.ReadCloser, error)//func (c *Cmd) String() string//该方法会阻塞直到Cmd中的命令执行完成,但该命令必须是被Start方法开始执行的func (c *Cmd) Wait() error
type Errorfunc (e *Error) Error() stringfunc (e *Error) Unwrap() error
type ExitErrorfunc (e *ExitError) Error() string

各个函数详解

func LookPath

函数定义:

func LookPath(file string) (string, error)

在环境变量PATH指定的目录中搜索可执行文件,如过file中有文件分隔符(斜杠),则只在当前目录搜索。即:默认在系统的环境变量里查找给定的可执行命令文件,如果查找到返回路径,否则报错??是PATH$。可提供相对路径下进行查找,并返回相对路径。

示例:

import ("fmt""os/exec"
)func main() {f, err := exec.LookPath("ls")if err != nil {fmt.Println(err)}fmt.Println(f) //输出 /bin/ls
}

struct Cmd

Cmd代表一个正在准备或者在执行中的外部命令。

type Cmd struct {// Path是将要执行的命令的路径。// 该字段不能为空,如为相对路径会相对于Dir字段。Path string// Args保管命令的参数,包括命令名作为第一个参数;如果为空切片或者nil,相当于无参数命令。// 典型用法下,Path和Args都应被Command函数设定。Args []string// Env指定进程的环境,如为nil,则是在当前进程的环境下执行。Env []string// Dir指定命令的工作目录。如为空字符串,会在调用者的进程当前目录下执行。Dir string// Stdin指定进程的标准输入,如为nil,进程会从空设备读取(os.DevNull)Stdin io.Reader// Stdout和Stderr指定进程的标准输出和标准错误输出。// 如果任一个为nil,Run方法会将对应的文件描述符关联到空设备(os.DevNull)// 如果两个字段相同,同一时间最多有一个线程可以写入。Stdout io.WriterStderr io.Writer// ExtraFiles指定额外被新进程继承的已打开文件流,不包括标准输入、标准输出、标准错误输出。// 如果本字段非nil,entry i会变成文件描述符3+i。//// BUG: 在OS X 10.6系统中,子进程可能会继承不期望的文件描述符。// http://golang.org/issue/2603ExtraFiles []*os.File// SysProcAttr保管可选的、各操作系统特定的sys执行属性。// Run方法会将它作为os.ProcAttr的Sys字段传递给os.StartProcess函数。SysProcAttr *syscall.SysProcAttr// Process是底层的,只执行一次的进程。Process *os.Process// ProcessState包含一个已经存在的进程的信息,只有在调用Wait或Run后才可用。ProcessState *os.ProcessState// 下面是隐藏或非导出字段ctx             context.Context // nil means nonelookPathErr     error           // LookPath error, if any.finished        bool            // when Wait was calledchildFiles      []*os.FilecloseAfterStart []io.ClosercloseAfterWait  []io.Closergoroutine       []func() errorerrch           chan error // one send per goroutinewaitDone        chan struct{}
}

注:exec在执行调用系统命令时,会先对需要执行的操作进行一次封装,然后在执行。封装后的命令对象具有以上struct属性。而封装方式即使用下边的Command函数。

func Command

函数定义:

func Command(name string, arg ...string) *Cmd

函数返回一个*Cmd,用于使用给出的参数执行name指定的程序。返回值只设定了Path和Args两个参数。

如果name不含路径分隔符(如果不是相对路径),将使用LookPath获取完整路径(就是用默认的全局变量路径);否则直接使用name。参数arg不应包含命令名。

示例:

func main() {cmd := exec.Command("go", "version")fmt.Println(cmd.Path, cmd.Args)//输出: /usr/local/go/bin/go [go version]
}

注:在调用命令执行封装时,如果不提供相对路径,系统会使用LookPath获取完整路径;即这里可以给一个相对路径。

以上操作只会将命令进行封装,相当于告诉系统将进行哪些操作,但是执行时无法获取相关信息。

func Run

函数定义:

func (c *Cmd) Run() error

Run执行c包含的命令,并阻塞直到完成。

如果命令成功执行,stdin、stdout、stderr的转交没有问题,并且返回状态码为0,方法的返回值为nil(执行Run函数的返回状态,正确执行Run函数,并不代表正确执行了命令);如果函数没有执行或者执行失败,会返回*ExitError类型的错误;否则返回的error可能是表示I/O问题。

即:该命令只会执行且阻塞到执行结束,如果执行函数有错则返回报错信息,没错则返回nil,并不会返回执行结果。

func Start

函数定义:

func (c *Cmd) Start() error

Start开始执行c包含的命令,但并不会等待该命令完成即返回。

func Wait

函数定义:

func (c *Cmd) Wait() error

Wait会阻塞直到该命令执行完成,该命令必须是被Start方法开始执行的。

如果命令成功执行,stdin、stdout、stderr的转交没有问题,并且返回状态码为0,方法的返回值为nil;如果命令没有执行或者执行失败,会返回*ExitError类型的错误;否则返回的error,可能是表示I/O问题。Wait方法会在命令返回后释放相关的资源。

Start和Run的区别

示例:

func main() {cmd := exec.Command("sleep", "5")// 如果用Run, 执行到该步会阻塞等待5s//err := cmd.Run()err := cmd.Start()if err != nil {fmt.Println(err)}fmt.Printf("Waiting for command to finish...")// Start, 上面的内容会先输出,然后这里会阻塞等待5serr = cmd.Wait()fmt.Printf("Command finished with error: %v", err)
}

注:一个命令只能使用Start()或者Run()中的一个启动命令,不能两个同时使用。

func CombinedOutput

函数定义:

func (c *Cmd) CombinedOutput() ([]byte, error)

执行Cmd中包含的命令,并返回标准输出与标准错误合并后的切片。

示例:

func main() {cmd := exec.Command("ls", "-a", "-l")out, err := cmd.CombinedOutput()if err != nil {fmt.Println(err)}fmt.Println(string(out))
}

func Output

函数定义:

func (c *Cmd) Output() ([]byte, error)

执行Cmd中包含的命令,并返回标准输出的切片。

示例:

import ("fmt""os/exec"
)func main() {cmd := exec.Command("ls", "-a", "-l")out, err := cmd.Output()if err != nil {fmt.Println(err)}fmt.Println(string(out))
}

注:Output()和CombinedOutput()不能够同时使用,因为command的标准输出只能有一个,同时使用的话便会定义了两个,便会报错。

我们还可以通过指定一个对象连接到对应的管道进行传输参数(stdinpipe),获取输出(stdoutpipe),获取错误(stderrpipe)

func StdinPipe

函数定义:

func (c *Cmd) StdinPipe() (io.WriteCloser, error) 

err 返回的是执行函数时的错误。StdinPipe方法返回一个在命令Start后与命令标准输入关联的管道。当命令退出时,Wait方法将关闭这个管道。必要时调用者可以调用Close方法来强行关闭管道,例如命令在输入关闭后才会执行返回时需要显式关闭管道。

示例:

func main() {cmd := exec.Command("cat")stdin, err := cmd.StdinPipe()if err != nil {fmt.Println(err)}_, err = stdin.Write([]byte("tmp.txt"))if err != nil {fmt.Println(err)}stdin.Close()cmd.Stdout = os.Stdoutcmd.Start()
}

func StdoutPipe

函数定义:

func (c *Cmd) StdoutPipe() (io.ReadCloser, error)

StdoutPipe方法返回一个在命令Start后与命令标准输出关联的管道。当命令退出时,Wait方法将关闭这个管道。但是在从管道读取完全部数据之前调用Wait是错误的;同样使用StdoutPipe方法时调用Run函数也是错误的。

示例:

func main() {//执行系统命令, 第一个参数是命令名称, 后面参数可以有多个,命令参数cmd := exec.Command("ls", "-a", "-l")//获取输出对象,可以从该对象中读取输出结果stdout, err := cmd.StdoutPipe()if err != nil {fmt.Println(err)}//保证关闭输出流defer stdout.Close()//运行命令if err := cmd.Start(); err != nil {fmt.Println(err)}//读取输出结果content, err := ioutil.ReadAll(stdout)if err != nil {fmt.Println(err)}fmt.Println(string(content))
}

输出重定向到文件:

import ("fmt""os""os/exec"
)func main() {cmd := exec.Command("ls", "-a", "-l")//重定向标准输出到文件stdout, err := os.OpenFile("stdout.log", os.O_CREATE|os.O_WRONLY, 0600)if err != nil {fmt.Println(err)}defer stdout.Close()cmd.Stdout = stdout//执行命令if err := cmd.Start(); err != nil {fmt.Println(err)}
}

func StderrPipe

函数定义:

func (c *Cmd) StderrPipe() (io.ReadCloser, error)

StderrPipe方法返回一个在命令Start后与命令标准错误输出关联的管道。当命令退出时,Wait方法将关闭这个管道,一般不需要显式的关闭该管道。但是在从管道读取完全部数据之前调用Wait是错误的;同样使用StderrPipe方法时调用Run函数也是错误的。

示例:

func main() {cmd := exec.Command("mv", "hello")i, err := cmd.StderrPipe()if err != nil {fmt.Printf("Error:%s\n", err)}b, _ := ioutil.ReadAll(i)if err := cmd.Wait(); err != nil {fmt.Printf("Error: %s\n", err)}fmt.Println(string(b))
}

应用示例

项目中需要执行shell命令,虽然exec包提供了CombinedOutput()方法,在shell运行结束会返回shell执行的输出,但是用户在发起一次任务时,可能在不停的刷新log,想达到同步查看log的目的,但是CombinedOutput()方法只能在命令完全执行结束才返回整个shell的输出,所以肯定达不到效果,所以,需要寻找其它方法达到命令一边执行log一边输出的目的。

使用重定向

如果你的shell比较简单,并且log的文件路径也很容易确定,那么直接对shell执行的命令添加重定向最简单不过了,程序参考如下:

import ("fmt""os/exec"
)func main() {cmdStr := `
#!/bin/bash
for var in {1..10}
dosleep 1echo "Hello, Welcome ${var} times "
done`cmd := exec.Command("bash", "-c", cmdStr+" >> file.log")if err := cmd.Start(); err != nil {fmt.Println(err)}if err := cmd.Wait(); err != nil {fmt.Println(err)}
}

上面程序定义了一个每秒1次的shell,但是在shell执行前,对shell进行了拼接,使用了重定向,所以我们可以在另外一个终端中实时的看到 log 的变化

指定shell执行时的输出

使用exec.Command创建一个Shell后,就具有了两个变量:Stdout io.Writer和Stderr io.Writer。

这两个变量是用来指定程序的标准输出和标准错误输出的位置,所以我们就利用这两个变量,直接打开文件,然后将打开的文件指针赋值给这两个变量即可将程序的输出直接输出到文件中,也能达到相同的效果,参考程序如下:

import ("fmt""os""os/exec"
)func main() {cmdStr := `
#!/bin/bash
for var in {1..10}
dosleep 1echo "Hello, Welcome ${var} times "
done`cmd := exec.Command("bash", "-c", cmdStr)file, _ := os.OpenFile("file.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)defer file.Close()//指定输出位置cmd.Stderr = filecmd.Stdout = fileif err := cmd.Start(); err != nil {fmt.Println(err)}if err := cmd.Wait(); err != nil {fmt.Println(err)}
}

从shell执行结果的管道中获取输出

我们可以通过管道获取命令执行过程中的输出,参考程序如下:

import ("fmt""io""os""os/exec""strings""time"
)//通过管道同步获取日志的函数
func syncLog(reader io.ReadCloser) {file, _ := os.OpenFile("file.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)defer file.Close()buf := make([]byte, 1024, 1024)for {strNum, err := reader.Read(buf)if strNum > 0 {outputByte := buf[:strNum]file.WriteString(string(outputByte))}if err != nil {//读到结尾if err == io.EOF || strings.Contains(err.Error(), "file already closed") {err = nil} }}
}func main() {cmdStr := `
#!/bin/bash
for var in {1..10}
dosleep 1echo "Hello, Welcome ${var} times "
done`cmd := exec.Command("bash", "-c", cmdStr)//这里得到标准输出和标准错误输出的两个管道,此处获取了错误处理cmdStdoutPipe, _ := cmd.StdoutPipe()cmdStderrPipe, _ := cmd.StderrPipe()if err := cmd.Start(); err != nil {fmt.Println(err)}go syncLog(cmdStdoutPipe)go syncLog(cmdStderrPipe)if err := cmd.Wait(); err != nil {fmt.Println(err)}time.Sleep(time.Second * 5)
}

扩展 - 解决log格式乱的问题

上面第三种方式,我们直接是通过打开文件,然后将读取到的程序输出写入文件,但是实际上可能别人又已经封装好了一个logger,让你往logger里面写。比如,我这里就使用log包提供的log然后将程序的执行结果写入,但是因为使用了log包,所以写入的格式和log本身的格式造成格式会有部分的错乱,参考我的解决办法,解释都在注释里,如下:

import ("fmt""io""log""os""os/exec""strings"
)//通过管道同步获取日志的函数
func syncLog(logger *log.Logger, reader io.ReadCloser) {//因为logger的print方法会自动添加一个换行,所以我们需要一个cache暂存不满一行的logcache := ""buf := make([]byte, 1024, 1024)for {strNum, err := reader.Read(buf)if strNum > 0 {outputByte := buf[:strNum]outputSlice := strings.Split(string(outputByte), "\n")logText := strings.Join(outputSlice[:len(outputSlice)-1], "\n")logger.Printf("%s%s", cache, logText)cache = outputSlice[len(outputSlice)-1]}if err != nil {//读到结尾if err == io.EOF || strings.Contains(err.Error(), "file already closed") {err = nil}}}
}func main() {cmdStr := `
#!/bin/bash
for var in {1..10}
dosleep 1echo "Hello, Welcome ${var} times "
done`cmd := exec.Command("bash", "-c", cmdStr)//这里得到标准输出和标准错误输出的两个管道,此处获取了错误处理cmdStdoutPipe, _ := cmd.StdoutPipe()cmdStderrPipe, _ := cmd.StderrPipe()if err := cmd.Start(); err != nil {fmt.Println(err)}//打开一个文件,用作log封装输出file, _ := os.OpenFile("file.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)defer file.Close()//创建封装的log,第三个参数设置log输出的格式logger := log.New(file, "", log.LstdFlags)logger.Print("start print log:")oldFlags := logger.Flags()//为了保证shell的输出和标准的log格式不冲突,并且为了整齐,关闭logger自身的格式logger.SetFlags(0)go syncLog(logger, cmdStdoutPipe)go syncLog(logger, cmdStderrPipe)err := cmd.Wait()//执行完后再打开log输出的格式logger.SetFlags(oldFlags)logger.Print("log print done")if err != nil {fmt.Println(err)}
}

程序执行结果如下:

2019/08/11 21:30:51 start print log:
Hello, Welcome 1 times
Hello, Welcome 2 times
Hello, Welcome 3 times
Hello, Welcome 4 times
Hello, Welcome 5 times
Hello, Welcome 6 times
Hello, Welcome 7 times
Hello, Welcome 8 times
Hello, Welcome 9 times
Hello, Welcome 10 times
2019/08/11 21:31:01 log print done

参考资料及衍生读物

  1. Go基础篇5:内置模块

  2. golang os/exec 执行外部命令

  3. https://gowalker.org/os/exec

  4. [译]使用os/exec执行命令 GO语言

  5. golang os/exec包用法之Kill进程及其子进程

欢迎跳转到本文的原文链接:https://honeypps.com/golang/go-standard-lib-os-exec-guide/

想知道更多?描下面的二维码关注我

精彩推荐:

  • 《迟来的干货|Kafka权限管理实战》

  • 《Go命令行Cobra的使用》

  • 《史上最难的10道Java面试题》

  • 《7102-2019年技术文全套整理,建议收藏》

加技术群入口(备注:技术):>>>Learn More<<

免费资料入口(备注:1024):>>>Learn More<<

免费星球入口:>>>Free<<<

 
点个"在看"呗^_^

Go标准库os/exec使用指南相关推荐

  1. python标准库os的方法_Python中标准库OS的常用方法总结大全

    前言 我们经常会与文件和目录打交道,对于这些操作python提供了一个os模块,里面包含了很多操作文件和目录的函数.在写一些系统脚本或者自动化运维脚本的时候经常会用到这个 os 库,所以这里做个整理, ...

  2. Python 标准库 —— os 路径(os.path)

    <a href="http://blog.csdn.net/lanchunhui/article/details/50193327", target="_blank ...

  3. python标准库os中用来列出_雨课堂答案在哪查,雨课堂2020试题及答案

    常用的电镜有 和 两种,观察表面结构用 ,观察内部结构用 . [简答题]发票号码是否正确?如错误,请修改. [简答题]桥壳的功用有哪些? 在正常窦性心律的心电图上,同-导联连续出现两次或两次以上QRS ...

  4. python标准库os.path中用来_Python标准库os.path中用来判断指定文件是否存在的方法是_____________。...

    Python标准库os.path中用来判断指定文件是否存在的方法是_____________. 答:exists() 通常,我们称脾为( ? ?) 答:后天之本 智慧职教: 生物与非生物的主要区别在于 ...

  5. golang标准库os模块-文件目录相关

    golang标准库os模块-文件目录相关 本文视频教程:https://www.bilibili.com/video/BV1zR4y1t7Wj?from=search&seid=7990946 ...

  6. 开始学习python标准库---os

    学来学去,我才发现,最基础的东西几乎已是无所不能,为啥还去杂乱无章的学这学那?踏踏实实的从标准库学起吧~ 今天是os os.rename:重命名 os.remove:删除文件 os.listdir:用 ...

  7. 爬虫 (三十九) 常用标准库 OS (三十)

    长文预警  阅读约5分钟  相信如果你慢慢把这篇文章读完,然后合并实践 经过上述三节,穿插讲了一下HTTP的知识点,以及浏览器的界面资源的获取,以及运行过程,我们可以收获到很多东西,如果没有好好看的伙 ...

  8. Go 学习笔记(21)— 标准库 os 操作文件(新建、打开、写入、读取、删除、关闭文件)

    Go 操作文本文件时,与其它语言一样也有新建文件.打开文件.写文件.读文件.删除文件等操作.主要有两个标准库来提供这些操作,分别为 os 和 ioutil .在该文中我们介绍 os 模块. 1. 新建 ...

  9. python标准库os的方法listdir_使用python标准库快速修改文件名字

    大家在追剧的时候会一次性下载很多电影,但是很烦人的是前面会有很多电影网站的广告前缀. 今天我将介绍一个简短的代码,快速修改这些文件的名字. 工具:os 首先在电影目录下新建一个py文件,并导入os i ...

最新文章

  1. windows7 端口查看以及杀死进程释放端口
  2. 解决 org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type的问题...
  3. 3D打印「心脏」体外存活超6个月,心肌组织带有规律性搏动,来自中科院清华...
  4. python二十八:模块
  5. java开发五年面试经验_只有经验丰富的开发人员才能教您有关Java的5件事
  6. android console命令,我们可以通过两种方式进入Android的console: 1. 直接执行命令emulator -shell; 2....
  7. mssql导出数据到mysql_MSSQL数据导出到MYSQL
  8. Install and run DB Query Analyzer 6.04 on Microsoft Windows 10
  9. linux编程学习_您需要编程技能才能学习Linux吗?
  10. coursera 视频总是缓冲或者无法观看,有什么方法解决?
  11. Android之ViewPager中包含ViewFlipper时实现双滑动嵌套解决父控件干扰问题
  12. Mac上将Lightroom目录导入Capture One的详细步骤
  13. 软件设计师-数据库知识
  14. 怎么在csdn中找到自己发布的帖子
  15. 基于FPGA的LSTM加速器设计(MNIST数据集为例)
  16. emoji java_java 处理emoji表情信息转换为String
  17. C语言中用单引号括起多个字符的问题
  18. TensorFlow在win10上安装--精简教程
  19. Android 获取点击屏幕压力和坐标
  20. android新闻客户端的实现

热门文章

  1. 上传 录音_老罗推荐的是最好吗?AI旗舰录音笔对比评测
  2. 基于 DataLakeAnalytics 做跨地域的数据分析 1
  3. 回到网易后开源APM技术选型与实战
  4. JS求多个数组的重复数据
  5. 【转】新浪微博手机客户端刷新都是手动刷新或者下拉刷新,为什么不设计成自动刷新?...
  6. RHEL 6.5 rpm包安装mplyer
  7. uva 10759 Dice Throwing
  8. .net组件开发系列之武术系列 武术招数 控件生命周期与控件事件机制
  9. 2021牛客多校10 - Browser Games(哈希)
  10. CodeForces - 1497E2 Square-free division (hard version)(dp+数论)