Introduction
Hello, I wanted to create a small app that do fuzzy matching on what ever passed to it in the pipe. The app will take the input from the pipe, then enter terminal mode to interactive fuzzing.
The problem
The problem is that once os.Stdin
is used in pipe mode, it can’t be switched to terminal mode for interactive use.
package main
import (
"bufio"
"fmt"
"io"
"os"
"golang.org/x/term"
)
func main() {
var input []string
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input = append(input, scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(fmt.Errorf("error reading input: %w", err))
}
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer term.Restore(int(os.Stdin.Fd()), oldState)
screen := struct {
io.Reader
io.Writer
}{os.Stdin, os.Stdin}
_ = term.NewTerminal(screen, "")
}
running the application in pipe mode
λ /tmp/demo/ ls | go run .
panic: inappropriate ioctl for device
goroutine 1 [running]:
main.main()
/tmp/tt/main.go:24 +0x2d3
exit status 2
Solution
The solution is to use /dev/tty
instead of os.Stdin
for terminal interactions. /dev/tty
always refers to the terminal connected to the process, regardless of whether stdin
is being used as a pipe.
package main
import (
"bufio"
"fmt"
"io"
"os"
"golang.org/x/term"
)
func main() {
var input []string
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input = append(input, scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(fmt.Errorf("error reading input: %w", err))
}
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
panic(fmt.Errorf("failed to open /dev/tty: %w", err))
}
defer tty.Close()
oldState, err := term.MakeRaw(int(tty.Fd()))
if err != nil {
panic(err)
}
defer term.Restore(int(tty.Fd()), oldState)
screen := struct {
io.Reader
io.Writer
}{tty, tty}
t := term.NewTerminal(screen, "")
var buf [1]byte
for {
_, err := screen.Read(buf[:])
if err != nil {
panic(err)
}
if buf[0] == 3 { // ASCII code 3 is Ctrl+C
return
}
t.Write(buf[:])
}
}
now if we run the application, it no longer panics and I can type
λ /tmp/demo/ ls | go run .
I am input!
If you are interested in fuzzy app, which contains more advanced interactions feel free to look at play-fuzz project.