From 03e791cbcdfc0524fadb766676ebfb5ede14b83b Mon Sep 17 00:00:00 2001 From: Florian Walther Date: Sat, 3 Oct 2026 19:10:30 +0200 Subject: [PATCH] connect stdin of external commands to the terminal Without Stdin, commands read from /dev/null, so interactive programs like 'sudo su -', vim or ssh got EOF right away. Co-Authored-By: Claude Opus 5.5 --- main.go | 8 ++++++-- main_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 2c29a93..73bc158 100644 --- a/main.go +++ b/main.go @@ -760,9 +760,13 @@ Features: // Prepare the command to execute. cmd := exec.Command(args[0], args[1:]...) - // Set the correct output device. - cmd.Stderr = os.Stderr + // Connect the command to the terminal. Without Stdin the command would + // read from /dev/null, so interactive programs (su, vim, ssh, ...) would + // get EOF right away. readline only reads from stdin while a line is + // being edited, so it does not steal input from the command. + cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr // Execute the command return the error. return cmd.Run() diff --git a/main_test.go b/main_test.go index b14220a..dee7ff4 100644 --- a/main_test.go +++ b/main_test.go @@ -1221,3 +1221,34 @@ func TestShell_SIGINTKillsOnlyChild(t *testing.T) { t.Errorf("child was not interrupted (took %v)", elapsed) } } + +func TestExecInput_PassesStdin(t *testing.T) { + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + origStdin := os.Stdin + os.Stdin = r + defer func() { + os.Stdin = origStdin + r.Close() + }() + + if _, err := w.WriteString("hello\n"); err != nil { + t.Fatal(err) + } + w.Close() + + out := filepath.Join(t.TempDir(), "out") + t.Setenv("GOSH_TEST_OUT", out) + if err := execInput(`sh -c 'read x; printf %s "$x" > "$GOSH_TEST_OUT"'`); err != nil { + t.Fatal(err) + } + got, err := os.ReadFile(out) + if err != nil { + t.Fatal(err) + } + if string(got) != "hello" { + t.Errorf("command read %q from stdin, want %q", got, "hello") + } +}