cmd 脚本编写 go执行脚本命令示例及源码分析

11/28 06:26:35 来源网站:辅助卡盟平台

上面我们对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
}

在上面的方法分析之中我们可以看出运行一个命令的流程是Run-> Start->Wait,等待命令运行完成。并且在Start的时候会起来一个新的进程来执行命令。基于上面我们对Cmd的一顿分析,笔者感觉在文章开头写的测试代码实在是乏善可陈,因为Cmd封装了挺多东西的,我们在工作中完全可以充分利用他封装的功能,比如设置超时时间,设置标准输入流或者标准输出流,还可以定制化设置这个命令执行的环境变量等等。。。。延伸阅读

来源:【九爱网址导航www.fuzhukm.com】 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

    暂无相关资讯
cmd 脚本编写 go执行脚本命令示例及源码分析