使用 os/exec 执行命令 | 青训营笔记

简介: 使用 os/exec 执行命令 | 青训营笔记

前言

记录加入青训营的每一天笔记。

用Go去调用一些外部的命令其实很愉快的,这遍文章就总结一下我自己日常用的比较多的几种方法。

关于Unix标准输入输出

在具体聊os/exec的使用前,了解一下shell的标准输出是很有必要的。

我们平常会用到或看到这样的命令:

shell

复制代码

$ ls xxx 1>out.txt 2>&1
$ nohup xxx 2>&1 &

你知道这里1,2含义么?

其实这里的1,2指的就是Unix文件描述符。文件描述符其实就一数字,每一个文件描述符代表的都是一个文件。如果你打开100个文件,你就会获取到100个文件描述符。

这里需要注意的一点就是,在Unix中一切皆文件。当然,这里我们不必去深究,我们需要知道的是1,2代表的是标准输出stdout与标准错误输出stderr。还有0代表标准输入stdin

os/exec中就用到了Stdin,Stdout,Stderr,这些基本Unix知识或能帮助我们更好理解这些参数。

os/exec

os/exec包内容并不多,我们大概过一下。

  1. LookPath(file string) (string, error)
    寻找可执行文件路径,如果你指定的可执行文件在$PATH中,就会返回这个可执行文件的相对/绝对路径;如果你指定的是一个文件路径,他就是去判断文件是否可读取/执行,返回的是一样的路径。
    在我们需要使用一些外部命令/可执行文件的时候,我们可以先使用该函数判断一下该命令/可执行文件是否有效。
  2. Command(name string, arg …string) *Cmd
    使用你输入的参数,返回Cmd指针,可用于执行Cmd的方法。
    这里name就是我们的命令/可执行文件,后面的参数可以一个一个输入。
  3. CommandContext(ctx context.Context, name string, arg …string) *Cmd
    和上面功能一样,不过我们可以用上下文做一些超时等控制。
  4. 之后几个就是Cmd的一些方法。

其实读完,结合官方的一些example,使用很简单,下面具体写几个场景。

  1. ./testcmd/testcmd是我用Go写的一个简单的可执行文件,可以根据指定的参数 输出/延时输出/输出错误,方便我们演示。如下
func main() {
    var (
        start bool
        e     bool
    )
    flag.BoolVar(&start, "s", false, "start output")
    flag.BoolVar(&e, "e", false, "output err")
    flag.Parse()
    if start {
        for i := 5; i > 0; i-- {
            fmt.Fprintln(os.Stdout, "test cmd output", i)
            time.Sleep(1 * time.Second)
        }
    }
    if e {
        fmt.Fprintln(os.Stderr, "a err occur")
        os.Exit(1)
    }
    fmt.Fprintln(os.Stdout, "test cmd stdout")
}

简单执行

// 简单执行
func test1() {
    cmd := exec.Command("./testcmd/testcmd", "-s")
    // 使用CombinedOutput 将stdout stderr合并输出
    out, err := cmd.CombinedOutput()
    if err != nil {
        log.Printf("test1 failed %s\n", err)
    }
    log.Println("test1 output ", string(out))
}

输出:

Run Test 1
2019/06/06 18:02:39 test1 output  test cmd output 5
test cmd output 4
test cmd output 3
test cmd output 2
test cmd output 1
done

整个过程等待5秒,所有结果一次输出。

分离标准输出与错误输出

将错误分开输出,同时开了两个协成,同步的接收命令的输出内容。

//  stdout & stderr 分开输出
func test2() {
    cmd := exec.Command("./testcmd/testcmd", "-s", "-e")
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    cmd.Start()
    go func() {
        for {
            buf := make([]byte, 1024)
            n, err := stderr.Read(buf)
            if n > 0 {
                log.Printf("read err %s", string(buf[:n]))
            }
            if n == 0 {
                break
            }
            if err != nil {
                log.Printf("read err %v", err)
                return
            }
        }
    }()
    go func() {
        for {
            buf := make([]byte, 1024)
            n, err := stdout.Read(buf)
            if n == 0 {
                break
            }
            if n > 0 {
                log.Printf("read out %s", string(buf[:n]))
            }
            if n == 0 {
                break
            }
            if err != nil {
                log.Printf("read out %v", err)
                return
            }
        }
    }()
    err := cmd.Wait()
    if err != nil {
        log.Printf("cmd wait %v", err)
        return
    }
}

