0
votes

I have a feeling there is something obvious I'm missing, but my searches are coming up fruitless thus far.

I am trying to use a tcl/expect script to start up a tclsh interactive shell, add a procedure for easily reloading utilities for testing, and then return normal control to me.

So far, the one way I've discovered to make a tcl interactive shell "usable" is to start it with "rlwrap" so that I can use arrow keys, etc.

So I tried the following script and something about rlwrap is causing previous output to be dumped to stdout when the interact command is hit.

Is there something I can do to make this not happen?

Code:

package require Expect


puts "Tcl version   : [info tclversion]"
puts "Expect version: [exp_version]"


log_user 0
spawn -noecho rlwrap tclsh

# Create procedure to easily reload utilites after changes have been made
expect "% "
send {
   proc reload {} {
      # Procedure to reload utility source easily for testing
   }
}

# Source utilities
expect "% "
send "reload\r"

send_user "\nUse 'reload' procedure to re-source utility files\n\n"

log_user 1
interact

Output:

Tcl version   : 8.4
Expect version: 5.43.0

Use 'reload' procedure to re-source utility files


   proc reload {} {
      # Procedure to reload utility source easily for testing
   }
% reload
%  

You can that for some reason it's echoing the proc definition and the entering of the reload command. This occurs as soon as interact occurs. If I replace interact with "exit" I do not see any of this output.

Of course the output I'm hope to see would be this:

Tcl version   : 8.4
Expect version: 5.43.0

Use 'reload' procedure to re-source utility files

%  
2
Try expect "% " before log_user 1 - glenn jackman
Great suggestion Glenn, but unfortunately it didn't seem to change the output. - fnJeff
You don't send a carriage return after you send the proc. - glenn jackman
I experimented with that, and it didn't seem to matter at all. Not sure why, but it works as expected (other than the extra displayed output). The proc is correctly defined and usable once interact allows me control. - fnJeff

2 Answers

1
votes

If you don't mind to compile a small C program yourself, you could use this:

#include <tcl.h>

#ifdef WIN32
#ifdef UNICODE
#define WIN32_UNICODE
#endif
#endif

int TclSHI_Main(Tcl_Interp*);

static int g_argc;
#ifdef WIN32_UNICODE
#define Tcl_NewStringObj Tcl_NewUnicodeObj
static wchar_t*** g_argv;
void wmain(int argc, wchar_t **argv) {
#else
static char*** g_argv;
void main(int argc, char **argv) {
#endif
    g_argc = argc;
    g_argv = &argv;
    Tcl_FindExecutable(argv[0]);
    Tcl_Main(1, argv, TclSHI_Main);
}

int TclSHI_Main(Tcl_Interp* interp) {
    Tcl_Obj* lobj;
    int i;
    if (g_argc > 1) {
        Tcl_SetVar2Ex(interp, "argv0", NULL, Tcl_NewStringObj((*g_argv)[1], -1), TCL_GLOBAL_ONLY);
    }
    lobj = Tcl_NewObj();
    Tcl_IncrRefCount(lobj);
    for (i = 2; i < g_argc; i++) {
        Tcl_ListObjAppendElement(interp, lobj, Tcl_NewStringObj((*g_argv)[i], -1));
    }
    Tcl_SetVar2Ex(interp, "argv", NULL, lobj, TCL_GLOBAL_ONLY);
    Tcl_DecrRefCount(lobj);
    Tcl_SetVar2Ex(interp, "argc", NULL, Tcl_NewIntObj(g_argc - 2), TCL_GLOBAL_ONLY);
    if (g_argc > 1) {
        Tcl_Eval(interp, "source $argv0");
    }
    return TCL_OK;
}

I tested it on windows (CL) and linux (GCC).
To compile it with gcc I used gcc TclSH.c -o TclSHI -ltcl8.6
On windows I used Visual Studio.

It tells Tcl that it did not receive any arguments (Tcl_Main(1,...)), but populates the new interp with this arguments and sources the file. After this step it will always show the prompt (it never received any arguments, right?).

There is a small problem with your expect solution, if you specify any arguments, Tcl would execute that script, and never show the prompt.

Also note that I'm a novice C programmer, so this solution might not be bullet proof.

0
votes

What you want to do is to wait for an unambiguous marker that indicates that the subordinate process is ready.

# ... your script as above ...
expect "% "

#### NEW STUFF STARTS ####
send "reload;puts READY\r"
expect "READY\r"

# Note that we need to fake the prompt; c'est la vie
send_user "\nUse 'reload' procedure to re-source utility files\n\n% "

# Now start doing things!
log_user 1
interact

Or at least that works when I try with a subordinate process, but I wasn't using rlwrap in the mix so that might change things…