我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go执行脚本命令的使用实例分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go执行脚本命令的使用实例分析

这篇文章主要介绍“Go执行脚本命令的使用实例分析”,在日常操作中,相信很多人在Go执行脚本命令的使用实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go执行脚本命令的使用实例分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!                            

简介

在开发中我们可能会遇到需要在程序中调用脚本的需求,或者涉及到两个语言之间的交互,笔者之前就遇到了需要在go中调用python的需求,然后在代码中应用了go-python3这个库,实际上在go中调用python的脚本也是一个解决之法。这片文章将介绍在go中运行shell脚本的方法以及对其源码的相应解析。

程序用例

test_command.go

package learnimport (   "fmt"   "os/exec"   "testing")func TestCmd(t *testing.T) {   if o, e := exec.Command("./test.sh", "1", "2").Output(); e != nil {      fmt.Println(e)   } else {      fmt.Println(string(o))   }}

test.sh

#!/bin/basha=$1b=$2echo $aecho $b

上面这个例子的意思是要运行test.sh这个脚本,并且入参是1,2。脚本里面写的东西相对就比较简单了,就是打印这两个入参。其实问题的关键在于exec.Command()这个方法,下面我们来刨根问底,一探究竟。

源码解析

func Command(name string, arg ...string) *Cmd {   cmd := &Cmd{      Path: name,      Args: append([]string{name}, arg...),   }   if filepath.Base(name) == name {      if lp, err := LookPath(name); err != nil {         cmd.lookPathErr = err      } else {         cmd.Path = lp      }   }   return cmd}// Base返回path的最后一个元素。// 在提取最后一个元素之前,将删除尾部的路径分隔符。// 如果路径为空,Base返回"."。// 如果路径完全由分隔符组成,Base返回单个分隔符。func Base(path string) string {   if path == "" {      return "."   }   // Strip trailing slashes.   for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {      path = path[0 : len(path)-1]   }   // Throw away volume name   path = path[len(VolumeName(path)):]   // Find the last element   i := len(path) - 1   for i >= 0 && !os.IsPathSeparator(path[i]) {      i--   }   if i >= 0 {      path = path[i+1:]   }   // If empty now, it had only slashes.   if path == "" {      return string(Separator)   }   return path}//LookPath在由PATH环境变量命名的目录中搜索一个名为file入参的可执行文件。如果文件包含一个斜线,就会直接尝试,而不参考PATH。其结果可能是一个绝对路径或相对于当前目录的路径。func LookPath(file string) (string, error) {   if strings.Contains(file, "/") {      err := findExecutable(file)      if err == nil {         return file, nil      }      return "", &Error{file, err}   }   path := os.Getenv("PATH")   for _, dir := range filepath.SplitList(path) {      if dir == "" {         // Unix shell semantics: path element "" means "."         dir = "."      }      path := filepath.Join(dir, file)      if err := findExecutable(path); err == nil {         return path, nil      }   }   return "", &Error{file, ErrNotFound}}// 寻找file同名的可执行命令func findExecutable(file string) error {   d, err := os.Stat(file)   if err != nil {      return err   }   if m := d.Mode(); !m.IsDir() && m&0111 != 0 {      return nil   }   return os.ErrPermission}

通过上面对exec.Command()源码的分析我们可以得知,这个函数只是寻找与path名字相同的可执行文件并且构建了一个Cmd的对象返回。这里值得注意的是,当我们输入的path如果不是一个可执行的文件的具体路径,那么就会去PATH环境变量中的注册的路径中找寻与path相同名字的命令,如果这个时候没有找到就会报错。

那么接下来我们那看看这个Cmd是何方神圣呢,有什么用,怎么用呢。下面我们看看Cmd这个结构体里都有些什么东西。

