Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 128 lines (111 sloc) 3.69 KB
#!/usr/bin/env expect
# feeds the commands in the given input file (or stdin) to the
# subsequent program then turns interaction over to the user
package require Tcl 8.5
# for equivalent of mktemp(1); requires tcllib package
package require fileutil
set prog_name [lindex [split $argv0 "/"] end]
if {[llength $argv] < 2} {
puts stderr "Usage: $prog_name file|- command \[args ..]"
exit 64
# somewhat high timeout to help spot when feed is not properly
# feeding things
set timeout 3
# avoid doubled lines whereby both this program and what lines being fed
# to print the same data
stty -echo
proc warn {msg} {
global prog_name
puts stderr "$prog_name: $msg"
proc die {{msg ""}} {
if {$msg ne ""} { warn $msg }
exit 1
# first argument is where the commands come from: stdin or from a file?
set argv [lassign $argv input_file]; # it's shift, Jim, but not as we know it
if {$input_file eq "-"} {
# copy stdin into a tmp file and then hopefully restore access to tty
set tmpfile [::fileutil::tempfile "$prog_name-"]
trap {file delete $tmpfile; puts ""; exit 1} {SIGHUP SIGINT SIGPIPE SIGTERM}
if {[catch {set tmpfh [open $tmpfile w]} err]} {
file delete $tmpfile
die $err
while {[gets stdin line] >= 0} {
puts $tmpfh $line
if {[catch {close $tmpfh} err]} {
file delete $tmpfile
die $err
close stdin
open /dev/tty r+
if {[catch {set input_fh [open $tmpfile r]} err]} {
file delete $tmpfile
die $err
} else {
# regular file input - though without any "| program" shenanigans
set input_file [string trimleft $input_file "| "]
if {[catch {set input_fh [open $input_file r]} err]} {
die $err
set tmpfile ""
if {[catch {spawn -noecho {*}$argv} err]} {
if {$tmpfile ne ""} { file delete $tmpfile }
die $err
# control+z will background feed (and whatever being run) while for
# control+c that is passed through to the program being run;
# control+\ remains a good way to abort when things go really awry
set CONTROL_C \003
set CONTROL_Z \032
trap {puts ""; exit 1} {SIGHUP SIGPIPE SIGTERM}
trap {send $CONTROL_C} SIGINT
proc interactionby {} {
global CONTROL_C CONTROL_Z input_fh tmpfile
stty echo
if {$tmpfile ne ""} { file delete $tmpfile }
if {[catch {close $input_fh} err]} { die $err }
interact {
$CONTROL_C {send -raw $CONTROL_C}
-reset $CONTROL_Z {exec kill -STOP [pid]}
# and here we try to figure out hopefully a 90% solution for feeding of
# varied inputs to varied REPL
set tocall [lindex $argv 0]
# ... except that buck now gets passed to the user to write (there's an
# example over in my dotfiles repo)
set feedrc [lindex [array get env FEEDRC] end]
if {$feedrc eq ""} { set feedrc $env(HOME)/.feedrc }
catch {source $feedrc} cresult coptions
# ... unless they screw that part up
if {[dict get $coptions -code] != 0} {
warn [dict get $coptions -errorinfo]
die "feed: could not source $feedrc"
# ... though do need a fallback if nothing created the necessary proc
if {[info commands dosomethingwith] eq ""} {
# assume only leading # as comments and send the rest through;
# probably adequate for shell scripts and doubtless other things
# though does not wait for any prompt
proc dosomethingwith {line} {
if {[regexp {^[#]} $line]} { return }
send -- "$line\r"
set terminate 0
while {[gets $input_fh line] >= 0} {
dosomethingwith $line
if {$terminate} { break }
# and hand REPL over to the bluberous fleshy mandibles of the user
# (user .feedrc might call this directly e.g. if error condition in REPL
# demands an immediate interact)