输出:

Run Test 2
2019/06/06 18:02:39 read out test cmd output 5
2019/06/06 18:02:40 read out test cmd output 4
2019/06/06 18:02:41 read out test cmd output 3
2019/06/06 18:02:42 read out test cmd output 2
2019/06/06 18:02:43 read out test cmd output 1
2019/06/06 18:02:44 read err a err occur
2019/06/06 18:02:44 cmd wait exit status 1

按行读取输出内容

使用bufio按行读取输出内容。

// 按行读输出的内容
func test3() {
    cmd := exec.Command("./testcmd/testcmd", "-s", "-e")
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    oReader := bufio.NewReader(stdout)
    eReader := bufio.NewReader(stderr)
    cmd.Start()
    go func() {
        for {
            line, err := oReader.ReadString('\n')
            if line != "" {
                log.Printf("read line %s", line)
            }
            if err != nil || line == "" {
                log.Printf("read line err %v", err)
                return
            }
        }
    }()
    go func() {
        for {
            line, err := eReader.ReadString('\n')
            if line != "" {
                log.Printf("read err %s", line)
            }
            if err != nil || line == "" {
                log.Printf("read err %v", err)
                return
            }
        }
    }()
    err := cmd.Wait()
    if err != nil {
        log.Printf("cmd wait %v", err)
        return
    }
}

输出:

Run Test 3
2019/06/06 18:06:44 read line test cmd output 5
2019/06/06 18:06:45 read line test cmd output 4
2019/06/06 18:06:46 read line test cmd output 3
2019/06/06 18:06:47 read line test cmd output 2
2019/06/06 18:06:48 read line test cmd output 1
2019/06/06 18:06:49 read err a err occur
2019/06/06 18:06:49 cmd wait exit status 1

设置执行超时时间

有时候我们要控制命令的执行时间,这是就可以使用上下文去控制了。

// 通过上下文控制超时
func test4() {
    ctx, calcel := context.WithTimeout(context.Background(), 2*time.Second)
    defer calcel()
    cmd := exec.CommandContext(ctx, "./testcmd/testcmd", "-s", "-e")
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    oReader := bufio.NewReader(stdout)
    eReader := bufio.NewReader(stderr)
    cmd.Start()
    go func() {
        for {
            line, err := oReader.ReadString('\n')
            if line != "" {
                log.Printf("read line %s", line)
            }
            if err != nil || line == "" {
                log.Printf("read line err %v", err)
                return
            }
        }
    }()
    go func() {
        for {
            line, err := eReader.ReadString('\n')
            if line != "" {
                log.Printf("read err %s", line)
            }
            if err != nil || line == "" {
                log.Printf("read err %v", err)
                return
            }
        }
    }()
    err := cmd.Wait()
    if err != nil {
        log.Printf("cmd wait %v", err)
        return
    }
}

输出:

Run Test 4
2019/06/06 18:06:49 read line err EOF
2019/06/06 18:06:49 read err EOF
2019/06/06 18:06:49 read line test cmd output 5
2019/06/06 18:06:50 read line test cmd output 4
2019/06/06 18:06:51 read line err EOF
2019/06/06 18:06:51 read err EOF
2019/06/06 18:06:51 cmd wait signal: killed

持续输入指令,交互模式

有很多命令支持交互模式,我们进入之后就可以持续的输入一些命令,同时获取输出。如openssl命令。

下面我们需要进入交换模式,执行输入三个命令,并获取输出。

