Five methods of executing commands with OS / exec in go language

Time:2021-2-21

For a complete series of tutorials, see:http://golang.iswbm.com

The library used to execute commands in golang isos/exec, exec.Command Function returns aCmdAccording to different requirements, the execution of commands can be divided into three situations

  • Only execute the command, not get the result
  • Execute the command and get the result (regardless of stdout and stderr)
  • Execute the command and get the result (distinguish stdout and stderr)

The first type: only execute the command, do not get the result

Directly call the run function of the CMD object, only success and failure are returned, and no output result can be obtained.


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)
	}
}

Second, execute the command and get the result

Sometimes we execute a command just to get the output result. At this time, you can call the combinedoutput function of CMD.


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))
}

The combinedoutput function only returns out and does not distinguish stdout from stderr. If you want to distinguish them, you can look directly at the third method.


$ 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

But before that, I found a small problem: sometimes shell commands can be executed, not code exec.

For example, I just want to check/var/log/What about the log suffix file in the directory? A little Linux based students, will use this command


$ 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

Put it in this wayexec.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))
}

What happened? No, it’s wrong.


$ 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

Why is it wrong? I’m sure there’s no problem

Actually, it’s very simplels -l /var/log/*.log It’s not equivalent to the following code.


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

The shell command corresponding to the above code should be as follows. If you write like this, LS will treat the content in the parameter as a specific file name and ignore the wildcard*


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

Third, execute commands and distinguish stdout and stderr

The above writing method can’t distinguish standard output from standard error. Just change it to the following writing method.

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 // standard output
	cmd.Stderr  =& stderr // standard error
	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)
	}
}

The output is as follows, you can see that the previous error content is classified into standard error


$ 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

Fourth: multiple command combination, please use pipeline

The output of the previous command is used as the parameter of the next command. You can use pipe characters in a shell|To achieve.

For example, the following command counts the number of error logs in the message log.


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

Similarly, there is a similar implementation in 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()
}

The output is as follows


$ go run demo.go 
19

Fifth: setting command level environment variables

The setenv function of OS library is used to set environment variables, which affect the whole process life cycle.


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)
}

As long as in this process,NAMEThe value of this variable will be zerowangbmNo matter how many times you execute an order


$ go run demo.go 
wangbm

If you want to reduce the scope of environment variables to the command level, there is a way.

In order to facilitate verification, I create a new sh script as follows

$ cat /home/wangbm/demo.sh
echo $NAME
$ bash /home/wangbm/ demo.sh  #Since there is no name in the global environment variable, there is no output

In addition, demo.go The code in is as follows

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) // add environment variable to CMD1 command: 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)
}

After execution, you can see that the command executed for the second time does not output the variable value of name.


$ go run demo.go 
output: wangbm
output: 

So far, this article about the five ways to execute commands with OS / exec in go language is introduced here. For more information about how to execute commands in go language, please search previous articles of developer or continue to browse the following related articles. I hope you can support developer more in the future!

Recommended Today

Use of Android WebView (super detailed usage)

1.1 overview of WebView Android WebView is a special view on the Android platform. It can be used to display web pages. This WebView class can be used to display only one online web page in the app. Of course, it can also be used to develop browsers. The internal implementation of WebView uses WebKit […]