// Cmd结构体代表一个准备或正在执行的外部命令// 一个Cmd的对象不能在Run,Output或者CombinedOutput方法调用之后重复使用。type Cmd struct {   // Path代表运行命令的路径   // 这个字段是唯一一个需要被赋值的字段,不能是空字符串,   // 并且如果Path是相对路径,那么参照的是Dir这个字段的所指向的目录   Path string   // Args这个字段代表调用命令所需的参数,其中Path在运行命令时以Args[0]的形式存在   // 如果这个参数是空,那个就直接使用Path运行命令   //   // 在较为普遍普遍的场景里面,Path和Args这两个参数在调用命令的时候都会被用到   Args []string   // Env代表当前进程的环境变量   // 每个Env数组中的条目都以“key=value”的形式存在   // 如果Env是nil,那边运行命令所创建的进程将使用当前进程的环境变量   // 如果Env中存在重复的key,那么会使用这个key中排在最后一个的值。   // 在Windows中存在特殊的情况, 如果系统中缺失了SYSTEMROOT,或者这个环境变量没有被设置成空字符串,那么它操作都是追加操作。   Env []string   // Dir代表命令的运行路径   // 如果Dir是空字符串,那么命令就会运行在当前进程的运行路径   Dir string   // Stdin代表的是系统的标准输入流   // 如果Stdin是一个*os.File,那么进程的标准输入将被直接连接到该文件。   Stdin io.Reader   // Stdout表示标准输出流   // 如果StdOut是一个*os.File,那么进程的标准输入将被直接连接到该文件。   // 值得注意的是如果StdOut和StdErr是同一个对象,那么同一时间只有一个协程可以调用Writer   Stdout io.Writer   Stderr io.Writer   // ExtraFiles指定由新进程继承的额外开放文件。它不包括标准输入、标准输出或标准错误。如果不为零,第i项成为文件描述符3+i。   // ExtraFiles前面三个元素分别放的是stdin,stdout,stderr   // ExtraFiles在Windows上是不支持的   ExtraFiles []*os.File   SysProcAttr *syscall.SysProcAttr   // 当命令运行之后,Process就是该命令运行所代表的进程   Process *os.Process   // ProcessState包含关于一个退出的进程的信息,在调用Wait或Run后可用。   ProcessState *os.ProcessState   ctx             context.Context // ctx可以用来做超时控制   lookPathErr     error           // 如果在调用LookPath寻找路径的时候出错了,就赋值到这个字段   finished        bool                // 当Wait被调用了一次之后就会被设置成True,防止被重复调用        childFiles      []*os.File   closeAfterStart []io.Closer   closeAfterWait  []io.Closer   goroutine       []func() error  //一系列函数,在调用Satrt开始执行命令的时候会顺带一起执行这些函数。每个函数分配一个goroutine执行   errch           chan error             // 与上一个字段联合使用,通过这个chan将上面函数执行的结果传到当前goroutine   waitDone        chan struct{}}

上面我们对Cmd这个结构体的一些字段做了解析,可以理解为Cmd就是对一个命令生命周期内的抽象。下面我们来分析Cmd的一下方法,看看他是怎么使用的。

