Skip to content

Instantly share code, notes, and snippets.

@creack
Last active April 15, 2021 02:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save creack/68448647d726fbfd4fb7bd04ae5358dd to your computer and use it in GitHub Desktop.
Save creack/68448647d726fbfd4fb7bd04ae5358dd to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
set +o history
export PS1=$'\x19'
// Example read for https://github.com/creack/pty/issues/114.
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"github.com/creack/pty"
"golang.org/x/term"
)
const PromptChar = '\x19'
func example() {
// Create arbitrary command.
c := exec.Command("/bin/bash", "--init-file", "parallel-bashrc.sh")
// Start the command with a pty.
ptmx, err := pty.Start(c)
if err != nil {
panic(err)
}
doneCh := make(chan struct{})
// Make sure to close the pty at the end.
defer func() {
<-doneCh // Wait for the SIGWINCH routine to complete before closing ptmx.
_ = ptmx.Close() // Best effort.
}()
// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
defer close(doneCh)
for range ch {
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
fmt.Printf("error resizing pty: %s\n", err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize.
defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
// Set stdin in raw mode.
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
r := bufio.NewReader(ptmx)
// Read past first prompt.
_, _ = r.ReadBytes(PromptChar) // TODO: Handle error.
buf := make([]byte, 4096)
for i := 0; i < 500; i++ {
invocation := "echo $USER"
// Write the command to the shell in the pty.
_, _ = ptmx.WriteString(invocation + "\n") // TODO: Handle error.
// Read past the echoed command.
invocBuf, _ := r.ReadBytes('\n') // TODO: Handle error.
// Make sure we discard what we expect and nothing more.
if string(invocBuf) != invocation+"\r\n" {
panic(fmt.Errorf("unexpected invoc read: %q != %q", invocBuf, invocation+"\r\n"))
}
for {
fmt.Printf("+++ about to ptmx.Read #%d\r\n", i)
n, err := r.Read(buf) // NOTE: This probably could be simplified with `r.ReadBytes(PromptChar)`.
fmt.Printf("+++ finished ptmx.Read #%d (%d bytes)\r\n", i, n)
if err != nil {
fmt.Printf("[ ERROR ] %s\r\n", err)
return
}
if n == 0 {
panic("should never happen")
}
// this is where business logic would go in the real application
fmt.Printf("### %q\r\n", strings.TrimSpace(string(buf[0:n])))
if buf[n-1] == PromptChar {
// Back at the prompt, nothing more to do.
break
}
}
}
}
func main() {
example()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment