How to write a shell that can switch to bash? - bash

I want to switch to the bash shell from my custom shell in go.
I am not sure how to sleep my parent process (custom shell) and switch to the child process (bash)
This is my part of the code.
cmd := exec.Command("bash", "-c", "/bin/bash")
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf(err.Error())
}
fmt.Printf("%s\n", stdoutStderr)
I want to do it as follows:
myshell >> /bin/bash
$ /bin/myshell
myshell >>
the code is exec but not fork
binary, lookErr := exec.LookPath("/bin/bash")
if lookErr != nil {
panic(lookErr)
}
args := []string{"/bin/bash"}
env := os.Environ()
execErr := syscall.Exec(binary, args, env)
if execErr != nil {
panic(execErr)
}
so if I exit the bash shell, of course, my custom shell is killed.
myshell> bash
bash-3.2$ exit
exit

This is a recipe for how to stop a process and let the parent shell process take over control. I'm not sure if you can use it to do the opposite.
The idea is to send a stop signal to yourself.
pid := os.Getpid()
process, err := os.FindProcess(pid)
if err != nil {
err = errors.Wrapf(err, "error pausing process %+v", process)
return
}
process.Signal(syscall.SIGSTOP)
Source https://github.com/grzegorz-zur/bare-minimum/blob/95fb116631b6146707c47455d8ffce6bb5b8717c/editor.go#L152
Earlier you need to listen in a goroutine to continue signal to reinitialize your process if needed
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGCONT, syscall.SIGTERM)
for signal := range signals {
switch signal {
case syscall.SIGCONT:
// reinitialize
case syscall.SIGTERM:
// stop
}
}
Source https://github.com/grzegorz-zur/bare-minimum/blob/95fb116631b6146707c47455d8ffce6bb5b8717c/editor.go#L87

I have resolved this problem by this way.
package main
import (
"os/exec"
)
func main() {
cmd := exec.Command("sh", "-c", "bash")
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Run()
}

Related

How do you get the color of the output from an exec.StdoutPipe()?

