Capturing Command Execution / Getting Terminal Cursor Position

1,216 views
Skip to first unread message

Seth Ammons SG

unread,
Nov 27, 2014, 11:31:55 PM11/27/14
to golan...@googlegroups.com
Hey Gophers!

I am trying to get the current position of the cursor in the terminal. I found examples of doing it in bash and perl, but I can't get it to work in Go. 

Here is a boiled down version:

When I run that on my machine, I get nothing. However, if I un-comment the alternative commands (L19-20), then I get output (so the pipe technique should be working; my command must be wrong or the output is not going to stdout). 
I'm not sure if I am using cmd.Exec improperly or what. Can anyone help me figure out how to either get that command to work or an alternative way of getting the current cursor position in the terminal? 

Thanks!!

Seth Ammons SG

unread,
Nov 28, 2014, 11:50:51 AM11/28/14
to golan...@googlegroups.com
I realized that my implementation was flawed; I thought I needed to pipe output. In the terminal:

$ echo -e "\033[6n" ; read -dR

The read is just helping with the presentation of the stdout of the echo. So the pipe solution I linked is invalid. However, when I try to:

c1 := exec.Command("echo", "-e", "foo")

I get "-e foo" as output. I should only get "foo". I think that is the crux of my issue. 

Seth Ammons SG

unread,
Nov 28, 2014, 1:09:50 PM11/28/14
to golan...@googlegroups.com
I'm getting closer.


While that does not work on the playground, it works in my terminal. I needed to read in stdin to get the values I need.

However! There is still an issue. This implementation requires that I hit 'enter' in the terminal to finish reading in the stdin. How can I get the stdin to not need my interaction on ReadSlice()?

package main

import (
"bufio"
"bytes"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
)

func main() {
// $ echo -e "\033[6n" | read -dR

cmd := exec.Command("echo", "-e", fmt.Sprintf("%c[6n", 27))
randomBytes := &bytes.Buffer{}
cmd.Stdout = randomBytes

// Start command asynchronously
_ = cmd.Start()

// capture keyboard output from echo command
reader := bufio.NewReader(os.Stdin)
cmd.Wait()

// by printing the command output, we are triggering input
fmt.Print(randomBytes)
text, _ := reader.ReadSlice('R') // how to get this to not require manual newline?

// check for the desired output
if strings.Contains(string(text), ";") {
re := regexp.MustCompile(`\d+;\d+`)
line := re.FindString(string(text))
fmt.Printf("works! line,Col: %s\n", line)

} else {
fmt.Println("it does not work. womp womp.")
}
}



On Thursday, November 27, 2014 8:31:55 PM UTC-8, Seth Ammons SG wrote:

Diddymus

unread,
Nov 28, 2014, 3:12:31 PM11/28/14
to golan...@googlegroups.com
Hi Seth,

You need to put the terminal into raw mode rather than cooked mode. Easy way is to 'stty raw' before running your example and then 'stty -raw' to go back to cooked mode. Tested with you example in an XTerm.

Seth Ammons SG

unread,
Nov 28, 2014, 4:38:24 PM11/28/14
to golan...@googlegroups.com
Thanks Diddymus,
That looks like I need to run those commands before and after my code in the termina ($ stty raw; go run main.go; stty -raw;) and that works. Do you know how I can set those to run from inside my app? I tried to use cmd.Exec() on it, but I get an exit status of 1 on stty. Thanks again!

Matt Harden

unread,
Nov 28, 2014, 4:43:29 PM11/28/14
to Seth Ammons SG, golan...@googlegroups.com
stty accesses the terminal on stdin, so if you use cmd:=exec.Command("stty", "raw"), make sure you set cmd.Stdin = os.Stdin before calling cmd.Run(). Otherwise Run() will run set stdin for the command to /dev/null.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Dave Cheney

unread,
Nov 28, 2014, 7:09:51 PM11/28/14
to golan...@googlegroups.com
Shameless plug, github.com/pkg/term. It is designed for serial devices but should work for your purposes, use /dev/tty as the device.

Seth Ammons SG

unread,
Nov 28, 2014, 8:07:01 PM11/28/14
to golan...@googlegroups.com, seth....@sendgrid.com
That was the ticket! Thanks Matt!
For anyone who wants the complete example: http://play.golang.org/p/kcMLTiDRZY

Seth Ammons SG

unread,
Nov 28, 2014, 8:08:54 PM11/28/14
to golan...@googlegroups.com
Thanks Dave! Yet another piece of code you've produced that I can learn from :)

Lars Seipel

unread,
Nov 29, 2014, 9:30:30 AM11/29/14
to Seth Ammons SG, golan...@googlegroups.com
On Fri, Nov 28, 2014 at 05:07:00PM -0800, Seth Ammons SG wrote:
> That was the ticket! Thanks Matt!
> For anyone who wants the complete
> example: http://play.golang.org/p/kcMLTiDRZY

Also, why execute echo at all? You can just os.Stdout.Write (or
fmt.Printf…) the same thing.

Seth Ammons SG

unread,
Nov 29, 2014, 9:57:51 AM11/29/14
to golan...@googlegroups.com, seth....@sendgrid.com
Thanks Lars! Sometimes I find myself glued to trying to do something one way, and forget to re-evaluate. Printing directly is _way_ more sane of an approach. 

Nick Craig-Wood

unread,
Dec 1, 2014, 10:25:53 AM12/1/14
to Seth Ammons SG, golan...@googlegroups.com
Depending on how much of a text user interface you want to make, you
might find termbox useful which abstracts that messy terminal stuff away
and just works on Windows, Mac and Linux.

https://github.com/nsf/termbox-go

--
Nick Craig-Wood <ni...@craig-wood.com> -- http://www.craig-wood.com/nick
Reply all
Reply to author
Forward
0 new messages