I’m trying to make a function that executes commands just like if they’d be inserted in a terminal. So if I execute (the command is to print the current shell name/path)
ps -p $$
bash
ps -p $$
I’d expect to see sh as the first shell and bash as the 2nd since I just created a new shell inside the main one –> doesn’t happen and I see sh printed both times with my current code I show below.
Code:
func main() {
var commands_list []string = []string{
"ps -p $$",
"bash",
"ps -p $$",
}
output, err := ExecCmdSHELL(commands_list)
if err != nil {
fmt.Println(err)
fmt.Println("-----------")
fmt.Println(output.Exit_code)
} else {
fmt.Println(err)
fmt.Println("-----------")
fmt.Println(output.Exit_code)
fmt.Println("-----------")
fmt.Println(output.Stdout_str)
fmt.Println("-----------")
fmt.Println(output.Stderr_str)
}
}
func ExecCmdMainSHELL(commands_list[] string, windows_shell string, linux_shell string) (CmdOutput, error) {
if windows_shell == "" {
windows_shell = "powershell.exe"
}
if linux_shell == "" {
linux_shell = "sh"
}
var shell string = ""
if "windows" == runtime.GOOS {
shell = windows_shell
} else {
shell = linux_shell
}
var commands_str string = ""
for _, command := range commands_list {
commands_str += command
if command != "" && !strings.HasSuffix(command, "\n") {
fmt.Println("Adding newline to command " + command)
commands_str += "\n"
}
}
fmt.Println(commands_str)
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.Command(shell)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Stdin = strings.NewReader(commands_str)
err := cmd.Run()
var stdout_str = strings.ReplaceAll(stdout.String(), "\r\n", "\n")
stdout_str = strings.ReplaceAll(stdout_str, "\r", "\n")
var stderr_str = strings.ReplaceAll(stderr.String(), "\r\n", "\n")
stderr_str = strings.ReplaceAll(stderr_str, "\r", "\n")
var exit_code int = 0
if err != nil {
var exiterr *exec.ExitError
if errors.As(err, &exiterr) {
exit_code = exiterr.ExitCode()
err = nil
} else {
exit_code = GENERIC_ERR
}
}
return CmdOutput{
Stdout_str: stdout_str,
Stdout: &stdout,
Stderr_str: stderr_str,
Stderr: &stderr,
Exit_code: exit_code,
}, err
}
Output:
PID TTY TIME CMD
955719 pts/0 00:00:00 sh
PID TTY TIME CMD
955719 pts/0 00:00:00 sh
I expected to see bash
on the 4th line. What’s wrong with my code?
Thank you in advance!
The issue you’re facing is related to the fact that the sh
shell you’re running is not interactive, and as a result, it doesn’t execute the .bashrc
or .bash_profile
files that would launch the bash
shell.
To make the subshell (bash
in this case) run in interactive mode, you can modify your command to include the -i
option, which forces the shell to be interactive. Here’s the modified code:
var commands_list []string = []string{
"ps -p $$",
"bash -i", // Run bash in interactive mode
"ps -p $$",
}
Now, when you run this, the bash
command will be executed in interactive mode, and you should see the new shell in the process list.
Here’s the modified code:
package main
import (
"bytes"
"fmt"
"os/exec"
"runtime"
"strings"
)
const (
GENERIC_ERR = -1
)
type CmdOutput struct {
Stdout_str string
Stdout *bytes.Buffer
Stderr_str string
Stderr *bytes.Buffer
Exit_code int
}
func main() {
var commands_list []string = []string{
"ps -p $$",
"bash -i", // Run bash in interactive mode
"ps -p $$",
}
output, err := ExecCmdMainSHELL(commands_list, "", "")
if err != nil {
fmt.Println(err)
fmt.Println("-----------")
fmt.Println(output.Exit_code)
} else {
fmt.Println("-----------")
fmt.Println(output.Exit_code)
fmt.Println("-----------")
fmt.Println(output.Stdout_str)
fmt.Println("-----------")
fmt.Println(output.Stderr_str)
}
}
func ExecCmdMainSHELL(commands_list []string, windows_shell string, linux_shell string) (CmdOutput, error) {
if windows_shell == "" {
windows_shell = "powershell.exe"
}
if linux_shell == "" {
linux_shell = "sh"
}
var shell string = ""
if "windows" == runtime.GOOS {
shell = windows_shell
} else {
shell = linux_shell
}
var commands_str string = ""
for _, command := range commands_list {
commands_str += command
if command != "" && !strings.HasSuffix(command, "\n") {
fmt.Println("Adding newline to command " + command)
commands_str += "\n"
}
}
fmt.Println(commands_str)
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.Command(shell)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Stdin = strings.NewReader(commands_str)
err := cmd.Run()
var stdout_str = strings.ReplaceAll(stdout.String(), "\r\n", "\n")
stdout_str = strings.ReplaceAll(stdout_str, "\r", "\n")
var stderr_str = strings.ReplaceAll(stderr.String(), "\r\n", "\n")
stderr_str = strings.ReplaceAll(stderr_str, "\r", "\n")
var exit_code int = 0
if err != nil {
var exiterr *exec.ExitError
if errors.As(err, &exiterr) {
exit_code = exiterr.ExitCode()
err = nil
} else {
exit_code = GENERIC_ERR
}
}
return CmdOutput{
Stdout_str: stdout_str,
Stdout: &stdout,
Stderr_str: stderr_str,
Stderr: &stderr,
Exit_code: exit_code,
}, err
}
With this modification, the bash
shell should now be run interactively, and you should see it in the process list when you run ps -p $$
after starting it.