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?
bash
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.
sh
.bashrc
.bash_profile
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:
-i
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.
ps -p $$