Last active
April 15, 2021 02:12
-
-
Save creack/68448647d726fbfd4fb7bd04ae5358dd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set +o history | |
export PS1=$'\x19' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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