小能豆

Execute inner shell and send/capture input/output of both shells in Go

go

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!


阅读 81

收藏
2023-12-18

共1个答案

小能豆

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.

2023-12-18