Golang processes

From wikinotes
Revision as of 21:59, 17 July 2022 by Will (talk | contribs) (→‎Subprocess)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Start/Manage a process, or get information about system processes.

Documentation

os https://pkg.go.dev/os@go1.18.3

Current Process

import "os"
import "os/user"

os.Executable()
os.Getpid()
os.Getppid()
os.Getuid()
os.Getgid()

os.Exit(1)              // exit process with exitcode '1'

// environment
envvars := os.Environ() // ["FOO=bar", "BAR=baz", ...]
pwd, err := os.Getwd()  // get current pwd/cwd
user.Current()          // 'will'

Manage Processes

import "syscall"
import "os"
import "os/signal"

proc := os.Process.FindProcess(1234)  // find process with pid
proc.Kill()                           // kill process (-9/SIGKILL)
proc.Signal(os.Iterrupt)              // send SIGINT to process (looks like you can use any syscall.SIG*)
proc.Wait()                           // wait for process to end

syscall.Kill(1234, syscall.SIGINT)    // send SIGINT to pid 1234

Subprocess

high-level/friendly

// 1. create command,
// 2. modify it's exec.Cmd struct (ex. stdin,stdout)
// 3. run process
// 4. cmd.ProcessState has info about process, including exitcode
import "os"
import "os/exec"

cmd := exec.Command("netstat", "-an")
stdout, err := cmd.Output()

// must be read-from/written-to in go routine! (can be anonymous)
// ex. go func() { defer stdin.Close(); stdin.Write([]byte("foo")); }()
reader, err := cmd.StderrPipe()
reader, err := cmd.StdoutPipe()
writer, err := cmd.StdinPipe()

cmd.Process.Pid    // get pid (after starting process)
err := cmd.Run()   // run and wait
err := cmd.Start() // run, don't wait
err := cmd.Wait()  // wait for proces to finish

StdinPipe example


cmd := exec.Command("pandoc", "-f", "mediawiki", "-t", "rst")
stdin, _ := cmd.StdinPipe()
ch := make(chan, error)
go func(ch chan<- error) {
    defer stdin.Close()
    _, err := stdin.Write([]byte("abc"))
    ch <- err
}(ch)
cmd.Run()


Stdout/Stderr example

cmd := exec.Command("pandoc", "-f", "mediawiki", "-t", "rst", "foo.mediawiki")
stdout, _ := cmd.StdoutPipe()
defer stdout.Close()
cmd.Start()
out, _ := io.ReadAll(stdout)
cmd.Wait()

Stdin/Stdout/Stderr example


Pipes returned from exec.Cmd methods are closed automatically when exec.Cmd.Wait() is called.
When manipulating both Stdin/Stdout, the process may wait for Stdin to be closed to complete.
You'll need to close it manually, and surface Close() errors using channels.

func (this *Cmd) Execute(stdin io.Reader) (render string, errs []error) {
	// record goroutine errors
	wg := sync.WaitGroup{}
	wg.Add(1)
	ch := make(chan error, 2)
	defer func(ch <-chan error) {
		wg.Wait()
		err := <-ch
		if err != nil {
			errs = append(errs, err)
		}
	}(ch)

	// build pipes
	stdout, err := this.StdoutPipe()
	if err != nil {
		errs = append(errs, err)
	}
	stderr, err := this.StderrPipe()
	if err != nil {
		errs = append(errs, err)
	}
	stdinW, err := this.StdinPipe()
	if err != nil {
		errs = append(errs, err)
	}
	if len(errs) > 0 {
		return "", errs
	}

	// write stdin
	go func(ch chan<- error) {
		defer func() {
			err := stdinW.Close()
			if err != nil {
				ch <- err
			}
			close(ch)
			wg.Done()
		}()
		data, err := io.ReadAll(stdin)
		if err != nil {
			ch <- err
			return
		}
		if _, err = stdinW.Write(data); err != nil {
			ch <- err
		}
	}(ch)

	// run command
	err = this.Start()
	if err != nil {
		errs = append(errs, err)
		return "", errs
	}

	outAll, err := io.ReadAll(stdout)
	if err != nil {
		errs = append(errs, err)
	}
	errAll, err := io.ReadAll(stderr)
	if err != nil {
		errs = append(errs, err)
	}
	if len(errs) > 0 {
		return "", errs
	}

	err = this.Wait()
	if err != nil {
		logger.Debugf("STDERR:\n%s", errAll)
		errs = append(errs, err)
	}
	return string(outAll), errs
}


low-level

// 1. create ProcAttr
// 2. start process
// 3. wait for process
// 4. get info from it's ProcessState
import "os"
import "os/exec"

psAttr := os.ProcAttr{
    Dir: "/var/tmp",
    Env: []string{"FOO=BAR"},
}
ps := os.StartProcess("netstat", []string{"-a", "-n"}, psAttr)
pstate, err := ps.Wait()
pstate.ExitCode()

Reading from STDIN

stat, _ := os.Stdin.Stat()

if (stat.Mode() & os.ModeNamedPipe) == 0 {
    fmt.Println("Nothing to read on stdin")
    os.Exit(1)
} else {
    var reader = bufio.NewReader(os.Stdin)
    for {
        line, err := reader.ReadString('\n')
        if err == io.EOF {
            break
        }
        fmt.Print(line)
        time.Sleep(1 * time.Second)
    }
}