I want to be able to seamlessly print the output from an unknown(user defined) command in go passed through a io.ReadCloser. The bufio.NewScanner reads the std out and prints the text correctly, however the color that the child process prints is not recorded and passed through the pipe(or I don't know how to access it).
I tried using execErr := syscall.Exec(binary, cmd.Args, os.Environ()) however since this takes over the go process, I can't get an array of processes to run.
// SpawnGroup spawns a group of processes
func SpawnGroup(cmds []*exec.Cmd) {
spawnWg := &sync.WaitGroup{}
spawnWg.Add(len(cmds))
defer spawnWg.Wait()
for _, cmd := range cmds {
go Spawn(cmd, spawnWg)
}
}
// Spawn spawn a child process
func Spawn(cmd *exec.Cmd, wg *sync.WaitGroup) {
defer wg.Done()
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
err := cmd.Start()
if err != nil {
color.Red(err.Error())
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
errScanner := bufio.NewScanner(stderr)
for errScanner.Scan() {
color.Red(scanner.Text())
}
cmd.Wait()
}
For example, if I try to run two commands passed in an array such as sls offline and vue-cli-service serve everything works as expected, but the logged output doesn't have the color. Both of these processes print some of their output to the command line with color. The exact array of commands will be unknown, so I need a way to print the exact output from them.
I was able to get this to work in node by using:
let cmd = await spawn(command, args, {
stdio: 'inherit',
shell: true,
...options
});
I haven't been able to track down how to do this in go.
Thanks for any advice or help, this is my first real dive into go, but it seems like an excellent language!
Changing the spawn code to assign the os.Stdout to the cmd.Stdout enabled the output to print with the correct colors.
func Spawn(cmd *exec.Cmd, wg *sync.WaitGroup) {
defer wg.Done()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
color.Red(err.Error())
}
defer cmd.Wait()
}```

Golang os/exec flushing stdin without closing it

I would like to manage a process in Go with the package os/exec. I would like to start it and be able to read the output and write several times to the input.
The process I launch in the code below, menu.py, is just a python script that does an echo of what it has in input.
func ReadOutput(rc io.ReadCloser) (string, error) {
x, err := ioutil.ReadAll(rc)
s := string(x)
return s, err
}
func main() {
cmd := exec.Command("python", "menu.py")
stdout, err := cmd.StdoutPipe()
Check(err)
stdin, err := cmd.StdinPipe()
Check(err)
err = cmd.Start()
Check(err)
go func() {
defer stdin.Close() // If I don't close the stdin pipe, the python code will never take what I write in it
io.WriteString(stdin, "blub")
}()
s, err := ReadOutput(stdout)
if err != nil {
Log("Process is finished ..")
}
Log(s)
// STDIN IS CLOSED, I CAN'T RETRY !
}
And the simple code of menu.py :
while 1 == 1:
name = raw_input("")
print "Hello, %s. \n" % name
The Go code works, but if I don't close the stdin pipe after I write in it, the python code never take what is in it. It is okay if I want to send only one thing in the input on time, but what is I want to send something again few seconds later? Pipe is closed! How should I do? The question could be "How do I flush a pipe from WriteCloser interface?" I suppose
I think the primary problem here is that the python process doesn't work the way you might expect. Here's a bash script echo.sh that does the same thing:
#!/bin/bash
while read INPUT
do echo "Hello, $INPUT."
done
Calling this script from a modified version of your code doesn't have the same issue with needing to close stdin:
func ReadOutput(output chan string, rc io.ReadCloser) {
r := bufio.NewReader(rc)
for {
x, _ := r.ReadString('\n')
output <- string(x)
}
}
func main() {
cmd := exec.Command("bash", "echo.sh")
stdout, err := cmd.StdoutPipe()
Check(err)
stdin, err := cmd.StdinPipe()
Check(err)
err = cmd.Start()
Check(err)
go func() {
io.WriteString(stdin, "blab\n")
io.WriteString(stdin, "blob\n")
io.WriteString(stdin, "booo\n")
}()
output := make(chan string)
defer close(output)
go ReadOutput(output, stdout)
for o := range output {
Log(o)
}
}
The Go code needed a few minor changes - ReadOutput method needed to be modified in order to not block - ioutil.ReadAll would have waited for an EOF before returning.
Digging a little deeper, it looks like the real problem is the behaviour of raw_input - it doesn't flush stdout as expected. You can pass the -u flag to python to get the desired behaviour:
cmd := exec.Command("python", "-u", "menu.py")
or update your python code to use sys.stdin.readline() instead of raw_input() (see this related bug report: https://bugs.python.org/issue526382).
Even though there is some problem with your python script. The main problem is the golang pipe. A trick to solve this problem is use two pipes as following:
// parentprocess.go
package main
import (
"bufio"
"log"
"io"
"os/exec"
)
func request(r *bufio.Reader, w io.Writer, str string) string {
w.Write([]byte(str))
w.Write([]byte("\n"))
str, err := r.ReadString('\n')
if err != nil {
panic(err)
}
return str[:len(str)-1]
}
func main() {
cmd := exec.Command("bash", "menu.sh")
inr, inw := io.Pipe()
outr, outw := io.Pipe()
cmd.Stdin = inr
cmd.Stdout = outw
if err := cmd.Start(); err != nil {
panic(err)
}
go cmd.Wait()
reader := bufio.NewReader(outr)
log.Printf(request(reader, inw, "Tom"))
log.Printf(request(reader, inw, "Rose"))
}
The subprocess code is the same logic as your python code as following:
#!/usr/bin/env bash
# menu.sh
while true; do
read -r name
echo "Hello, $name."
done
If you want to use your python code you should do some changes:
while 1 == 1:
try:
name = raw_input("")
print "Hello, %s. \n" % name
sys.stdout.flush() # there's a stdout buffer
except:
pass # make sure this process won't die when come across 'EOF'
// StdinPipe returns a pipe that will be connected to the command's
// standard input when the command starts.
// The pipe will be closed automatically after Wait sees the command exit.
// A caller need only call Close to force the pipe to close sooner.
// For example, if the command being run will not exit until standard input`enter code here`
// is closed, the caller must close the pipe.
func (c *Cmd) StdinPipe() (io.WriteCloser, error) {}

How do I send a signal to a child process?

I'm having trouble sending a signal from a parent process and receiving it in the child process.
This is the code for the child process. It exits when it receives SIGINT.
// child.go
func main() {
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
fmt.Println("started")
<-stop
fmt.Println("stopped")
}
This is the parent process. It starts child.go, sends SIGINT, then waits for it to exit.
// main.go
func main() {
// Start child process
cmd := exec.Command("go", "run", "child.go")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Start: " + err.Error())
return
}
// Wait, then send signal
time.Sleep(time.Millisecond * 500)
err = cmd.Process.Signal(os.Interrupt)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Signal: " + err.Error())
return
}
// Wait for child process to finish
err = cmd.Wait()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Wait: " + err.Error())
}
return
}
This code should print started\nstopped to show that it worked as expected, but it only prints started and hangs at cmd.Wait(), meaning the child process did not receive the signal.
When I run go run child.go it works fine, so I don't think the problem is with that file. I understand that func (*Process) Signal doesn't work on Windows; I am using Linux.
How can I fix the code so that the child process gets the signal sent by the parent process?
As mentioned by #JimB in the comments section, the go run is your problem.
go run child.go will compile child and execute it as it's own process. If you run a ps after go run child.go, you will see two processes running.
The process you are watching and signalling is the go executable, not the child.
Replace the exec.Command("go", "run", "child.go") with the compiled binary exec.Command("child")and it should work.

How to force golang to close opened pipe

I have to process a long output of a script and find some data. This data most likely will be located at the almost very beginning of the output. After data found I do not need to process output anymore and can quit.
The issue that I cannot stop processing output because exec.Cmd does not have any function to close opened command.
Here are some simplified code (error handling was ommited):
func processOutput(r *bufio.Reader)(){
for {
line, _, err := r.ReadLine()
if some_condition_meet {
break
}
......
}
return
}
func execExternalCommand(){
cmdToExecute := "......"
cmd := exec.Command("bash", "-c", cmdToExecute)
output, _ := cmd.StdoutPipe()
cmd.Start()
r := bufio.NewReader(output)
go processOutput(r)
cmd.Wait()
return
}
What should I do at the end at processOutput function to stop cmd? Maybe there is another way how to solve it.
Thanks
As it stands, you can't do this from processOutput because all it receives is a bufio.Reader. You would need to pass the exec.Cmd to it for it to do anything with the forked process.
To kill the forked process, you can send it a signal, e.g.: cmd.Process.Kill() or cmd.Process.Signal(os.SIGINT). See documentation on exec.Cmd and os.Process.
You could probably use "context" for example:
package main
import (
"bufio"
"context"
"os/exec"
)
func processOutput(r *bufio.Reader, cancel context.WithCancel) {
for {
line, _, err := r.ReadLine()
if some_condition_meet {
break
}
}
cancel()
return
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cmdToExecute := "......"
cmd := exec.CommandContext(ctx, "bash", "-c", cmdToExecute)
output, _ := cmd.StdoutPipe()
cmd.Start()
r := bufio.NewReader(output)
go processOutput(r, cancel)
cmd.Wait()
return
}
In case need to end with a timeout this could work (example is taken from here: https://golang.org/src/os/exec/example_test.go)
func ExampleCommandContext() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
// This will fail after 100 milliseconds. The 5 second sleep
// will be interrupted.
}
}
A basic example just using sleep but closing it after 1 second: https://play.golang.org/p/gIXKuf5Oga

Echo command in Golang

I'm currently trying to execute a simple echo command in Golang on Linux. My code is the following:
cmd = exec.Command("echo", "\"foo 0x50\"", ">", "test.txt")
_, err = cmd.Output()
if err != nil {
fmt.Println(err)
}
But test.txt doesn't appear in my folder (even after compile and run the code). That not the first that that I use this method to execute commands and I never thought that I will be block on an echo command.
So how can I fix this code in order to have "foo 0x50" (with the quotes) in the test.txt?
You can redirect the stdout like this:
// Remove the redirect from command
cmd := exec.Command("echo", "\"foo 0x50\"")
// Make test file
testFile, err := os.Create("test.txt")
if err != nil {
panic(err)
}
defer outfile.Close()
// Redirect the output here (this is the key part)
cmd.Stdout = testFile
err = cmd.Start(); if err != nil {
panic(err)
}
cmd.Wait()
For anyone who wants to pass the complete command in oneline/string ,
func call(command string) {
cmd := exec.Command("sudo", "bash", "-c", command)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
fmt.Println(err)
}
err1 := cmd.Wait()
if err1 != nil {
fmt.Println(err1)
}
}
The Wait waits for the command to exit and waits for any copying to stdin or copying from stdout or stderr to complete. It closes the pipe after seeing the command exit.
Sources :
https://stackoverflow.com/a/43246464/9892358
https://stackoverflow.com/a/43246464/9892358
https://zetcode.com/golang/exec-command/

Resources