背景
项目中需要执行shell命令,虽然exec包提供了CombinedOutput()
方法,在shell运行结束会返回shell执行的输出,但是用户在发起一次任务时,可能在不停的刷新log,想达到同步查看log的目的,但是CombinedOutput()
方法只能在命令完全执行结束才返回整个shell的输出,所以肯定达不到效果,所以,需要寻找其它方法达到命令一边执行log一边输出的目的。
1. 使用重定向
如果你的shell比较简单,并且log的文件路径也很容易确定,那么直接对shell执行的命令添加重定向最简单不过了,程序参考如下
package main
import (
"fmt"
"os/exec"
)
func main() {
cmdStr := `
#!/bin/bash
for var in {1..10}
do
sleep 1
echo "Hello, Welcome ${var} times "
done`
cmd := exec.Command("bash", "-c",
cmdStr+" >> file.log")
err := cmd.Start()
if err != nil {
fmt.Println(err)
}
err = cmd.Wait()
if err != nil {
fmt.Println(err)
}
}
上面程序定义了一个每秒1次的shell,但是在shell执行前,对shell进行了拼接,使用了重定向,所以我们可以在另外一个 terminal中实时的看到 log 的变化
2. 指定Shell执行时的输出
使用exec.Command创建一个Shell后,就具有了两个变量:
Stdout io.Writer
Stderr io.Writer
这两个变量是用来指定程序的标准输出和标准错误输出的位置,所以我们就利用这两个变量,直接打开文件,然后将打开的文件指针赋值给这两个变量即可将程序的输出直接输出到文件中,也能达到相同的效果,参考程序如下:
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
cmdStr := `
#!/bin/bash
for var in {1..10}
do
sleep 1
echo "Hello, Welcome ${var} times "
done`
cmd := exec.Command("bash", "-c", cmdStr)
f, _ := os.OpenFile("file.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
defer f.Close()
cmd.Stderr = f
cmd.Stdout = f
err := cmd.Start()
if err != nil {
fmt.Println(err)
}
err = cmd.Wait()
if err != nil {
fmt.Println(err)
}
}
3. 从shell执行结果的管道中获取输出
和第二种方法类似,exec.Shell
不仅提供了Stdout
和Stdin
这两个变量,还有下面两个方法
func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
...
}
func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
...
}
上面两个方法会返回命令执行过程中标准输出和标准错误输出的两个管道,我们可以通过这个管道获取命令执行过程中的输出,参考程序如下:
package main
import (
"fmt"
"io"
"os"
"os/exec"
"strings"
)
func syncLog(reader io.ReadCloser) {
f, _ := os.OpenFile("file.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
defer f.Close()
buf := make([]byte, 1024, 1024)
for {
strNum, err := reader.Read(buf)
if strNum > 0 {
outputByte := buf[:strNum]
f.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}
do
sleep 1
echo "Hello, Welcome ${var} times "
done`
cmd := exec.Command("bash", "-c", cmdStr)
cmdStdoutPipe, _ := cmd.StdoutPipe()
cmdStderrPipe, _ := cmd.StderrPipe()
err := cmd.Start()
if err != nil {
fmt.Println(err)
}
go syncLog(cmdStdoutPipe)
go syncLog(cmdStderrPipe)
err = cmd.Wait()
if err != nil {
fmt.Println(err)
}
}
扩展 - 解决log格式乱的问题
上面第三种方式,我们直接是通过打开文件,然后将读取到的程序输出写入文件,但是实际上可能别人又已经封装好了一个logger,让你往logger里面写,比如,我这里就使用log包提供的log然后将程序的执行结果写入,但是因为使用了log包,所以写入的格式和log本身的格式造成格式会有部分的错乱,参考我的解决办法,解释都在注释里,如下:
package main
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"strings"
)
func syncLog(logger *log.Logger, reader io.ReadCloser) {
cache := ""
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}
do
sleep 1
echo "Hello, Welcome ${var} times "
done`
cmd := exec.Command("bash", "-c", cmdStr)
cmdStdoutPipe, _ := cmd.StdoutPipe()
cmdStderrPipe, _ := cmd.StderrPipe()
err := cmd.Start()
if err != nil {
fmt.Println(err)
}
f, _ := os.OpenFile("file.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
defer f.Close()
logger := log.New(f, "", log.LstdFlags)
logger.Print("start print log:")
oldFlags := logger.Flags()
logger.SetFlags(0)
go syncLog(logger, cmdStdoutPipe)
go syncLog(logger, cmdStderrPipe)
err = cmd.Wait()
logger.SetFlags(oldFlags)
logger.Print("log print done")
if err != nil {
fmt.Println(err)
}
}
程序执行结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019050122572539.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3podXhpbnF1YW42MQ==,size_16,color_FFFFFF,t_70)
可以看到,shell的执行过程中的log是没有日志的前缀的,这样也保证了log的整齐:)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)