golang shell

package shell

import (
	"context"
	"fmt"
	"os/exec"
	"time"
)

// 自定义输出结构体
type customOutput struct {
	outPut   chan string
	resetCtx chan struct{}
}

// Write 将输出写入到 customOutput 结构体中,并通知重置超时。
func (c customOutput) Write(p []byte) (int, error) {
	output := string(p)
	c.outPut <- output
	c.resetCtx <- struct{}{} // 每次有输出时通知续期
	return len(p), nil
}

type CustomShellCommand struct {
	cmd      *exec.Cmd
	outPipe  chan string
	resetCtx chan struct{}
	ctx      context.Context
	cancel   context.CancelFunc
	timer    *time.Timer
	timeout  time.Duration
}

// NewShell 创建新的 Shell 命令实例
func NewShell(command []string, timeout time.Duration) *CustomShellCommand {
	ctx, cancel := context.WithCancel(context.Background())
	outPipe := make(chan string, 100)
	resetCtx := make(chan struct{}, 1) // 用于通知续期

	cmd := exec.CommandContext(ctx, command[0], command[1:]...)
	cmd.Stdout = customOutput{outPipe, resetCtx}

	// 创建并返回 CustomShellCommand
	return &CustomShellCommand{
		cmd:      cmd,
		outPipe:  outPipe,
		resetCtx: resetCtx,
		ctx:      ctx,
		timeout:  timeout,
		cancel:   cancel,
		timer:    time.NewTimer(timeout), // 设置初始超时
	}
}

// Run 执行命令并处理续期超时
func (c *CustomShellCommand) Run() error {
	errChan := make(chan error, 1) // 创建一个缓冲区大小为1的错误通道

	go func() {
		err := c.cmd.Run() // 在 goroutine 中执行命令
		//if err != nil {
		//	fmt.Println("Command exited with error:", err)
		//}
		errChan <- err // 将错误发送到错误通道
		c.cancel()     // 当命令执行完成时取消上下文
	}()

	for {
		select {
		case <-c.ctx.Done(): // 当命令被取消或完成时退出
			//fmt.Println("Command context done.")
			return c.ctx.Err()
		case <-c.timer.C: // 当超时没有输出时,停止命令
			//fmt.Println("Command timed out.")
			c.cancel()
			return fmt.Errorf("command timed out")
		case <-c.resetCtx: // 当有输出时重置超时计时器
			if !c.timer.Stop() {
				<-c.timer.C // 确保通道被清空
			}
			c.timer.Reset(c.timeout) // 重置计时器为
		case err := <-errChan: // 捕获命令执行的错误
			return err // 返回错误
		}
	}
}

// Killed 取消任务
func (c *CustomShellCommand) Killed() {
	c.cancel()
}

// GetOutputPipe 返回命令的输出通道
func (c *CustomShellCommand) GetOutputPipe() <-chan string {
	return c.outPipe
}

// Close 关闭输出通道并取消上下文
func (c *CustomShellCommand) Close() {
	close(c.outPipe)
	c.cancel()
}

  

posted @ 2024-09-28 16:30  老鲜肉  阅读(3)  评论(0编辑  收藏  举报