summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Carr <[email protected]>2024-11-08 06:43:04 -0600
committerJeff Carr <[email protected]>2024-11-08 06:43:04 -0600
commitd887da32f20feee8bfbebf7b2db63f1a42fd7176 (patch)
tree8a13e9890c6c9c6e149bdf4cb5ebaf458fcaa167
parentae5cadf5455d5f65fdab0b690b8d403090750fc3 (diff)
use go-cmd/cmd and purge old codev0.22.5
-rw-r--r--chomp.go98
-rw-r--r--cmd.go192
-rw-r--r--int.go31
-rw-r--r--old.go (renamed from shell.go)88
-rw-r--r--run.go253
-rw-r--r--wget.go3
-rw-r--r--xterm.go4
7 files changed, 212 insertions, 457 deletions
diff --git a/chomp.go b/chomp.go
deleted file mode 100644
index 19acaf7..0000000
--- a/chomp.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package shell
-
-/*
- perl 'chomp'
-
- send it anything, always get back a string
-*/
-
-import (
- "bytes"
- "fmt"
- "reflect"
- "strings"
-
- "go.wit.com/log"
-)
-
-// import "github.com/davecgh/go-spew/spew"
-
-func chompBytesBuffer(buf *bytes.Buffer) string {
- var bytesSplice []byte
- bytesSplice = buf.Bytes()
-
- return Chomp(string(bytesSplice))
-}
-
-// TODO: obviously this is stupidly wrong
-// TODO: fix this to trim fucking everything
-// really world? 8 fucking years of this language
-// and I'm fucking writing this? jesus. how the
-// hell is everyone else doing this? Why isn't
-// this already in the strings package?
-func perlChomp(s string) string {
- // lots of stuff in go moves around the whole block of whatever it is so lots of things are padded with NULL values
- s = strings.Trim(s, "\x00") // removes NULL (needed!)
-
- // TODO: christ. make some fucking regex that takes out every NULL, \t, ' ", \n, and \r
- s = strings.Trim(s, "\n")
- s = strings.Trim(s, "\n")
- s = strings.TrimSuffix(s, "\r")
- s = strings.TrimSuffix(s, "\n")
-
- s = strings.TrimSpace(s) // this is like 'chomp' in perl
- s = strings.TrimSuffix(s, "\n") // this is like 'chomp' in perl
- return s
-}
-
-// TODO: fix this to chomp \n \r NULL \t and ' '
-func Chomp(a interface{}) string {
- // switch reflect.TypeOf(a) {
- switch t := a.(type) {
- case string:
- var s string
- s = a.(string)
- return perlChomp(s)
- case []uint8:
- // log.Printf("shell.Chomp() FOUND []uint8")
- var tmp []uint8
- tmp = a.([]uint8)
-
- s := string(tmp)
- return perlChomp(s)
- case uint64:
- // log.Printf("shell.Chomp() FOUND []uint64")
- s := fmt.Sprintf("%d", a.(uint64))
- return perlChomp(s)
- case int64:
- // log.Printf("shell.Chomp() FOUND []int64")
- s := fmt.Sprintf("%d", a.(int64))
- return perlChomp(s)
- case *bytes.Buffer:
- // log.Printf("shell.Chomp() FOUND *bytes.Buffer")
- var tmp *bytes.Buffer
- tmp = a.(*bytes.Buffer)
- if tmp == nil {
- return ""
- }
-
- var bytesSplice []byte
- bytesSplice = tmp.Bytes()
- return Chomp(string(bytesSplice))
- default:
- tmp := fmt.Sprint("shell.Chomp() NO HANDLER FOR TYPE: %T", a)
- handleError(fmt.Errorf(tmp), -1)
- log.Warn("shell.Chomp() NEED TO MAKE CONVERTER FOR type =", reflect.TypeOf(t))
- }
- tmp := "shell.Chomp() THIS SHOULD NEVER HAPPEN"
- handleError(fmt.Errorf(tmp), -1)
- return ""
-}
-
-// this is stuff from a long time ago that there must be a replacement for
-func RemoveFirstElement(slice []string) (string, []string) {
- if len(slice) == 0 {
- return "", slice // Return the original slice if it's empty
- }
- return slice[0], slice[1:] // Return the slice without the first element
-}
diff --git a/cmd.go b/cmd.go
new file mode 100644
index 0000000..cfaafd1
--- /dev/null
+++ b/cmd.go
@@ -0,0 +1,192 @@
+package shell
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/go-cmd/cmd"
+ "go.wit.com/log"
+)
+
+// this is a simplified interaction with the excellent
+// go-cmd/cmd package to work 'shell' like.
+
+// in all cases here, STDERR -> STDOUT
+// If you want the output from whatever you run
+// to be captured like it appears when you see it
+// on the command line, this is what this tries to do
+
+/*
+ if r := shell.Run([]{"ping", "-c", "3", "localhost"}); r.Error == nil {
+ if r.Exit == 0 {
+ log.Println("ran ok")
+ } else {
+ log.Println("ran")
+ }
+ // all stdout/stderr captured in r.Stdout
+ }
+*/
+
+// shortcut, sends a blank value for pwd
+// which means the exec Dir is not set
+// echos output (otherwise use RunQuiet)
+func Run(args []string) cmd.Status {
+ return PathRun("", args)
+}
+
+// exec the cmd at a filepath. this does not change the working directory
+// sets the exec dir if it's not an empty string
+// combines stdout and stderr
+// echo's output (otherwise use PathRunQuiet()
+// this is basically the exact example from the go-cmd/cmd devs
+// where the have rocked out a proper smart read on both filehandles
+// https://dave.cheney.net/2013/04/30/curious-channels
+func PathRun(path string, args []string) cmd.Status {
+ var save []string // combined stdout & stderr
+ var arg0 string
+ var argx []string
+ // Check if the slice has at least one element (the command name)
+ if len(args) == 0 {
+ var s cmd.Status
+ s.Error = errors.New("Error: Command slice is empty.")
+ return s
+ }
+ if len(args) == 1 {
+ // Pass the first element as the command, and the rest as variadic arguments
+ arg0 = args[0]
+ } else {
+ arg0 = args[0]
+ argx = args[1:]
+ }
+
+ // Disable output buffering, enable streaming
+ cmdOptions := cmd.Options{
+ Buffered: false,
+ Streaming: true,
+ }
+
+ // Create Cmd with options
+ envCmd := cmd.NewCmdOptions(cmdOptions, arg0, argx...)
+ if path != "" {
+ // set the path for exec
+ envCmd.Dir = path
+ }
+
+ // Print STDOUT and STDERR lines streaming from Cmd
+ doneChan := make(chan struct{})
+ go func() {
+ defer close(doneChan)
+ // Done when both channels have been closed
+ // https://dave.cheney.net/2013/04/30/curious-channels
+ for envCmd.Stdout != nil || envCmd.Stderr != nil {
+ select {
+ case line, open := <-envCmd.Stdout:
+ if !open {
+ envCmd.Stdout = nil
+ continue
+ }
+ save = append(save, line)
+ fmt.Println(line)
+ case line, open := <-envCmd.Stderr:
+ if !open {
+ envCmd.Stderr = nil
+ continue
+ }
+ save = append(save, line)
+ fmt.Println(line)
+ }
+ }
+ }()
+
+ // Run and wait for Cmd to return, discard Status
+ <-envCmd.Start()
+
+ // Wait for goroutine to print everything
+ <-doneChan
+
+ s := envCmd.Status()
+ s.Stdout = save
+ return s
+}
+
+// absolutely doesn't echo anything
+func PathRunQuiet(pwd string, args []string) cmd.Status {
+ // Check if the slice has at least one element (the command name)
+ if len(args) == 0 {
+ var s cmd.Status
+ s.Error = errors.New("Error: Command slice is empty.")
+ return s
+ }
+
+ // Start a long-running process, capture stdout and stderr
+ a, b := RemoveFirstElement(args)
+ findCmd := cmd.NewCmd(a, b...)
+ if pwd != "" {
+ findCmd.Dir = pwd
+ }
+ statusChan := findCmd.Start() // non-blocking
+
+ ticker := time.NewTicker(2 * time.Second)
+
+ // this is interesting, maybe useful, but wierd, but neat. interesting even
+ // Print last line of stdout every 2s
+ go func() {
+ for range ticker.C {
+ status := findCmd.Status()
+ n := len(status.Stdout)
+ if n != 0 {
+ fmt.Println("todo:removethisecho", status.Stdout[n-1])
+ }
+ }
+ }()
+
+ // Stop command after 1 hour
+ go func() {
+ <-time.After(1 * time.Hour)
+ findCmd.Stop()
+ }()
+
+ // Check if command is done
+ select {
+ case finalStatus := <-statusChan:
+ log.Info("finalStatus =", finalStatus.Exit, finalStatus.Error)
+ return finalStatus
+ // done
+ default:
+ // no, still running
+ }
+
+ // Block waiting for command to exit, be stopped, or be killed
+ finalStatus := <-statusChan
+ return finalStatus
+}
+
+func blah(cmd []string) {
+ r := Run(cmd)
+ log.Info("cmd =", r.Cmd)
+ log.Info("complete =", r.Complete)
+ log.Info("exit =", r.Exit)
+ log.Info("err =", r.Error)
+ log.Info("len(stdout+stderr) =", len(r.Stdout))
+}
+
+// run these to see confirm the sytem behaves as expected
+func RunTest() {
+ blah([]string{"ping", "-c", "3", "localhost"})
+ blah([]string{"exit", "0"})
+ blah([]string{"exit", "-1"})
+ blah([]string{"true"})
+ blah([]string{"false"})
+ blah([]string{"grep", "root", "/etc/", "/proc/cmdline", "/usr/bin/chmod"})
+ blah([]string{"grep", "root", "/proc/cmdline"})
+ fmt.Sprint("blahdone")
+}
+
+// this is stuff from a long time ago that there must be a replacement for
+func RemoveFirstElement(slice []string) (string, []string) {
+ if len(slice) == 0 {
+ return "", slice // Return the original slice if it's empty
+ }
+ return slice[0], slice[1:] // Return the slice without the first element
+}
diff --git a/int.go b/int.go
deleted file mode 100644
index bf78209..0000000
--- a/int.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package shell
-
-/*
- send it anything, always get back an int
-*/
-
-// import "log"
-// import "reflect"
-// import "strings"
-// import "bytes"
-import "strconv"
-
-func Int(s string) int {
- s = Chomp(s)
- i, err := strconv.Atoi(s)
- if err != nil {
- handleError(err, -1)
- return 0
- }
- return i
-}
-
-func Int64(s string) int64 {
- s = Chomp(s)
- i, err := strconv.Atoi(s)
- if err != nil {
- handleError(err, -1)
- return 0
- }
- return int64(i)
-}
diff --git a/shell.go b/old.go
index 9ef2dfb..b4133fd 100644
--- a/shell.go
+++ b/old.go
@@ -1,12 +1,12 @@
package shell
+// old code and probably junk
+
import (
"io/ioutil"
"net/http"
"os"
"os/exec"
- "strings"
- "time"
"go.wit.com/log"
)
@@ -47,6 +47,7 @@ func Quiet(q bool) {
quiet = q
}
+/*
func Script(cmds string) int {
// split on new lines (while we are at it, handle stupid windows text files
lines := strings.Split(strings.Replace(cmds, "\r\n", "\n", -1), "\n")
@@ -59,22 +60,7 @@ func Script(cmds string) int {
}
return 0
}
-
-func SpewOn() {
- spewOn = true
-}
-
-func SetDelayInMsec(msecs int) {
- msecDelay = msecs
-}
-
-func SetStdout(newout *os.File) {
- shellStdout = newout
-}
-
-func SetStderr(newerr *os.File) {
- shellStderr = newerr
-}
+*/
func Unlink(filename string) bool {
if err := os.Remove(filename); err != nil {
@@ -84,40 +70,16 @@ func Unlink(filename string) bool {
}
}
-func RM(filename string) {
- os.Remove(Path(filename))
-}
-
-func Daemon(cmdline string, timeout time.Duration) int {
- for {
- RunString(cmdline)
- time.Sleep(timeout)
- }
-}
-
-// run something and never return from it
-// TODO: pass STDOUT, STDERR, STDIN correctly
-// TODO: figure out how to nohup the process and exit
-func Exec(cmdline string) {
- log.Log(INFO, "shell.Run() START "+cmdline)
-
- cmd := Chomp(cmdline) // this is like 'chomp' in perl
- cmdArgs := strings.Fields(cmd)
-
- process := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...)
- process.Stderr = os.Stderr
- process.Stdin = os.Stdin
- process.Stdout = os.Stdout
- process.Start()
- err := process.Wait()
- log.Log(INFO, "shell.Exec() err =", err)
- os.Exit(0)
-}
-
// run interactively. output from the cmd is in real time
// shows all the output. For example, 'ping -n localhost'
// shows the output like you would expect to see
-func NewRun(workingpath string, cmd []string) error {
+func RunSimple(cmd []string) error {
+ log.Log(INFO, "NewRun() ", cmd)
+
+ return PathRunSimple("", cmd)
+}
+
+func PathRunSimple(workingpath string, cmd []string) error {
log.Log(INFO, "NewRun() ", cmd)
process := exec.Command(cmd[0], cmd[1:len(cmd)]...)
@@ -128,11 +90,15 @@ func NewRun(workingpath string, cmd []string) error {
process.Stdout = os.Stdout
process.Start()
err := process.Wait()
- log.Log(INFO, "shell.Exec() err =", err)
+ if err != nil {
+ log.Log(INFO, "shell.Exec() err =", err)
+ }
return err
}
// return true if the filename exists (cross-platform)
+
+// return true if the filename exists (cross-platform)
func Exists(filename string) bool {
_, err := os.Stat(Path(filename))
if os.IsNotExist(err) {
@@ -175,29 +141,9 @@ func Cat(filename string) string {
if err != nil {
return ""
}
- return Chomp(buffer)
+ return string(buffer)
}
-/*
-// run interactively. output from the cmd is in real time
-// shows all the output. For example, 'ping -n localhost'
-// shows the output like you would expect to see
-func RunPathHttpOut(workingpath string, cmd []string, w http.ResponseWriter, r *http.Request) error {
- log.Log(INFO, "NewRun() ", cmd)
-
- process := exec.Command(cmd[0], cmd[1:len(cmd)]...)
- // Set the working directory
- process.Dir = workingpath
- process.Stderr = os.Stderr
- process.Stdin = os.Stdin
- process.Stdout = os.Stdout
- process.Start()
- err := process.Wait()
- log.Log(INFO, "shell.Exec() err =", err)
- return err
-}
-*/
-
func RunPathHttpOut(path string, cmd []string, w http.ResponseWriter, r *http.Request) error {
log.Warn("Run(): ", cmd)
diff --git a/run.go b/run.go
deleted file mode 100644
index ca6bf23..0000000
--- a/run.go
+++ /dev/null
@@ -1,253 +0,0 @@
-package shell
-
-import (
- "errors"
- "os"
- "os/exec"
- "strings"
- "syscall"
-
- "go.wit.com/log"
-)
-
-var msecDelay int = 20 // check every 20 milliseconds
-
-// TODO: look at https://github.com/go-cmd/cmd/issues/20
-// use go-cmd instead here?
-// exiterr.Sys().(syscall.WaitStatus)
-
-// run command and return it's output
-/*
-func RunCapture(cmdline string) string {
- test := New()
- test.Exec(cmdline)
- return Chomp(test.Buffer)
-}
-
-func RunWait(args []string) *OldShell {
- test := New()
- cmdline := strings.Join(args, " ")
- test.Exec(cmdline)
- return test
-}
-*/
-
-// var newfile *shell.File
-func RunString(args string) bool {
- // return false
- parts := strings.Split(args, " ")
- return Run(parts)
-}
-
-func Run(args []string) bool {
- dir, err := os.Getwd()
- if err != nil {
- return false
- }
-
- r := RunPath(dir, args)
- if r.Ok {
- return true
- }
- return false
-}
-
-var ErrorArgvEmpty error = errors.New("command was empty")
-
-type RunResult struct {
- Ok bool
- Argv []string
- Path string
- Output []byte
- Err error
- Outerr error
-}
-
-// run, but set the working path
-func RunPath(path string, args []string) *RunResult {
- r := new(RunResult)
- r.Path = path
- r.Argv = args
- if len(args) == 0 {
- r.Ok = true
- r.Err = ErrorArgvEmpty
- return r
- }
- if args[0] == "" {
- r.Ok = false
- r.Err = ErrorArgvEmpty
- return r
- }
- thing := args[0]
- parts := args[1:]
- cmd := exec.Command(thing, parts...)
- cmd.Dir = path
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
-
- log.Info("path =", path, "cmd =", strings.Join(args, " "))
- if err := cmd.Run(); err != nil {
- // Handle error if the command execution fails
- // log.Info("RunPath() failed")
- // log.Info("cmd.Enviorn =", cmd.Environ())
- out, outerr := cmd.Output()
- // log.Info("cmd.output =", out)
- // log.Info("cmd.output err=", outerr)
- // log.Info("path =", path)
- // log.Info("args =", args)
- // log.Info("err =", err.Error())
- r.Ok = false
- r.Err = err
- r.Output = out
- r.Outerr = outerr
- return r
- }
- out, outerr := cmd.Output()
- r.Output = out
- r.Outerr = outerr
- r.Ok = true
- return r
-}
-
-// send the path and the command
-// captures the output so you can not see the command run in real time
-func RunCmd(workingpath string, parts []string) (error, bool, string) {
- if len(parts) == 0 {
- log.Warn("command line was empty")
- return errors.New("empty"), false, ""
- }
- if parts[0] == "" {
- log.Warn("command line was empty")
- return errors.New("empty"), false, ""
- }
- thing := parts[0]
- parts = parts[1:]
- log.Log(INFO, "working path =", workingpath, "thing =", thing, "cmdline =", parts)
-
- // Create the command
- cmd := exec.Command(thing, parts...)
-
- // Set the working directory
- cmd.Dir = workingpath
-
- // Execute the command
- output, err := cmd.CombinedOutput()
- if err != nil {
- if thing == "git" {
- log.Log(INFO, "git ERROR. maybe okay", workingpath, "thing =", thing, "cmdline =", parts)
- log.Log(INFO, "git ERROR. maybe okay err =", err)
- if err.Error() == "exit status 1" {
- log.Log(INFO, "git ERROR. normal exit status 1")
- if parts[0] == "diff-index" {
- log.Log(INFO, "git normal diff-index when repo dirty")
- return nil, false, "git diff-index exit status 1"
- }
- }
- }
-
- log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
- log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
- log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
- log.Error(err)
- log.Warn("output was", string(output))
- log.Warn("cmd exited with error", err)
- // panic("fucknuts")
-
- // The command failed (non-zero exit status)
- if exitErr, ok := err.(*exec.ExitError); ok {
- // Assert that it is an exec.ExitError and get the exit code
- if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
- log.Warn("Exit Status: %d\n", status.ExitStatus())
- }
- } else {
- log.Warn("cmd.Run() failed with %s\n", err)
- }
- return err, false, string(output)
- }
-
- tmp := string(output)
- tmp = strings.TrimSpace(tmp)
-
- // Print the output
- return nil, true, tmp
-}
-
-// send the path and the command
-// also does not seem to show the output in realtime
-func RunCmdRun(workingpath string, parts []string) error {
- if len(parts) == 0 {
- log.Warn("command line was empty")
- return errors.New("empty")
- }
- if parts[0] == "" {
- log.Warn("command line was empty")
- return errors.New("empty")
- }
- thing := parts[0]
- parts = parts[1:]
- log.Log(INFO, "working path =", workingpath, "thing =", thing, "cmdline =", parts)
-
- // Create the command
- cmd := exec.Command(thing, parts...)
-
- // Set the working directory
- cmd.Dir = workingpath
-
- // Execute the command
- err := cmd.Run()
- if err != nil {
- log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
- log.Error(err)
- log.Warn("cmd exited with error", err)
- // panic("fucknuts")
-
- // The command failed (non-zero exit status)
- if exitErr, ok := err.(*exec.ExitError); ok {
- // Assert that it is an exec.ExitError and get the exit code
- if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
- log.Warn("Exit Status: %d\n", status.ExitStatus())
- }
- } else {
- log.Warn("cmd.Run() failed with %s\n", err)
- }
- return err
- }
- return nil
-}
-
-func (r *RunResult) Stdout() string {
- return string(r.Output)
-}
-
-// run, but set the working path
-func Output(path string, args []string) *RunResult {
- r := new(RunResult)
- r.Path = path
- r.Argv = args
- if len(args) == 0 {
- r.Ok = true
- r.Err = ErrorArgvEmpty
- return r
- }
- if args[0] == "" {
- r.Ok = false
- r.Err = ErrorArgvEmpty
- return r
- }
- thing := args[0]
- parts := args[1:]
- cmd := exec.Command(thing, parts...)
- cmd.Dir = path
- output, err := cmd.CombinedOutput()
-
- if err := cmd.Run(); err != nil {
- r.Ok = false
- r.Err = err
- r.Output = output
- return r
- }
- r.Output = output
- r.Err = err
- r.Ok = true
- return r
-}
diff --git a/wget.go b/wget.go
index bd767d6..a09550b 100644
--- a/wget.go
+++ b/wget.go
@@ -74,8 +74,7 @@ func WgetToFile(filepath string, url string) error {
// BUGS: The author's idea of friendly may differ to that of many other people.
func Write(filepath string, data string) bool {
// TODO: this isn't working for some reason and is making two '\n' chars
- // probably because Chomp() isn't fixed yet
- data = Chomp(data) + "\n"
+ data = strings.TrimSpace(data) + "\n"
// Create the file
ospath := Path(filepath)
log.Log(INFO, "shell.Write() START ospath =", ospath, "filepath =", filepath)
diff --git a/xterm.go b/xterm.go
index b06ef33..051e956 100644
--- a/xterm.go
+++ b/xterm.go
@@ -123,7 +123,7 @@ func XtermCmdWait(path string, cmd []string) {
// keeps git diff from exiting on small diffs
os.Setenv("LESS", "-+F -+X -R")
- RunCmdRun(path, argsXterm)
+ PathRun(path, argsXterm)
}
// spawns an xterm with something you can run at a command line
@@ -136,5 +136,5 @@ func XtermCmdBash(path string, cmd []string) {
bash += "'; bash\""
tmp = append(argsXterm, "bash", bash)
log.Info("XtermCmd() path =", path, "cmd =", tmp)
- go RunCmd(path, tmp)
+ go PathRun(path, tmp)
}