Golang processes: Difference between revisions

From wikinotes
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{ TODO |
Start/Manage a process, or get information about system processes.
* signal handling
* safely reading from stdin
* piping outputs to inputs
* what about exit codes?
}}


= Documentation =
= Documentation =
Line 66: Line 61:
stdout, err := cmd.Output()
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.StderrPipe()
reader, err := cmd.StdoutPipe()
reader, err := cmd.StdoutPipe()
writer, err := cmd.StdinPipe()
writer, err := cmd.StdinPipe()


cmd.Process.Pid    // get pid (after starting process)
err := cmd.Run()  // run and wait
err := cmd.Run()  // run and wait
err := cmd.Start() // run, don't wait
err := cmd.Start() // run, don't wait
err := cmd.Wait()  // wait for proces to finish
err := cmd.Wait()  // wait for proces to finish
</syntaxhighlight>
</syntaxhighlight>
{{ expand
| StdinPipe example
|
<syntaxhighlight lang="go">
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()
</syntaxhighlight>
}}
{{ expand
| Stdout/Stderr example
|
<syntaxhighlight lang="go">
cmd := exec.Command("pandoc", "-f", "mediawiki", "-t", "rst", "foo.mediawiki")
stdout, _ := cmd.StdoutPipe()
defer stdout.Close()
cmd.Start()
out, _ := io.ReadAll(stdout)
cmd.Wait()
</syntaxhighlight>
}}
{{ expand
| Stdin/Stdout/Stderr example
|
Pipes returned from <code>exec.Cmd</code> methods are closed automatically when <code>exec.Cmd.Wait()</code> is called.<br>
When manipulating both Stdin/Stdout, the process may wait for Stdin to be closed to complete.<br>
You'll need to close it manually, and surface Close() errors using channels.
<syntaxhighlight lang="go">
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
}
</syntaxhighlight>
}}


low-level
low-level
Line 97: Line 215:
<blockquote>
<blockquote>
<syntaxhighlight lang="go">
<syntaxhighlight lang="go">
var reader = bufio.NewReader(os.Stdin)
stat, _ := os.Stdin.Stat()
stat, err := os.Stdin.Stat()
panicOn(err)


if (stat.Mode() & os.ModeNamedPipe) == 0 {
if (stat.Mode() & os.ModeNamedPipe) == 0 {
Line 105: Line 221:
     os.Exit(1)
     os.Exit(1)
} else {
} else {
    var reader = bufio.NewReader(os.Stdin)
     for {
     for {
         line, err := reader.ReadString('\n')
         line, err := reader.ReadString('\n')

Latest revision as of 21:59, 17 July 2022

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)
    }
}