From 30d3fe09b5e7d117a3db260a6700d95d19a48367 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Sun, 16 Jun 2019 10:49:01 -0700 Subject: [PATCH] smarter go syntax for Run() Signed-off-by: Jeff Carr --- run.go | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++ shell.go | 17 +++-- structs.go | 41 ++++++++++++ 3 files changed, 228 insertions(+), 9 deletions(-) create mode 100644 run.go diff --git a/run.go b/run.go new file mode 100644 index 0000000..9b74bd3 --- /dev/null +++ b/run.go @@ -0,0 +1,179 @@ +package shell + +import "strings" +import "time" +import "os/exec" +import "bytes" +import "io" +import "fmt" +import "os" +import "bufio" +import "github.com/svent/go-nbreader" + +import "git.wit.com/wit/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) + +// var newfile *shell.File + +func Run(cmdline string) string { + test := New() + test.Exec(cmdline) + return Chomp(test.Buffer) +} + +func (cmd *Shell) Run(cmdline string) string { + cmd.InitProcess(cmdline) + if (cmd.Error != nil) { + return "" + } + cmd.Exec(cmdline) + return Chomp(cmd.Buffer) +} + +func (cmd *Shell) InitProcess(cmdline string) { + log.Println("shell.InitProcess() START " + cmdline) + + cmd.Cmdline = Chomp(cmdline) // this is like 'chomp' in perl + cmdArgs := strings.Fields(cmd.Cmdline) + if (len(cmdArgs) == 0) { + cmd.Error = fmt.Errorf("cmdline == ''") + cmd.Done = true + return + } + if (cmdArgs[0] == "cd") { + if (len(cmdArgs) > 1) { + log.Println("os.Chdir()", cmd) + os.Chdir(cmdArgs[1]) + } + handleError(nil, 0) + cmd.Done = true + return + } + + cmd.Process = exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...) +} + +func (cmd *Shell) FileCreate(out string) { + var newfile File + + var iof io.ReadCloser + if (out == "STDOUT") { + iof, _ = cmd.Process.StdoutPipe() + } else { + iof, _ = cmd.Process.StderrPipe() + } + + newfile.Fio = iof + newfile.Fbufio = bufio.NewReader(iof) + newfile.Fnbreader = nbreader.NewNBReader(newfile.Fbufio, 1024) + + if (out == "STDOUT") { + cmd.STDOUT = &newfile + } else { + cmd.STDERR = &newfile + } +} + +// NOTE: this might cause problems: +// always remove the newlines at the end ? +func (cmd *Shell) Exec(cmdline string) { + log.Println("shell.Run() START " + cmdline) + + cmd.InitProcess(cmdline) + if (cmd.Error != nil) { + return + } + + cmd.FileCreate("STDOUT") + cmd.FileCreate("STDERR") + + cmd.Process.Start() + + // TODO; 'goroutine' both of these + // and make your own wait that will make sure + // the process is then done and run process.Wait() + go cmd.Capture(cmd.STDERR) + cmd.Capture(cmd.STDOUT) + + // wait until the process exists + // https://golang.org/pkg/os/exec/#Cmd.Wait + // What should happen here, before calling Wait() + // is checks to make sure the READERS() on STDOUT and STDERR are done + err := cmd.Process.Wait() + + // time.Sleep(2 * time.Second) // putting this here doesn't help STDOUT flush() + + if (err != nil) { + cmd.Error = err + log.Println("process.Wait() END err =", err.Error()) + } else { + log.Println("process.Wait() END") + } + return +} + +func (cmd *Shell) Capture(f *File) { + log.Debugln("nbrREADER() START") + + if (cmd.Buffer == nil) { + cmd.Buffer = new(bytes.Buffer) + } + if (cmd.Buffer == nil) { + log.Debugln("f.Buffer == nil") + log.Debugln("SHOULD DIE HERE") + f.Dead = false + cmd.Error = fmt.Errorf("could not make buffer") + cmd.Done = true + } + + f.Dead = false + + // loop that keeps trying to read from f + for (f.Dead == false) { + time.Sleep(time.Duration(msecDelay) * time.Millisecond) // only check the buffer 500 times a second + + // set to false so it keeps retrying reads + f.Empty = false + + // tight loop that reads 1024 bytes at a time until buffer is empty + // 1024 is set in f.BufferSize + for (f.Empty == false) { + f.Empty = cmd.ReadToBuffer(f) + } + } +} + +// returns true if filehandle buffer is empty +func (cmd *Shell) ReadToBuffer(f *File) bool { + // log.Debugln("ReadToBuffer() START") + nbr := f.Fnbreader + oneByte := make([]byte, 1024) + if (nbr == nil) { + // log.Debugln("ReadToBuffer() ERROR nbr is nil") + f.Dead = true + return true + } + count, err := nbr.Read(oneByte) + f.TotalCount += count + + if (err != nil) { + // log.Debugln("ReadToBuffer() file has closed with", err) + // log.Debugln("ReadToBuffer() count = ", count, "err = ", err) + f.Dead = true + return true + } + if (count == 0) { + // log.Debugln("ReadToBuffer() START count == 0 return true") + return true + } + // log.Debugln("ReadToBuffer() count = ", count) + // tmp := Chomp(oneByte) + // log.Debugln("ReadToBuffer() tmp = ", tmp) + io.WriteString(cmd.Buffer, string(oneByte)) + return false +} diff --git a/shell.go b/shell.go index 51f6378..2714e76 100644 --- a/shell.go +++ b/shell.go @@ -1,16 +1,13 @@ package shell -import "fmt" import "strings" import "time" import "os" import "os/exec" import "bufio" -import "bytes" -import "io" import "io/ioutil" -import "github.com/davecgh/go-spew/spew" +// import "github.com/davecgh/go-spew/spew" import "github.com/svent/go-nbreader" // import "log" @@ -26,10 +23,10 @@ var shellStderr *os.File var spewOn bool = false var quiet bool = false -var msecDelay int = 20 // number of milliseconds to delay between reads with no data +// var msecDelay int = 20 // number of milliseconds to delay between reads with no data -var bytesBuffer bytes.Buffer -var bytesSplice []byte +// var bytesBuffer bytes.Buffer +// var bytesSplice []byte func handleError(c interface{}, ret int) { log.Debug("shell.Run() Returned", ret) @@ -39,7 +36,7 @@ func handleError(c interface{}, ret int) { } func init() { - callback = nil + callback = nil } func InitCallback(f func(interface{}, int)) { @@ -80,9 +77,10 @@ func SetStderr(newerr *os.File) { shellStderr = newerr } +/* // NOTE: this might cause problems: // always remove the newlines at the end ? -func Run(cmdline string) string { +func OldRun(cmdline string) string { log.Println("shell.Run() START " + cmdline) cmd := Chomp(cmdline) // this is like 'chomp' in perl @@ -202,6 +200,7 @@ func Run(cmdline string) string { log.Println("shell.Run() END ", cmdline) return Chomp(b) } +*/ func Daemon(cmdline string, timeout time.Duration) int { for { diff --git a/structs.go b/structs.go index b5726e8..5f3df65 100644 --- a/structs.go +++ b/structs.go @@ -1,6 +1,7 @@ package shell import "io" +import "os/exec" import "bufio" import "bytes" import "github.com/svent/go-nbreader" @@ -9,6 +10,7 @@ var FileMap map[string]*File var readBufferSize int +/* type File struct { Name string BufferSize int @@ -22,7 +24,45 @@ type File struct { Fbufio *bufio.Reader // := bufio.NewReader(pOUT) Fnbreader *nbreader.NBReader // := nbreader.NewNBReader(readOUT, 1024) } +*/ +type File struct { + Name string + // BufferSize int + // Buffer *bytes.Buffer + // Fbytes []byte + TotalCount int + Empty bool + Dead bool + + Fio io.ReadCloser // := process.StdoutPipe() + Fbufio *bufio.Reader // := bufio.NewReader(pOUT) + Fnbreader *nbreader.NBReader // := nbreader.NewNBReader(readOUT, 1024) +} + +type Shell struct { + Cmdline string + Process *exec.Cmd + Done bool + Quiet bool + Error error + Buffer *bytes.Buffer + + // which names are really better here? + // for now I init them both to test out + // how the code looks and feels + STDOUT *File + STDERR *File + Stdout *File + Stderr *File +} + +func New() *Shell { + var tmp Shell + return &tmp +} + +/* func FileCreate(f io.ReadCloser) *File { var newfile File @@ -32,3 +72,4 @@ func FileCreate(f io.ReadCloser) *File { return &newfile } +*/