// Run方法开始执行这个命令并等待它运行结束// 如果命令运行,在复制stdin、stdout和stder时没有问题,并且以零退出状态退出,则返回的错误为nil。// 如果命令启动但没有成功完成,错误类型为类型为*ExitError。在其他情况下可能会返回其他错误类型。// 如果调用的goroutine已经用runtime.LockOSThread锁定了操作系统线程,并修改了任何可继承的OS级 线程状态(例如,Linux或Plan 9名称空间),新的 进程将继承调用者的线程状态。func (c *Cmd) Run() error {   if err := c.Start(); err != nil {      return err   }   return c.Wait()}// Start方法启动指定的命令,但不等待它完成。//// 如果Start成功返回,c.Process字段将被设置。//// 一旦命令运行完成,Wait方法将返回退出代码并释放相关资源。func (c *Cmd) Start() error {    if c.lookPathErr != nil {        c.closeDescriptors(c.closeAfterStart)        c.closeDescriptors(c.closeAfterWait)        return c.lookPathErr    }    if runtime.GOOS == "windows" {        lp, err := lookExtensions(c.Path, c.Dir)        if err != nil {            c.closeDescriptors(c.closeAfterStart)            c.closeDescriptors(c.closeAfterWait)            return err        }        c.Path = lp    }    if c.Process != nil {        return errors.New("exec: already started")    }    if c.ctx != nil {        select {        case <-c.ctx.Done():            c.closeDescriptors(c.closeAfterStart)            c.closeDescriptors(c.closeAfterWait)            return c.ctx.Err()        default:        }    }  //初始化并填充ExtraFiles    c.childFiles = make([]*os.File, 0, 3+len(c.ExtraFiles))    type F func(*Cmd) (*os.File, error)  //在这里会调用stdin,stdout和stderr方法,如果Cmd的StdIn,StdOut,StdErr不是nil,就会将相关的copy任务封装成func放在goroutine字段中,等待在Start方法执行的时候调用。    for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {        fd, err := setupFd(c)        if err != nil {            c.closeDescriptors(c.closeAfterStart)            c.closeDescriptors(c.closeAfterWait)            return err        }        c.childFiles = append(c.childFiles, fd)    }    c.childFiles = append(c.childFiles, c.ExtraFiles...)  // 如果cmd的Env没有赋值,那么就用当前进程的环境变量    envv, err := c.envv()    if err != nil {        return err    }  // 会用这个命令启动一个新的进程  // 在Linux的系统上,底层是调用了Frok来创建另一个进程,由于文章篇幅有限,就不对此处进行详细分析了,详情可看延伸阅读    c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{        Dir:   c.Dir,        Files: c.childFiles,        Env:   addCriticalEnv(dedupEnv(envv)),        Sys:   c.SysProcAttr,    })    if err != nil {        c.closeDescriptors(c.closeAfterStart)        c.closeDescriptors(c.closeAfterWait)        return err    }    c.closeDescriptors(c.closeAfterStart)    // 除非有goroutine要启动,否则不会申请Chan    if len(c.goroutine) > 0 {        c.errch = make(chan error, len(c.goroutine))        for _, fn := range c.goroutine {            go func(fn func() error) {                c.errch <- fn()            }(fn)        }    }  // 超时控制    if c.ctx != nil {        c.waitDone = make(chan struct{})        go func() {            select {            case <-c.ctx.Done(): //如果超时了,就Kill掉执行命令的进程                c.Process.Kill()            case <-c.waitDone:            }        }()    }    return nil}func (c *Cmd) stdin() (f *os.File, err error) {    if c.Stdin == nil {        f, err = os.Open(os.DevNull)        if err != nil {            return        }        c.closeAfterStart = append(c.closeAfterStart, f)        return    }    if f, ok := c.Stdin.(*os.File); ok {        return f, nil    }  //Pipe返回一对相连的Files;从r读出的数据返回写到w的字节。    pr, pw, err := os.Pipe()    if err != nil {        return    }    c.closeAfterStart = append(c.closeAfterStart, pr)    c.closeAfterWait = append(c.closeAfterWait, pw)  //将相关的任务添加到goroutine中    c.goroutine = append(c.goroutine, func() error {        _, err := io.Copy(pw, c.Stdin)        if skip := skipStdinCopyError; skip != nil && skip(err) {            err = nil        }        if err1 := pw.Close(); err == nil {            err = err1        }        return err    })    return pr, nil}func (c *Cmd) stdout() (f *os.File, err error) {    return c.writerDescriptor(c.Stdout)}func (c *Cmd) stderr() (f *os.File, err error) {    if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {        return c.childFiles[1], nil    }    return c.writerDescriptor(c.Stderr)}func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {    if w == nil {        f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)        if err != nil {            return        }        c.closeAfterStart = append(c.closeAfterStart, f)        return    }    if f, ok := w.(*os.File); ok {        return f, nil    }    pr, pw, err := os.Pipe()    if err != nil {        return    }    c.closeAfterStart = append(c.closeAfterStart, pw)    c.closeAfterWait = append(c.closeAfterWait, pr)  //将相关的任务添加到goroutine中    c.goroutine = append(c.goroutine, func() error {        _, err := io.Copy(w, pr)        pr.Close() // in case io.Copy stopped due to write error        return err    })    return pw, nil}// 等待命令退出,并等待任何复制到stdin或从stdout或stderr复制的完成。// 在调用Wait之前,Start方法必须被调用// 如果命令运行,在复制stdin、stdout和stder时没有问题,并且以零退出状态退出,则返回的错误为nil。// 如果命令运行失败或没有成功完成,错误类型为*ExitError。对于I/O问题可能会返回其他错误类型。// 如果c.Stdin、c.Stdout或c.Stderr中的任何一个不是*os.File,Wait也会等待各自的I/O循环复制到进程中或从进程中复制出来//// Wait释放与Cmd相关的任何资源。func (c *Cmd) Wait() error {    if c.Process == nil {        return errors.New("exec: not started")    }    if c.finished {        return errors.New("exec: Wait was already called")    }    c.finished = true  //等待进程运行完毕并退出    state, err := c.Process.Wait()    if c.waitDone != nil {        close(c.waitDone)    }    c.ProcessState = state  //检查goroutine字段上面的函数运行有没有错误    var copyError error    for range c.goroutine {        if err := <-c.errch; err != nil && copyError == nil {            copyError = err        }    }    c.closeDescriptors(c.closeAfterWait)    if err != nil {        return err    } else if !state.Success() {        return &ExitError{ProcessState: state}    }    return copyError}// 输出运行该命令并返回其标准输出。// 任何返回的错误通常都是*ExitError类型的。// OutPut实际上是封装了命令的执行流程并且制定了命令的输出流func (c *Cmd) Output() ([]byte, error) {    if c.Stdout != nil {        return nil, errors.New("exec: Stdout already set")    }    var stdout bytes.Buffer    c.Stdout = &stdout    captureErr := c.Stderr == nil    if captureErr {        c.Stderr = &prefixSuffixSaver{N: 32 << 10}    }    err := c.Run()    if err != nil && captureErr {        if ee, ok := err.(*ExitError); ok {            ee.Stderr = c.Stderr.(*prefixSuffixSaver).Bytes()        }    }    return stdout.Bytes(), err}

