thomasrooney / gexpect Goto Github PK
View Code? Open in Web Editor NEWPure golang expect library, for easily starting and controlling subprocesses
License: MIT License
Pure golang expect library, for easily starting and controlling subprocesses
License: MIT License
Is there a easy way to read more that one line from the output ?
In the python version you can use the following to grab the whole buffer:
child.before
I'm looking for something similar.
or a way to search the buffer for a string and read it and everything else on the line .
Using the current master of gexpect with Ubuntu 14.04 and Go 1.4.2 linux/amd64, running "go test -v" hangs inside TestBiChannel:
=== RUN TestHelloWorld
--- PASS: TestHelloWorld (0.00s)
gexpect_test.go:10: Testing Hello World...
=== RUN TestDoubleHelloWorld
--- PASS: TestDoubleHelloWorld (0.00s)
gexpect_test.go:22: Testing Double Hello World...
=== RUN TestHelloWorldFailureCase
--- PASS: TestHelloWorldFailureCase (0.00s)
gexpect_test.go:42: Testing Hello World Failure case...
=== RUN TestBiChannel
(...never completes...)
Debugging a little, it seems that the "msg" variable at gexpect_test:62 gets the original "echo" twice and waiting for "echo2" never completes.
Hey there! 👋
I tried to use gexpect
to test a CLI app and encountered an issue regarding expected and actual line endings (i.e. 0xa
/0xd
). On echo 'test\n'
I was expecting to get test\n
, but unfortunately I get test\r
as a return value of the readLine
function. If my target app outputs multiline go string with \n
as a line separator, I simply can't use the same string as a test expectation since the concatenated result of the reading of all the lines will be separated with \r
s.
Here is a minimal code sample to reproduce:
package main
import (
"fmt"
"github.com/ThomasRooney/gexpect"
)
func main() {
child, err := gexpect.Spawn("echo 'test\n'")
if err != nil {
panic(err)
}
expected := "test\n"
actual, _ := child.ReadLine()
fmt.Println("Expected:")
fmt.Printf("%#v", expected)
fmt.Println()
fmt.Printf("%#v", []byte(expected))
fmt.Println("\n=========================")
fmt.Println("Actual:")
fmt.Printf("%#v", actual)
fmt.Println()
fmt.Printf("%#v", []byte(actual))
}
On my machine (MacOS Sierra 10.12.2, zsh 5.3, go 1.7.4 darwin/amd64) it results in:
Expected:
"test\n"
[]byte{0x74, 0x65, 0x73, 0x74, 0xa}
=========================
Actual:
"test\r"
[]byte{0x74, 0x65, 0x73, 0x74, 0xd}
If I change the test string in above code to test\r
the result will be:
Expected:
"test\r"
[]byte{0x74, 0x65, 0x73, 0x74, 0xd}
=========================
Actual:
"test\r\r"
[]byte{0x74, 0x65, 0x73, 0x74, 0xd, 0xd}
b6b347c seems like a regression; when the child dies it loses the pty and the pipe will close automatically.
With the forced Close, the Interact goroutines seem to be delayed past the cmd.Wait()
:
Too many goroutines running after all test(s).
3 instances of:
syscall.Syscall(...)
/usr/lib/go/src/syscall/asm_linux_amd64.s:18 +0x5
syscall.read(...)
/usr/lib/go/src/syscall/zsyscall_linux_amd64.go:783 +0x5f
syscall.Read(...)
/usr/lib/go/src/syscall/syscall_unix.go:161 +0x4d
os.(*File).read(...)
/usr/lib/go/src/os/file_unix.go:228 +0x53
os.(*File).Read(...)
/usr/lib/go/src/os/file.go:95 +0x8a
io.copyBuffer(...)
/usr/lib/go/src/io/io.go:380 +0x247
io.Copy(...)
/usr/lib/go/src/io/io.go:350 +0x64
created by github.com/coreos/etcd/vendor/github.com/thomasrooney/gexpect.(*ExpectSubprocess).Interact
/home/anthony/go/src/github.com/coreos/etcd/vendor/github.com/thomasrooney/gexpect/gexpect.go:377 +0x145
it would be great if this package could utilize an interface for the subprocess. that way, a subprocess interface could be implemented on top of other methods of executing a subprocess, such as a pure go ssh client, executing commands on a remote machine.
we use an interface for commands in our project at https://github.com/coreos/mantle/blob/master/system/exec/exec.go#L32 so something similar in gexpect would be fantastic.
我该怎么去获取到输出的所有字符串呢?就是expect去匹配的那一个命令的输出
Program hangs when I try to execute Expect with unexpected string
child, err := gexpect.Spawn("passwd test")
if err != nil {
log.Fatal(err)
}
err = child.Expect("YOU WILL NEVER FIND ME")
if err != nil {
log.Println("should print error", err) // should print error, but it never execute
}
I want to do this because there could be different results for one command. For example, ssh $hostname ls /tmp
may output the directory information of tmp
directly if the ssh public key has copied to the host, or get the prompt for the password, I found it only works well with non-interactive command.
As per subject, Wait()
may run into a subtle deadlock if the child buffer is not drained. Consider the following example:
package main
import "github.com/ThomasRooney/gexpect"
import "fmt"
func main() {
fmt.Printf("Starting...\n")
child, err := gexpect.Spawn("bash")
if err != nil {
panic(err)
}
child.Expect("$")
fmt.Printf("Got prompt..\n")
len := 8000
child.SendLine(fmt.Sprintf("for i in `seq %d`; do echo 'a'; done", len))
child.SendLine(`exit`)
fmt.Printf("Waiting for exit...\n")
child.Wait()
fmt.Printf("Done\n")
child.Close()
}
When running this, Wait()
will never return. Lowering the len
value, it will correctly return.
The problem with the example is that child output should be asynchronously drained while waiting, otherwise it can block when writing to the pty and deadlock with the parent (gexpect) as shown in strace:
$ strace -f -p ${BASH_PID}
write(1, "a\n", 2
$ strace -f -p ${GOEXPECT_PID}
waitid(P_PID, ${BASH_PID}, <unfinished ...>
This seems to be an intrinsic limitation of Wait()
, and should be properly documented similarly to https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.wait
Hey @ThomasRooney,
Just hooked up your code to do run a router maintenance script.
I have noticed that when I call ReadLine() the output is empty. If I try ReadUntil('#') it does show the output up until that point.
Am I doing something wrong?
Thanks,
Jon
Code below;
package main
import (
"fmt"
"github.com/ThomasRooney/gexpect"
)
func main() {
fmt.Printf("Starting SSH...\n")
p, err := gexpect.Spawn("ssh [email protected]")
if err != nil {
panic(err)
}
p.Expect("Password:")
p.SendLine("password123")
p.Expect("#")
p.Send("show version | include System image ")
f, err := p.ReadLine()
if err != nil {
panic(err)
}
fmt.Printf("%v\n", f) //Variable f is empty, If i replace with ReadUntil('#') it shows the string up to this point
p.Close()
}
Anyone know if this behaves on Windows ? Good old windows has some differences for this type of stuff :)
Reading the documentation and the test cases led me to believe there was no handling for the case of a timeout. But then I looked at your actual code and you do use the time.After() inside a select go idiom for a timeout option. Why not provide an example in your read me, or provide a test case?
This looks iike a very promising project. I wrote a ton of expect a long time ago and now that I'm into go, having a go version of expect is great. Amazing how few lines of go it takes to make something so useful.
Also, why not make the timeout error a global constant so the user could test the error received against it to handle the normal timeout case more cleanly?
this gexpect
package worked better than the google and netflix ones. but I have one challenge.
I'm trying to capture the response from the automation that I'm trying to do using this package.. my script has ssh
commands, and then runs a cli command which has its sub prompt. I'm able to grab all the screen text, using the Capture()
and Collect()
functions, except after reaching CLI's prompt.
However, when I use Interact()
at the end of the script, and remove the Capture()
and Collect()
calls, then I'm able to see the text from the cli console. So, the gexpect
is able to read the screen text from the CLI console, but i'm not able to grab them using the Capture()
and Collect()
functions. any suggestion on how to resolve this blocker?
Please let me know if you need any more details.
env: mac mojave
How can I send Ctrl-C to the interactive session but not the host session?
Hi,
I was testing "AsyncInteractChannels". When using:
send, receiver := child.AsyncInteractChannels()
Somehow the output looks buffered or waiting on lines '\n'. Would it be possible to create a version without buffering? So each character is passed without delay. Example code:
package main
import (
"bufio"
"fmt"
"os"
"time"
"github.com/ThomasRooney/gexpect"
)
func main() {
// open program
child, err := gexpect.Spawn("/bin/sh")
if err != nil {
panic("could not open program " + err.Error())
}
defer child.Close()
// open in/out channels
doCommand, receiver := child.AsyncInteractChannels()
doneChan := make(chan bool)
tickChan := time.NewTicker(time.Millisecond * 10000).C
// keyboard
reader := bufio.NewReader(os.Stdin)
keyboard := make(chan string)
go func(keyboard chan string) {
for {
s, _, err := reader.ReadRune()
if err != nil {
close(keyboard)
return
}
keyboard <- string(s)
}
}(keyboard)
// the loop
for {
select {
case msg, open := <-receiver:
if !open {
return
}
fmt.Printf("%s\n", msg)
case <-tickChan:
doCommand <- "echo example trigger 1000 seconds\n"
case commandline := <-keyboard:
doCommand <- commandline
case <-doneChan:
fmt.Println("Done")
return
}
}
}
Regards, Arjen
Interact tries to copy everything from PTY to both standard output and standard error in two goroutines. Isn't that racy? Sometimes contents of PTY will end up in standard output, sometimes in standard error.
Not sure what should be the fix - either remove the go io.Copy(os.Stderr, expect.f)
line or something like:
go func() {
mw := io.MultiWriter(os.Stdout, os.Stderr)
io.Copy(mw, expect.f)
}()
I suppose the former.
Hi.
$ go build -i -v -x
WORK=/tmp/go-build260147053
/home/oceanfish81/gexpect/examples
mkdir -p $WORK/b001/
cd $WORK
/home/oceanfish81/gollvm_dist/bin/llvm-goc -fgo-importcfg=/dev/null -c -x c - -o /dev/null || true
mkdir -p $WORK/b001/importcfgroot/github.com/ThomasRooney
ln -s /home/oceanfish81/go/pkg/gccgo_linux_amd64/github.com/ThomasRooney/libgexpect.a $WORK/b001/importcfgroot/github.com/ThomasRooney/libgexpect.a
cd /home/oceanfish81/gexpect/examples
/home/oceanfish81/gollvm_dist/bin/llvm-goc -c -O2 -g -m64 -fdebug-prefix-map=$WORK=/tmp/go-build -gno-record-gcc-switches -fgo-relative-import-path=/home/oceanfish81/gexpect/examples -o $WORK/b001/go.o -I $WORK/b001/importcfgroot ./ftp.go ./ping.go ./python.go ./screen.go_/home/oceanfish81/gexpect/examples
./ping.go:6:1: error: redefinition of 'main'
./ftp.go:6:1: note: previous definition of 'main' was here
./python.go:6:1: error: redefinition of 'main'
./ftp.go:6:1: note: previous definition of 'main' was here
./screen.go:7:1: error: redefinition of 'main'
./ftp.go:6:1: note: previous definition of 'main' was here
$ go version
go version go1.15.2 gollvm LLVM 12.0.0git linux/amd64
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/oceanfish81/.cache/go-build"
GOENV="/home/oceanfish81/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/oceanfish81/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/oceanfish81/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/oceanfish81/gollvm_dist"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/oceanfish81/gollvm_dist/tools"
GCCGO="/home/oceanfish81/gollvm_dist/bin/llvm-goc"
AR="ar"
CC="/usr/bin/clang"
CXX="/usr/bin/clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build393449434=/tmp/go-build -gno-record-gcc-switches -funwind-tables"
Hello @ThomasRooney
I tried your project and it's really nice but may be one problem is that each input line will be printed once as well. It looks like:
print('aaa')
pint('aaa')
raaa
print('bbb')
print('bbb')
bbb
Is it a problem or something I used incorrectly?
Best regards,
Marshall
I need to write an interactive shell using mysql. When I spawn mysql the mysql client launches beautifully and I am able to hide some commands to initialize the environment, however I loose the ability to go back through command history. WHen I hit any of the arrow keys I get strange characters.
`mysql> select user();
select user();
+----------------------+
| user() |
+----------------------+
| sduser@sdhost12 |
+----------------------+
1 row in set (0.00 sec)
mysql> ^[[A`
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.