{"id":13397078,"url":"https://github.com/google/goexpect","last_synced_at":"2025-03-13T23:32:06.440Z","repository":{"id":44379722,"uuid":"89188193","full_name":"google/goexpect","owner":"google","description":"Expect for Go","archived":true,"fork":false,"pushed_at":"2021-12-02T14:15:30.000Z","size":4523,"stargazers_count":759,"open_issues_count":19,"forks_count":136,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-01-07T16:08:05.949Z","etag":null,"topics":["automation","cli","expect","ssh"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/google.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-24T02:11:39.000Z","updated_at":"2024-12-23T06:21:38.000Z","dependencies_parsed_at":"2022-07-14T11:47:18.285Z","dependency_job_id":null,"html_url":"https://github.com/google/goexpect","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fgoexpect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fgoexpect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fgoexpect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fgoexpect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/google","download_url":"https://codeload.github.com/google/goexpect/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243500239,"owners_count":20300761,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","cli","expect","ssh"],"created_at":"2024-07-30T18:01:10.603Z","updated_at":"2025-03-13T23:32:05.972Z","avatar_url":"https://github.com/google.png","language":"Go","readme":"[![CircleCI](https://circleci.com/gh/google/goexpect.svg?style=svg)](https://circleci.com/gh/google/goexpect)\n\nThis package is an implementation of [Expect](https://en.wikipedia.org/wiki/Expect) in [Go](https://golang.org).\n\n\n## Features:\n - Spawning and controlling local processes with real PTYs.\n - Native SSH spawner.\n - Expect backed spawner for testing.\n - Generic spawner to make implementing additional Spawners simple.\n - Has a batcher for implementing workflows without having to write extra logic\n   and code.\n\n## Options\n\nAll Spawn functions accept a variadic of type expect.Option , these are used for changing\noptions of the Expecter.\n\n### CheckDuration\n\nThe Go Expecter checks for new data every two seconds as default. This can be changed by using\nthe CheckDuration `func CheckDuration(d time.Duration) Option`.\n\n### Verbose\n\nThe Verbose option is used to turn on/off verbose logging for Expect/Send statements.\nThis option can be very useful when troubleshooting workflows since it will log every interaction\nwith the device.\n\n### VerboseWriter\n\nThe VerboseWriter option can be used to change where the verbose session logs are written.\nUsing this option will start writing verbose output to the provided io.Writer instead of the log default.\n\nSee the [ExampleVerbose](https://github.com/google/goexpect/blob/5c8d637b0287a2ae7bb805554056728c453871e4/expect_test.go#L585) code for an example of how to use this. \n\n### NoCheck\n\nThe Go Expecter periodically checks that the spawned process/ssh/session/telnet etc. session is alive.\nThis option turns that check off.\n\n### DebugCheck\n\nThe DebugCheck option adds debugging to the alive Check done by the Expecter, this will start logging information\nevery time the check is run. Can be used for troubleshooting and debugging of Spawners.\n\n### ChangeCheck\n\nThe ChangeCheck option makes it possible to replace the Spawner Check function with a brand new one.\n\n### SendTimeout\n\nThe SendTimeout set timeout on the `Send` command, without timeout the `Send` command will wait forewer for the expecter process.\n\n### BufferSize\n\nThe BufferSize option provides a mechanism to configure the client io buffer size in bytes.\n\n## Basic Examples\n\n### networkbit.ch\n\nAn [article](http://networkbit.ch/golang-regular-expression/) with some examples was written about goexpect on [networkbit.ch](http://networkbit.ch). \n\n### The [Wikipedia Expect](https://en.wikipedia.org/wiki/Expect) examples.\n\n#### Telnet\n\nFirst we try to replicate the Telnet example from wikipedia as close as possible.\n\nInteraction:\n\n```diff\n+ username:\n- user\\n\n+ password:\n- pass\\n\n+ %\n- cmd\\n\n+ %\n- exit\\n\n```\n\n*Error checking was omitted to keep the example short*\n\n```\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/google/goexpect\"\n\t\"github.com/google/goterm/term\"\n)\n\nconst (\n\ttimeout = 10 * time.Minute\n)\n\nvar (\n\taddr = flag.String(\"address\", \"\", \"address of telnet server\")\n\tuser = flag.String(\"user\", \"\", \"username to use\")\n\tpass = flag.String(\"pass\", \"\", \"password to use\")\n\tcmd  = flag.String(\"cmd\", \"\", \"command to run\")\n\n\tuserRE   = regexp.MustCompile(\"username:\")\n\tpassRE   = regexp.MustCompile(\"password:\")\n\tpromptRE = regexp.MustCompile(\"%\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tfmt.Println(term.Bluef(\"Telnet 1 example\"))\n\n\te, _, err := expect.Spawn(fmt.Sprintf(\"telnet %s\", *addr), -1)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer e.Close()\n\n\te.Expect(userRE, timeout)\n\te.Send(*user + \"\\n\")\n\te.Expect(passRE, timeout)\n\te.Send(*pass + \"\\n\")\n\te.Expect(promptRE, timeout)\n\te.Send(*cmd + \"\\n\")\n\tresult, _, _ := e.Expect(promptRE, timeout)\n\te.Send(\"exit\\n\")\n\n\tfmt.Println(term.Greenf(\"%s: result: %s\\n\", *cmd, result))\n}\n\n```\n\nIn essence to run and attach to a process the `expect.Spawn(\u003ccmd\u003e,\u003ctimeout\u003e)` is used.\nThe spawn returns an Expecter `e` that can run `e.Expect` and `e.Send` commands to match information\nin the output and Send information in.\n\n*See the https://github.com/google/goexpect/blob/master/examples/newspawner/telnet.go  example for a slightly more fleshed out version*\n\n#### FTP\n\nFor the FTP example we use the expect.Batch for the following interaction.\n\n```diff\n+ username:\n- user\\n\n+ password:\n- pass\\n\n+ ftp\u003e\n- prompt\\n\n+ ftp\u003e\n- mget *\\n\n+ ftp\u003e'\n- bye\\n\n```\n\n*ftp_example.go*\n\n```\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/google/goexpect\"\n\t\"github.com/google/goterm/term\"\n)\n\nconst (\n\ttimeout = 10 * time.Minute\n)\n\nvar (\n\taddr = flag.String(\"address\", \"\", \"address of telnet server\")\n\tuser = flag.String(\"user\", \"\", \"username to use\")\n\tpass = flag.String(\"pass\", \"\", \"password to use\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tfmt.Println(term.Bluef(\"Ftp 1 example\"))\n\n\te, _, err := expect.Spawn(fmt.Sprintf(\"ftp %s\", *addr), -1)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer e.Close()\n\n\te.ExpectBatch([]expect.Batcher{\n\t\t\u0026expect.BExp{R: \"username:\"},\n\t\t\u0026expect.BSnd{S: *user + \"\\n\"},\n\t\t\u0026expect.BExp{R: \"password:\"},\n\t\t\u0026expect.BSnd{S: *pass + \"\\n\"},\n\t\t\u0026expect.BExp{R: \"ftp\u003e\"},\n\t\t\u0026expect.BSnd{S: \"bin\\n\"},\n\t\t\u0026expect.BExp{R: \"ftp\u003e\"},\n\t\t\u0026expect.BSnd{S: \"prompt\\n\"},\n\t\t\u0026expect.BExp{R: \"ftp\u003e\"},\n\t\t\u0026expect.BSnd{S: \"mget *\\n\"},\n\t\t\u0026expect.BExp{R: \"ftp\u003e\"},\n\t\t\u0026expect.BSnd{S: \"bye\\n\"},\n\t}, timeout)\n\n\tfmt.Println(term.Greenf(\"All done\"))\n}\n\n```\n\nUsing the expect.Batcher makes the standard Send/Expect interactions more compact and simpler to write.\n\n#### SSH\n\nWith the SSH login example we test out the [expect.Caser](https://github.com/google/goexpect/blob/7f68e6ee0bc89860ff53a5c0d50bcfae61853506/expect.go#L388-L397)\nand the [Case Tags](https://github.com/google/goexpect/blob/7f68e6ee0bc89860ff53a5c0d50bcfae61853506/expect.go#L324-L335).\n\nAlso for this we'll use the Go Expect native [SSH Spawner](https://github.com/google/goexpect/blob/7f68e6ee0bc89860ff53a5c0d50bcfae61853506/expect.go#L872-L879)\ninstead of spawning a process.\n\n\nInteraction:\n\n```diff\n+ \"Login: \"\n- user\n+ \"Password: \"\n- pass1\n+ \"Wrong password\"\n+ \"Login\"\n- user\n+ \"Password: \"\n- pass2\n+ router#\n```\n\n*ssh_example.go*\n\n```\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/ssh\"\n\n\t\"google.golang.org/grpc/codes\"\n\n\t\"github.com/google/goexpect\"\n\t\"github.com/google/goterm/term\"\n)\n\nconst (\n\ttimeout = 10 * time.Minute\n)\n\nvar (\n\taddr  = flag.String(\"address\", \"\", \"address of telnet server\")\n\tuser  = flag.String(\"user\", \"user\", \"username to use\")\n\tpass1 = flag.String(\"pass1\", \"pass1\", \"password to use\")\n\tpass2 = flag.String(\"pass2\", \"pass2\", \"alternate password to use\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tfmt.Println(term.Bluef(\"SSH Example\"))\n\n\tsshClt, err := ssh.Dial(\"tcp\", *addr, \u0026ssh.ClientConfig{\n\t\tUser:            *user,\n\t\tAuth:            []ssh.AuthMethod{ssh.Password(*pass1)},\n\t\tHostKeyCallback: ssh.InsecureIgnoreHostKey(),\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"ssh.Dial(%q) failed: %v\", *addr, err)\n\t}\n\tdefer sshClt.Close()\n\n\te, _, err := expect.SpawnSSH(sshClt, timeout)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer e.Close()\n\n\te.ExpectBatch([]expect.Batcher{\n\t\t\u0026expect.BCas{[]expect.Caser{\n\t\t\t\u0026expect.Case{R: regexp.MustCompile(`router#`), T: expect.OK()},\n\t\t\t\u0026expect.Case{R: regexp.MustCompile(`Login: `), S: *user,\n\t\t\t\tT: expect.Continue(expect.NewStatus(codes.PermissionDenied, \"wrong username\")), Rt: 3},\n\t\t\t\u0026expect.Case{R: regexp.MustCompile(`Password: `), S: *pass1, T: expect.Next(), Rt: 1},\n\t\t\t\u0026expect.Case{R: regexp.MustCompile(`Password: `), S: *pass2,\n\t\t\t\tT: expect.Continue(expect.NewStatus(codes.PermissionDenied, \"wrong password\")), Rt: 1},\n\t\t}},\n\t}, timeout)\n\n\tfmt.Println(term.Greenf(\"All done\"))\n}\n```\n\n### Generic Spawner\n\nThe Go Expect package supports adding new Spawners with the `func SpawnGeneric(opt *GenOptions, timeout time.Duration, opts ...Option) (*GExpect, \u003c-chan error, error)`\nfunction. \n\n*telnet spawner*\n\nFrom the [newspawner](https://github.com/google/goexpect/blob/master/examples/newspawner/telnet.go) example.\n\n```\nfunc telnetSpawn(addr string, timeout time.Duration, opts ...expect.Option) (expect.Expecter, \u003c-chan error, error) {\n\tconn, err := telnet.Dial(network, addr)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tresCh := make(chan error)\n\n\treturn expect.SpawnGeneric(\u0026expect.GenOptions{\n\t\tIn:  conn,\n\t\tOut: conn,\n\t\tWait: func() error {\n\t\t\treturn \u003c-resCh\n\t\t},\n\t\tClose: func() error {\n\t\t\tclose(resCh)\n\t\t\treturn conn.Close()\n\t\t},\n\t\tCheck: func() bool { return true },\n\t}, timeout, opts...)\n}\n```\n\n### Fake Spawner\n\nThe Go Expect package includes a Fake Spawner `func SpawnFake(b []Batcher, timeout time.Duration, opt ...Option) (*GExpect, \u003c-chan error, error)`. \nThis is expected to be used to simplify testing and faking of interactive workflows.\n\n*Fake Spawner*\n\n```\n// TestExpect tests the Expect function.\nfunc TestExpect(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tfail    bool\n\t\tsrv     []Batcher\n\t\ttimeout time.Duration\n\t\tre      *regexp.Regexp\n\t}{{\n\t\tname: \"Match prompt\",\n\t\tsrv: []Batcher{\n\t\t\t\u0026BSnd{`\nPretty please don't hack my chassis\n\nrouter1\u003e `},\n\t\t},\n\t\tre:      regexp.MustCompile(\"router1\u003e\"),\n\t\ttimeout: 2 * time.Second,\n\t}, {\n\t\tname: \"Match fail\",\n\t\tfail: true,\n\t\tre:   regexp.MustCompile(\"router1\u003e\"),\n\t\tsrv: []Batcher{\n\t\t\t\u0026BSnd{`\nWelcome\n\nRouter42\u003e`},\n\t\t},\n\t\ttimeout: 1 * time.Second,\n\t}}\n\n\tfor _, tst := range tests {\n\t\texp, _, err := SpawnFake(tst.srv, tst.timeout)\n\t\tif err != nil {\n\t\t\tif !tst.fail {\n\t\t\t\tt.Errorf(\"%s: SpawnFake failed: %v\", tst.name, err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tout, _, err := exp.Expect(tst.re, tst.timeout)\n\t\tif got, want := err != nil, tst.fail; got != want {\n\t\t\tt.Errorf(\"%s: Expect(%q,%v) = %t want: %t , err: %v, out: %q\", tst.name, tst.re.String(), tst.timeout, got, want, err, out)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n```\n\n*Disclaimer: This is not an official Google product.*\n","funding_links":[],"categories":["Go"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fgoexpect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogle%2Fgoexpect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fgoexpect/lists"}