Don Libes' Expect: A Surprisingly Underappreciated Unix Automation Tool
blog.robertelder.org
blog.robertelder.org
If you have a slave device over RS232, expect is probably your only option. But if that remote item is running sshd, you are way better off without expect.
Hence keys silently not working without "correct" permissions, passwords not passable on the CLI, inconsistent type-yes-to-override behavior, and other user-hostile design choices.
I love openssh, but it's a real pain in the ass sometimes.
Well, I maintain that the characters in the stream aren't as good success criterion as the stack of exit codes among the tasks. So if I "ssh remotefoo /tests/the_test.sh" it is the_test.sh's job to return 0 if it succeeded and nonzero otherwise. ssh will return 0 if the remote script returns zero, so I can count on that. But if the host is busy sometimes and the_test.sh takes 91 seconds on a heavily-loaded day but 37-45 seconds normally, I might encode the wrong timeout in my expect script. Things like /etc/issue or /etc/motd or PS1 should have no bearing on my test results. But if someone writes the text "Password:" or "Username:" or some other similar content in there, my expect script might choke on it.
It results in code like this:
https://github.com/ianmiell/shutit-chef-env/blob/master/shut...
which sets up a chef server.
[1] https://github.com/ianmiell/shutit-chef-env/blob/master/shut...
https://github.com/ip2k/Mad-Science?files=1
I wrote this a long time ago and it's ugly. It totally worked and let our company find more optimal hosting configs.
"Things Only 90s SysAdmins Will Remember"
Expect is mostly a tool of last resort when a utility has silly prompts that you can't bypass by hitting it with command line options like `-y` and stuff.
Fabric itself is a poor man's badly implemented RPC for sysadmins: you send commands and receive output and result codes. Fabric wraps this in a Python API, so you can react to how your operations run.
Salt and Ansible are similar to Fabric (and "poor man's badly implemented RPC" comment applies to them as well), but they also define some higher level tasks, like "have X package installed". All three have the operations defined at the call initiator and return synchronously, when all the work is done.
Then there are CFEngine, Puppet, and Chef. Every managed server runs an agent that periodically executes some rules. CFEngine and Puppet have their own DSLs, and Chef uses general purpose language (Ruby), what I consider a bad fit. These work asynchronously, as you don't push the rules to the servers, but change them in their distribution point and wait for servers to pick up the update.
When I was a graduate Comp Sci TA in the 1990's, it was the perfect way to automate grading the Unix shell that the Systems Software class was supposed to write. Effectively, I was also writing a regression/functional testing system for the class. I explained that the Expect/Tcl script was going to be used to grade their project, and I provided the code.
One of my students said I was the best TA ever! There were a few students who complained that they shouldn't be marked off for a feature breaking, because it had worked a week or two ago. There were a few students who never figured out that the script was going to be used to grade their project, even though I provided weekly email updates explaining this.
If it's deterministic, is there a risk of someone just writing a second Expect script (instead of a solution to the programming exercise) that interacts with yours and gives the replies that you want?
Early on there was one or two cases where subtle problems came up because the public set and private set accidentally tested things slightly differently, but the TA / prof was quick to respond.
I got about 2 assignments into the class before I realized that I was being a chump. I wrote a C program that simply printed out the expected output, and submitted it. I received a passing grade on the assignment, so I took it further. I wrote a Perl script which would submit "int main(void){return 0;}", read the expected output, generate a C file that printed out that output, and submit it again.
Apparently the TA for the class never checked the submitted assignments and relied entirely on the submit program, so I was uncaught for a few weeks. My downfall was trying to make the Perl script as efficient as possible (I had extra time on my hands because I didn't have to do the C assignments!). I had run my Perl script a few dozen times while optimizing it, and apparently the TA received an email every time an assignment was submitted. He brought it up to the professor and I was called into his office. Luckily he was lenient and let me just redo the assignments :)
Also:
1. I had read much of Don Libes' Expect book. He had a unique style of writing in that book. Hard to describe, partly since it was some years ago, and partly because I can't find the words for it. But I think anyone reading the book would notice the style after reading a few pages. It was not a bad style by any means, just ... something different.
2. I've read that expect was (maybe still is) used heavily in the electronics/EDA industry, though I am either not sure or don't remember how exactly they use it, or rather, for what. And also maybe it was used heavily by GNU software for testing. Maybe someone here can confirm.
The only times I've needed to use expect was to drive weird non-scriptable telnet-based remote systems and to automate unpackaging of intentionally-broken vendor-supplied self-expanding software archives that require some level of interaction.
The point being if you want to create shortcuts to jump to specific locations in a man page or drive outdated appliances or open user-hostile zips, expect is the tool for you!
For example, this article: 'Most programmers don't know what Expect can do for them'
In some cases you can deal with it with some "printf 'line1\nline2\n'| vendor_command", but the vendor command may ignore stdin and you have to use expect.
Having to automate that way is extremely brittle, stdout and stdin are very poor and unstable APIs. For example I remember some internationalized installers which gave stdout outputs according to your locales (lesson learned, export LC_ALL=en_US.utf8 before an expect script). And if one message is added or is changed, it can break...
Personally, I tend to repackage badly shipped software (yay installanywhere crap...) in deb or rpm files, at least that way, I encounter issues before I deploy in production and I can update/remove this software easily and completely. However, it only solves part of the problem, if the configuration steps are interactive only, you've to resort to expect in production.
Expect is less and less useful, it's a last resort solution, but when you need it's extremely helpful.
You run autoexpect first to record what you want it to do on a dry run, then tidy up the generated expect script, then plug it into ansible as a raw task.
Granted this shouldn't be a problem in 2016 but some vendors are idiots. But thanks to expect my life is a little more comfortable :)
https://github.com/ianmiell/shutit/blob/master/README.md
Recently I used it to migrate an etcd cluster within OpenShift reproducibly:
https://medium.com/@zwischenzugs/migrating-an-openshift-etcd...
Thanks!
For many of the devices, there was a way to fetch the config but it required logging into the device (generally telnet) and navigating a menu to "send" the config somewhere. Using expect to do that was a fantastic way to handle it.
There's also a number of cases where you want output from a command line program "as if run by a user at a terminal". For some programs, they change the output (ie, removing color codes, etc) if the output is not going to a terminal. Expect does a fantastic job of pretending to be a terminal.
Also, it's Tcl, and I love Tcl. It's (possibly tied) at the top of my list for languages. So much fun and more flexible than anything that doesn't support lisp level macros.
Silly backronym name aside, RANCID is a very handy tool and uses expect to do a lot of the 'dirty work'.
See also the Caius framework[4].
[1] http://shop.oreilly.com/product/9781565920903.do
[2] https://www.safaribooksonline.com/library/view/exploring-exp...
pw=c3-f3-341a
spawn /opt/cisco/anyconnect/bin/vpn connect vpn.example.com
expect "Username:*" { send "myname\r" }
expect "Password:*" { send "$pw\r" }
interact- If there are alternatives, they'll usually be better
- It'll still be around when there are no alternatives
- Some tasks (e.g. "toggle foo mode") are inherently easier than others (e.g. "press up five times and hope that's enough")
- Some tasks need frequent maintenance and tweaking (e.g. as i/o formats change, program features are added/rearranged, shell environments are tweaked, etc.)
- Some tasks work so well that they're taken for granted for a decade, and it's only when you want to tweak some part that you remember how truly horrifying the implementation is ;)
good to see this old util get some new light here.
I had experience coding in two of them for DOS PC's: ProComm Plus's ASPECT language ("Pascal-ish" flavored), and a C-like language called Salt built into Telix.
I mostly used the very popular Telix for my personal use, but a law firm for whom I did some IT contract work used Procomm Plus for connecting to various databases available over dial-up (e.g. local land title registry). I wrote a bunch of ASPECT code to automate their scraping tasks: log in, look up this and that, save in a file, that sort of thing.
Maybe expect is not for me, but given the title, I would've loved to see maybe a couple paragraphs of why `expect` is underappreciated...that is, what do typical Unix programmers use instead, because they're ignorant of expect. Much easier for me to understand the perspective of ignorance :)
Essentially, expect scripts run a program, send it some input, look for some regexes on stdout, and then send more input, which may change depending on what the regex matches. And so on.
This is useful, say, if you have a package management tool that requires you to explicitly confirm every update. Or if you want to automatically drive a shell on a remote machine over SSH, and you can't use ssh -c for some reason.
You don't need it often. But when you do, it's very useful.
eg. Running the installer displays a licence agreement, then prompts to accept or decline.
Off topic, but how long has "stars on github" been an indicator of software quality/longevity/usefulness/etc.?
How many "stars on github" does Expect have?
people have just dropped tcl in favor of perl and later python and others ... I don't personally like tcl anymore, but I programmed tcl/expect in the 90's and enjoyed it a lot. I now co-maintain pexpect.
if you didn't already, I highly recommend you type 'info info' in your terminal and spend half an hour learning how to use it.
Plus, if you are an emacs user, the emacs manual is bundled with emacs in info format, and you can obviously read info documents in emacs too.
Reading the gnu emacs manual inside emacs is a game-changer.
$ info Groff | awk '$2 == "History"{f = 4};f && f--' RS= ORS='\n\n'
1.2 History
===========
'troff' can trace its origins back to a formatting program called
'RUNOFF', written by Jerry Saltzer, which ran on the CTSS (_Compatible
Time Sharing System_, a project of MIT, the Massachusetts Institute of
Technology) in the mid-sixties.(1) (*note History-Footnote-1::) The
name came from the use of the phrase "run off a document", meaning to
print it out. Bob Morris ported it to the 635 architecture and called
the program 'roff' (an abbreviation of 'runoff'). It was rewritten as
'rf' for the PDP-7 (before having UNIX), and at the same time (1969),
Doug McIllroy rewrote an extended and simplified version of 'roff' in
the BCPL programming language.
In 1971, the UNIX developers wanted to get a PDP-11, and to justify
the cost, proposed the development of a document formatting system for
the AT&T patents division. This first formatting program was a
reimplementation of McIllroy's 'roff', written by J. F. Ossanna.[Edit]: Indeed, I ran your command verbatim and obtained one more paragraph: When they needed are more flexible language [...].
You sneaky devil!
He he. Never copy/paste shell code found on the internet :-)
There are many stories of complex infrastructure being taken down by new firmware on routers or switches that change the order of parameters in a command :-)
If the system has a CLI, you write a script in that CLI; you don't write an expect script.
Sometimes systems have CLI, but only behind a remote access wall (telnet, serial, ...); then you might use expect---because there is no way to upload a script and dispatch it.
> Sometimes systems have CLI, but only behind a
> remote access wall (telnet, serial, ...); then
> you might use expect-
All managed switches, all routers, and all PDUs pretty much. $ ssh root@router uname -a
root@router's password:
Linux router 2.4.20 #1 Sun Jun 27 20:13:35 PDT 2010 mips GNU/Linux
The only use case for Expect here is typing in the password, which we can eliminate by administering some SSH keys.$ ssh root@router uname -a
Connection closed by shitty ssh implementation.
$ ssh root@router
Welcome to router!
>
brew install homebrew/dupes/expect
# find the expect/tcl path with this command
otool -L /usr/local/bin/expect
export TCLLIBPATH=/usr/local/Cellar/expect/5.45/lib
autoexpect"The name 'Expect' comes from the idea of send/expect sequences popularized by uucp, kermit and other modem control programs."
"Cause your computer to dial you back, so that you can login without paying for the call."
"Start a game (e.g., rogue) and if the optimal configuration doesn't appear, restart it (again and again) until it does, then hand over control to you."
"Connect to another network or BBS (e.g., MCI Mail, CompuServe) and automatically retrieve your mail so that it appears as if it was originally sent to your local system."
"Carry environment variables, current directory, or any kind of information across rlogin, telnet, tip, su, chgrp, etc."
During testing I kept finding edge cases. Then I eventually accounted for most of the edge cases, and gave a default answer for "unknown" edge cases. It sort of works, but I would like to have more confidence in the process.
Then again, I never felt that the problem was expect, expect does its job, and does its job reliably. The problem usually boils down to unexpected prompts (pun unintended).
I've also used pexect in Python for testing, no complains there. In fact at some point somebody tried to refactor this particular python program to use Paramiko, but decided to keep pexpect after running into some problems.
http://www.kylheku.com/cgit/tamarind/tree/auth.txr
All that's missing is the pseudo-tty manipulation and whatnot to do that sort of thing over TTY-based sessions.
Timeouts are handled by setting up timeouts at the socket level. These turn into an exception, which we catch and convert to a pattern matching failure.
This is not done for every file type; a master shell script dispatches different coloring strategies based on suffix.
I've been in love with pty's in general since the mid 1990's, and naturally found myself maintaining a highly visible python project about pty's.
love this stuff.