到此,关于“Go执行脚本命令的使用实例分析”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Go执行脚本命令的使用实例分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Go执行脚本命令的使用实例分析

这篇文章主要介绍“Go执行脚本命令的使用实例分析”,在日常操作中,相信很多人在Go执行脚本命令的使用实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go执行脚本命令的使用实例分析”的疑惑有所帮助!接下来
2023-06-22

redis脚本命令执行问题实例分析

这篇文章主要介绍“redis脚本命令执行问题实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“redis脚本命令执行问题实例分析”文章能帮助大家解决问题。1、redis-cli命令行中执行:#
2023-06-29

shell脚本中批量复制及执行命令的示例分析

这篇文章主要介绍了shell脚本中批量复制及执行命令的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。平时在处理一个或几个机器运行环境时,一个机器一个机器处理也能接受,
2023-06-09

linux中shell脚本实现root切换到普通用户执行脚本或命令的示例分析

这篇文章将为大家详细讲解有关linux中shell脚本实现root切换到普通用户执行脚本或命令的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。需求:安装deb包,设置程序安装后启动,不需要root
2023-06-09

shell中使用expect命令进行远程执行命令脚本

expect是用来实现自动交互功能的工具之一,使用expect-send来实现交互过程。 注意: 1、脚本的执行方法与bash shell不一样,比如:expect example.sh 2、向一个
2022-06-04

win下调用putty执行命令脚本分享

@echo offecho ===============================echo Production serverecho ===============================echo please input
2022-06-04

使用shell脚本执行hive、sqoop命令的方法

1、test.sh脚本内容如下:#!/bin/bash#CURR_DATE=`date +"%Y-%m-%d %H:%M:%S"`------>不能使用 v_sql="insert into origin_ennenergy_ene
2022-06-04

ssh远程执行命令方法和Shell脚本实例

写这篇博客之前,我google了一堆相关文章,大都是说修改/etc/sudoers,然后NOPASSWD:指定的cmd,但是真心不管用,没有远程虚拟终端这个方法就是浮云,ubuntu10.04 server 亲测!! ssh执行远程操作 命
2022-06-04

Linux命令行和shell脚本编程的示例分析

小编给大家分享一下Linux命令行和shell脚本编程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!第一个脚本文件代码如下:#!/bin/bashech
2023-06-09

BAT脚本调用maven命令的示例分析

这篇文章将为大家详细讲解有关BAT脚本调用maven命令的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。最近遇到一个问题,因为有一大堆本地库需要安装,之前采用的depedency方式为system
2023-06-08

使用hbase-shell命令怎么批量执行脚本

今天就跟大家聊聊有关使用hbase-shell命令怎么批量执行脚本,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。批量执行hbase shell 命令#!/bin/bash sourc
2023-06-09

使用Spark-shell怎么批量命令执行脚本

这篇文章给大家介绍使用Spark-shell怎么批量命令执行脚本,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。批量执行spark-shell命令,并指定提交参数#!/bin/bashsource /etc/profil
2023-06-09

如何在shell中使用expect命令进行远程执行命令脚本

如何在shell中使用expect命令进行远程执行命令脚本?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。expect是用来实现自动交互功能的工具之一,使用expect-send
2023-06-09

使用hive-shell命令怎么批量执行脚本

使用hive-shell命令怎么批量执行脚本?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。如下所示:#!/usr/bin/bashHADOOP_HOME="/o
2023-06-09

用Python实现命令行闹钟脚本实例

前言: 这篇文章给大家介绍了怎样用python创建一个简单的报警,它可以运行在命令行终端,它需要分钟做为命令行参数,在这个分钟后会打印”wake-up”消息,并响铃报警,你可以用0分钟来测试,它会立即执行,用扬声器控制面板调整声音。 以下是
2022-06-04

CentOS使用expect批量远程执行脚本和命令

我们有时可能会批量去操作服务器,比如批量在服务器上上传某个文件,安装软件,执行某个命令和脚本,重启服务,重启服务器等,如果人工去一台台操作的话会特别繁琐,并浪费人力。 这时我们可以使用expect,向目标服务器上发送指令去实现批量操
2022-06-04

Linux Shell脚本多命令执行逻辑的示例详解

Linux 中可以使用分号";“、双and号”&&“和双竖线”||"来连接多个命令。根据场景的不同适当的使用合适的符号,感兴趣的小伙伴可以了解一下
2022-11-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录