// 持续输入
func test5() {
    cmd := exec.Command("openssl")
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    stdin, _ := cmd.StdinPipe()
    cmd.Start()
    // 读
    var wg sync.WaitGroup
    wg.Add(3)
    go func() {
        defer wg.Done()
        for {
            buf := make([]byte, 1024)
            n, err := stderr.Read(buf)
            if n > 0 {
                fmt.Println(string(buf[:n]))
            }
            if n == 0 {
                break
            }
            if err != nil {
                log.Printf("read err %v", err)
                return
            }
        }
    }()
    go func() {
        defer wg.Done()
        for {
            buf := make([]byte, 1024)
            n, err := stdout.Read(buf)
            if n == 0 {
                break
            }
            if n > 0 {
                fmt.Println(string(buf[:n]))
            }
            if n == 0 {
                break
            }
            if err != nil {
                log.Printf("read out %v", err)
                return
            }
        }
    }()
    // 写
    go func() {
        stdin.Write([]byte("version\n\n"))
        stdin.Write([]byte("ciphers -v\n\n"))
        stdin.Write([]byte("s_client -connect razeencheng.com:443"))
        stdin.Close()
        wg.Done()
    }()
    wg.Wait()
    err := cmd.Wait()
    if err != nil {
        log.Printf("cmd wait %v", err)
        return
    }
}

这里,我们就用到了stdin标准输入了。输出如下:

Run Test 5
OpenSSL> LibreSSL 2.6.5
OpenSSL> OpenSSL>
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA384
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA384
ECDHE-RSA-AES256-SHA    SSLv3 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA1
ECDHE-ECDSA-AES256-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA1
DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH       Au=RSA  Enc=AESGCM(256) Mac=AEAD
DHE-RSA-AES256-SHA256   TLSv1.2 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA256
DES-CBC-SHA             SSLv3 Kx=RSA      Au=RSA  Enc=DES(56)   Mac=SHA1
...
OpenSSL> OpenSSL>
4466583148:error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/ssl/ssl_pkt.c:1205:SSL alert number 40
4466583148:error:140040E5:SSL routines:CONNECT_CR_SRVR_HELLO:ssl handshake failure:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/ssl/ssl_pkt.c:585:
CONNECTED(00000005)
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 0 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    Start Time: 1559815613
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
目录
相关文章
|
Python
python os.system 相对路径 命令执行
python os.system 相对路径 命令执行
94 0
python os.system 相对路径 命令执行
|
Linux
Linux查看操作系统版命令
Linux查看操作系统版命令
|
1月前
|
Linux 编译器 C语言
./build.sh:行1: g++: 未找到命令的错误问题在centos操作系统下面如何解决
通过上述步骤,您应该能够有效地解决CentOS系统中 `g++: 未找到命令`的错误。确保软件开发环境配置得当,对于顺利执行编译脚本和日常开发工作至关重要。如果问题依然存在,考虑检查脚本内的命令路径引用是否正确,或进一步排查系统配置问题。
103 0
|
1月前
|
存储 Java iOS开发
MacOS环境-手写操作系统-43-dir命令的实现 和 文件写入
MacOS环境-手写操作系统-43-dir命令的实现 和 文件写入
30 0
|
4月前
|
Shell Linux C语言
Linux初学者必备:全面掌握核心命令
Linux命令是操作系统的核心组成部分,用于管理和操作文件系统、网络、硬件资源以及执行各种任务。下面列出了一些常用的Linux命令及其基本用途,
Linux初学者必备:全面掌握核心命令
|
4月前
|
存储 算法 Unix
软考中级之数据库系统工程师笔记总结(三)操作系统
软考中级之数据库系统工程师笔记总结(三)操作系统
41 0
|
4月前
|
Linux
Linux02---命令基础 Linux命令基础, ls命令入门,ls命令参数和选项,命令行是一种以纯字符操作系统的方式,command命令本身,options命令的细节行为,parameter命令的
Linux02---命令基础 Linux命令基础, ls命令入门,ls命令参数和选项,命令行是一种以纯字符操作系统的方式,command命令本身,options命令的细节行为,parameter命令的
|
5月前
|
运维 安全 Unix
Linux操作系统 , 常用命令
Linux操作系统 , 常用命令
|
5月前
|
调度
操作系统的目标和功能笔记分享
【6月更文挑战第1天】操作系统的目标和功能笔记分享
67 1
|
6月前
|
调度
操作系统的目标和功能笔记分享
【5月更文挑战第3天】操作系统的目标和功能笔记分享
54 2