From 2d875260e81fb5cf99b6468bb784584161058ad9 Mon Sep 17 00:00:00 2001 From: kristof de spiegeleer Date: Sun, 7 Feb 2021 05:19:05 +0100 Subject: [PATCH] examples: Process examples (#8598) --- examples/process/.ignore | 1 + examples/process/command.v | 34 +++++++++++ examples/process/execve.v | 21 +++++++ examples/process/process_script.v | 59 ++++++++++++++++++ examples/process/process_stdin_trick.v | 83 ++++++++++++++++++++++++++ vlib/os/fd.c.v | 5 ++ vlib/os/process.v | 8 ++- 7 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 examples/process/.ignore create mode 100644 examples/process/command.v create mode 100644 examples/process/execve.v create mode 100644 examples/process/process_script.v create mode 100644 examples/process/process_stdin_trick.v diff --git a/examples/process/.ignore b/examples/process/.ignore new file mode 100644 index 0000000000..e42252a73d --- /dev/null +++ b/examples/process/.ignore @@ -0,0 +1 @@ +command \ No newline at end of file diff --git a/examples/process/command.v b/examples/process/command.v new file mode 100644 index 0000000000..718ce96326 --- /dev/null +++ b/examples/process/command.v @@ -0,0 +1,34 @@ +module main + +import os + +// basic example which shows how to use the Command function + +fn exec(path string) string { + mut out := '' + mut line := '' + mut cmd := os.Command{ + path: path + } + cmd.start() or { panic(err) } + + for { + line = cmd.read_line() + println(line) + out += line + if cmd.eof { + return out + } + } + return out +} + +fn main() { + mut out := '' + exec("bash -c 'find /tmp/'") + out = exec('echo to stdout') + out = exec('echo to stderr 1>&2') + println("'$out'") + // THIS DOES NOT WORK, is error, it goes to stderror of the command I run + assert out == 'to stderr' +} diff --git a/examples/process/execve.v b/examples/process/execve.v new file mode 100644 index 0000000000..9cbd0b0c8e --- /dev/null +++ b/examples/process/execve.v @@ -0,0 +1,21 @@ +module main + +import os + +fn exec(args []string) { + mut out := '' + mut line := '' + mut line_err := '' + + os.execve('/bin/bash', args, []) or { + // eprintln(err) + panic(err) + } +} + +fn main() { + // exec(["-c","find /"]) //works + exec(['-c', 'find /tmp/']) // here it works as well + + // exec(["-c","find","/tmp/"]) // does not work I guess is normal +} diff --git a/examples/process/process_script.v b/examples/process/process_script.v new file mode 100644 index 0000000000..fa415e7839 --- /dev/null +++ b/examples/process/process_script.v @@ -0,0 +1,59 @@ +module main + +import os + +// a test where we execute a bash script but work around where we put script in bash inside bash + +fn exec(path string, redirect bool) { + mut line := '' + mut line_err := '' + mut cmd := os.new_process('/bin/bash') + + if redirect { + cmd.set_args(['-c', '/bin/bash /tmp/test.sh 2>&1']) + } else { + cmd.set_args([path]) + } + + cmd.set_redirect_stdio() + cmd.run() + if cmd.is_alive() { + for { + line = cmd.stdout_read() + println('STDOUT: $line') + + if !redirect { + line_err = cmd.stderr_read() + println('STDERR: $line_err') + } + + if !cmd.is_alive() { + break + } + } + } + if cmd.code > 0 { + println('ERROR:') + println(cmd) + // println(cmd.stderr_read()) + } +} + +fn main() { + script := ' +echo line 1 +#will use some stderr now +echo redirect 1 to 2 1>&2 +echo line 3 +' + + os.write_file('/tmp/test.sh', script) or { panic(err) } + // os.chmod("/tmp/test.sh",0o700) //make executable + + // this will work because stderr/stdout are smaller than 4096 chars, once larger there can be deadlocks + // in other words this can never work reliably without being able to check if there is data on stderr or stdout + exec('/tmp/test.sh', false) + + // this will always work + exec('/tmp/test.sh', true) +} diff --git a/examples/process/process_stdin_trick.v b/examples/process/process_stdin_trick.v new file mode 100644 index 0000000000..7a1455d75e --- /dev/null +++ b/examples/process/process_stdin_trick.v @@ -0,0 +1,83 @@ +module main + +import os + +// this is a example script to show you stdin can be used and keep a process open + +fn exec(cmd string) (string, int) { + mut cmd2 := cmd + mut out := '' + mut line := '' + mut rc := 0 + mut p := os.new_process('/bin/bash') + + // there are methods missing to know if stderr/stdout has data as such its better to redirect bot on same FD + // not so nice trick to run bash in bash and redirect stderr, maybe someone has a better solution + p.set_args(['-c', 'bash 2>&1']) + p.set_redirect_stdio() + p.run() + + if !cmd2.ends_with('\n') { + cmd2 += '\n' + } + + p.stdin_write(cmd2) + p.stdin_write('\necho **OK**\n') + + for { + if !p.is_alive() { + break + } + line = p.stdout_read() + println(line) + // line_err = p.stderr_read() //IF WE CALL STDERR_READ will block + // we need a mechanism which allows us to check if stderr/stdout has data or it should never block + // is not a good way, need to use a string buffer, is slow like this + out += line + if out.ends_with('**OK**\n') { + out = out[0..(out.len - 7)] + break + } + } + + // println("read from stdout, should not block") + // is not really needed but good test to see behaviour + // out += p.stdout_read() + // println("read done") + + // println(cmd.stderr_read()) + + if p.code > 0 { + rc = 1 + println('ERROR:') + println(cmd2) + print(out) + } + // documentation says we need to call p.wait(), but this does not seem to work, will be process stop or become zombie? + // p.wait() + + return out, rc +} + +fn main() { + mut out := '' + mut rc := 0 + + // the following does not work, not sure why not + // out,rc = exec("find /tmp/ && echo '******'") + + out, rc = exec("find /tmp/ ; echo '******'") + println(out) + assert out.ends_with('******\n') + + out, rc = exec('echo to stdout') + assert out.contains('to stdout') + + out, rc = exec('echo to stderr 1>&2') + assert out.contains('to stderr') + + out, rc = exec('ls /sssss') + assert rc > 0 // THIS STILL GIVES AN ERROR ! + + println('test ok stderr & stdout is indeed redirected') +} diff --git a/vlib/os/fd.c.v b/vlib/os/fd.c.v index e786e5ed33..7fe9032940 100644 --- a/vlib/os/fd.c.v +++ b/vlib/os/fd.c.v @@ -1,6 +1,8 @@ module os // file descriptor based operations: + +// close filedescriptor pub fn fd_close(fd int) int { return C.close(fd) } @@ -18,6 +20,7 @@ pub fn fd_write(fd int, s string) { } } +// read from filedescriptor, block until data pub fn fd_slurp(fd int) []string { mut res := []string{} for { @@ -30,6 +33,8 @@ pub fn fd_slurp(fd int) []string { return res } +// read from filedescriptor, don't block +// return [bytestring,nrbytes] pub fn fd_read(fd int, maxbytes int) (string, int) { mut buf := malloc(maxbytes) nbytes := C.read(fd, buf, maxbytes) diff --git a/vlib/os/process.v b/vlib/os/process.v index fda1af57aa..9bef54a85c 100644 --- a/vlib/os/process.v +++ b/vlib/os/process.v @@ -26,9 +26,9 @@ pub mut: err string // if the process fails, contains the reason why args []string // the arguments that the command takes env_is_custom bool // true, when the environment was customized with .set_environment - env []string // the environment with which the process was started + env []string // the environment with which the process was started (list of 'var=val') use_stdio_ctl bool // when true, then you can use p.stdin_write(), p.stdout_slurp() and p.stderr_slurp() - stdio_fd [3]int + stdio_fd [3]int // the file descriptors } // new_process - create a new process descriptor @@ -162,16 +162,20 @@ pub fn (mut p Process) stdin_write(s string) { fd_write(p.stdio_fd[0], s) } +// will read from stdout pipe, will only return when EOF (end of file) or data +// means this will block unless there is data pub fn (mut p Process) stdout_slurp() string { p._check_redirection_call('stdout_slurp') return fd_slurp(p.stdio_fd[1]).join('') } +// read from stderr pipe, wait for data or EOF pub fn (mut p Process) stderr_slurp() string { p._check_redirection_call('stderr_slurp') return fd_slurp(p.stdio_fd[2]).join('') } +// read from stdout, return if data or not pub fn (mut p Process) stdout_read() string { p._check_redirection_call('stdout_read') s, _ := fd_read(p.stdio_fd[1], 4096)