github.com/kaydxh/golang@v0.0.131/go/os/exec/exec.go (about)

     1  /*
     2   *Copyright (c) 2022, kaydxh
     3   *
     4   *Permission is hereby granted, free of charge, to any person obtaining a copy
     5   *of this software and associated documentation files (the "Software"), to deal
     6   *in the Software without restriction, including without limitation the rights
     7   *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   *copies of the Software, and to permit persons to whom the Software is
     9   *furnished to do so, subject to the following conditions:
    10   *
    11   *The above copyright notice and this permission notice shall be included in all
    12   *copies or substantial portions of the Software.
    13   *
    14   *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20   *SOFTWARE.
    21   */
    22  package exec
    23  
    24  import (
    25  	"bytes"
    26  	"context"
    27  	"fmt"
    28  	"os/exec"
    29  	"syscall"
    30  	"time"
    31  
    32  	context_ "github.com/kaydxh/golang/go/context"
    33  )
    34  
    35  type CommandBuilder struct {
    36  	cmd exec.Cmd
    37  
    38  	opts struct {
    39  		Timeout time.Duration
    40  	}
    41  }
    42  
    43  func NewCommandBuilder(
    44  	options ...CommandBuilderOption,
    45  ) (*CommandBuilder, error) {
    46  	c := &CommandBuilder{}
    47  	c.ApplyOptions(options...)
    48  
    49  	return c, nil
    50  }
    51  
    52  // Exec return output result, err messgae, error
    53  func (c *CommandBuilder) Exec(cmdName string,
    54  	args ...string,
    55  ) (string, string, error) {
    56  	return Exec(c.opts.Timeout, cmdName, args...)
    57  }
    58  
    59  //timout ms
    60  func Exec(
    61  	timeout time.Duration,
    62  	cmdName string,
    63  	args ...string,
    64  ) (string, string, error) {
    65  	ctx, cancel := context_.WithTimeout(
    66  		context.Background(),
    67  		timeout,
    68  	)
    69  	defer cancel()
    70  
    71  	var stdout, stderr bytes.Buffer
    72  	args = append([]string{"-c", cmdName}, args...)
    73  	cmd := exec.CommandContext(ctx, "/bin/sh", args...)
    74  	cmd.Stdout, cmd.Stderr = &stdout, &stderr
    75  	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    76  	if err := cmd.Start(); err != nil {
    77  		return "", "", fmt.Errorf(
    78  			"error starting %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v",
    79  			cmd,
    80  			cmd.Stdout,
    81  			cmd.Stderr,
    82  			err,
    83  		)
    84  	}
    85  
    86  	errCh := make(chan error, 1)
    87  	go func() {
    88  		errCh <- cmd.Wait()
    89  	}()
    90  	select {
    91  	case err := <-errCh:
    92  		if err != nil {
    93  			var rc = 127
    94  			if ee, ok := err.(*exec.ExitError); ok {
    95  				rc = int(ee.Sys().(syscall.WaitStatus).ExitStatus())
    96  			}
    97  			return stdout.String(), stderr.String(),
    98  				fmt.Errorf(
    99  					"error running %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v\ncode:\n%v",
   100  					cmd,
   101  					cmd.Stdout,
   102  					cmd.Stderr,
   103  					err,
   104  					rc,
   105  				)
   106  		}
   107  	case <-ctx.Done():
   108  		cmd.Process.Kill()
   109  		return "", "", fmt.Errorf(
   110  			"timed out waiting for command %v:\nCommand stdout:\n%v\nstderr:\n%v",
   111  			cmd,
   112  			cmd.Stdout,
   113  			cmd.Stderr,
   114  		)
   115  	}
   116  
   117  	return stdout.String(), stderr.String(), nil
   118  }