2017-03-12 22:18:59 +02:00
|
|
|
package execext
|
|
|
|
|
|
|
|
import (
|
2017-04-13 01:32:56 +02:00
|
|
|
"context"
|
2017-04-22 20:46:29 +02:00
|
|
|
"errors"
|
|
|
|
"io"
|
2018-04-28 20:36:01 +02:00
|
|
|
"os"
|
2019-02-22 02:20:20 +02:00
|
|
|
"path/filepath"
|
2017-04-22 20:46:29 +02:00
|
|
|
"strings"
|
|
|
|
|
2019-09-09 02:55:02 +02:00
|
|
|
"mvdan.cc/sh/v3/expand"
|
|
|
|
"mvdan.cc/sh/v3/interp"
|
|
|
|
"mvdan.cc/sh/v3/shell"
|
|
|
|
"mvdan.cc/sh/v3/syntax"
|
2017-03-12 22:18:59 +02:00
|
|
|
)
|
|
|
|
|
2017-04-24 15:25:38 +02:00
|
|
|
// RunCommandOptions is the options for the RunCommand func
|
2017-04-22 20:46:29 +02:00
|
|
|
type RunCommandOptions struct {
|
|
|
|
Command string
|
|
|
|
Dir string
|
|
|
|
Env []string
|
|
|
|
Stdin io.Reader
|
|
|
|
Stdout io.Writer
|
|
|
|
Stderr io.Writer
|
|
|
|
}
|
|
|
|
|
2017-03-12 22:18:59 +02:00
|
|
|
var (
|
2017-04-22 20:46:29 +02:00
|
|
|
// ErrNilOptions is returned when a nil options is given
|
|
|
|
ErrNilOptions = errors.New("execext: nil options given")
|
2020-12-27 22:15:12 +02:00
|
|
|
|
|
|
|
setMinusE *syntax.File
|
2017-03-12 22:18:59 +02:00
|
|
|
)
|
|
|
|
|
2020-12-27 22:15:12 +02:00
|
|
|
func init() {
|
|
|
|
var err error
|
|
|
|
setMinusE, err = syntax.NewParser().Parse(strings.NewReader("set -e"), "")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-22 20:46:29 +02:00
|
|
|
// RunCommand runs a shell command
|
2018-09-01 16:02:23 +02:00
|
|
|
func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
2017-04-22 20:46:29 +02:00
|
|
|
if opts == nil {
|
|
|
|
return ErrNilOptions
|
|
|
|
}
|
|
|
|
|
2017-09-07 15:40:21 +02:00
|
|
|
p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "")
|
2017-04-22 20:46:29 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-03-12 22:18:59 +02:00
|
|
|
|
2018-04-28 20:36:01 +02:00
|
|
|
environ := opts.Env
|
|
|
|
if len(environ) == 0 {
|
|
|
|
environ = os.Environ()
|
|
|
|
}
|
|
|
|
|
2018-09-01 16:02:23 +02:00
|
|
|
r, err := interp.New(
|
|
|
|
interp.Dir(opts.Dir),
|
2018-12-15 19:43:40 +02:00
|
|
|
interp.Env(expand.ListEnviron(environ...)),
|
2020-12-27 21:49:51 +02:00
|
|
|
interp.OpenHandler(openHandler),
|
2018-09-01 16:02:23 +02:00
|
|
|
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
|
|
|
)
|
|
|
|
if err != nil {
|
2017-08-05 19:20:44 +02:00
|
|
|
return err
|
|
|
|
}
|
2020-12-27 22:15:12 +02:00
|
|
|
if err = r.Run(ctx, setMinusE); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-09-01 16:02:23 +02:00
|
|
|
return r.Run(ctx, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsExitError returns true the given error is an exis status error
|
|
|
|
func IsExitError(err error) bool {
|
2019-11-25 00:17:09 +02:00
|
|
|
if _, ok := interp.IsExitStatus(err); ok {
|
2018-09-01 16:02:23 +02:00
|
|
|
return true
|
|
|
|
}
|
2019-11-25 00:17:09 +02:00
|
|
|
return false
|
2017-03-12 22:18:59 +02:00
|
|
|
}
|
2018-12-24 19:19:53 +02:00
|
|
|
|
|
|
|
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
|
|
|
// if available.
|
|
|
|
func Expand(s string) (string, error) {
|
2019-02-22 02:20:20 +02:00
|
|
|
s = filepath.ToSlash(s)
|
2019-02-22 01:52:27 +02:00
|
|
|
s = strings.Replace(s, " ", `\ `, -1)
|
2018-12-24 19:19:53 +02:00
|
|
|
fields, err := shell.Fields(s, nil)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(fields) > 0 {
|
|
|
|
return fields[0], nil
|
|
|
|
}
|
|
|
|
return "", nil
|
|
|
|
}
|
2020-12-27 21:49:51 +02:00
|
|
|
|
|
|
|
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
|
|
|
if path == "/dev/null" {
|
|
|
|
return devNull{}, nil
|
|
|
|
}
|
|
|
|
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
|
|
|
|
}
|