How to make timeout less than 1 second?

To write more robust scripts, it is useful to "forget" the contents of the wait buffer to ensure that matching is only done on newly received input:

# this leaves expect buffer with unmatched history    
# + accumulates incoming data over 1 sec
set timeout 1
expect

# match everything in the buffer ~"forget"
expect *
# subsequent expect commands will see only what appeared since now

      

Is it possible the timeout would be less than 1 second without fixing the wait sources?

Note: set timeout 0

will not work as the first wait does not leave new incoming data in the buffer.

+3


source to share


2 answers


I'm not sure how to clear the buffer in the tcl interpreter.

I'm not sure about your use case, but I found that the most robust format for remote shell scripting expects, which is easiest to include #randomnumber

at the end of each dispatch, expect for #randomnumber

, this ensures the buffer is in sync with the last line I send to the spawned process. Your mileage will be different if the spawned process does not repeat the characters you submitted.

The pure python implementation from pexpect is great if you're okay with switching to python from the TCL implementation. Buffers work a little differently, so it will take a while. If you are executing commands on remote shells I would recommend python-remote (which I wrote)

you can use the buffer in the method you are using above,

import pexpect
spawn = pexpect.spawn(command)
stuff_inbuffer = spawn.read_nonblocking(size=100000, timeout=0.1)

      

sending random lines to sync the buffer before repeating



import random, pexpect
spawn = pexpect.spawn(command)
rand = random.random()
spawn.sendline(command + " #%s" %(rand))
spawn.expect("%s\r\n" %(rand))

      

you could either receive a waiting buffer, or wait, or read, which will wait until the buffer is large, or time out.

results = spwan.read(size=100000, timeout=10)

spawn.expect("something")
results = spawn.buffer

      

or

results = spawn.before

      

0


source


Patching expect is easy ... use negative timeouts for milliseconds (except -1, which is special):

# set timeout to 100 milliseconds
set timeout -100

      

Following if given name milliExpect.patch ... cd to expect5.45 directory and do

patch -Np1 -i milliExpect.patch.

      



Then normal (you might need to tell where tcl is in configure) ...

./configure; make; sudo make install

      

--- milliExpect.patch ----

--- expect5.45_orig/exp_event.c 2010-06-30 17:53:49.000000000 -0700
+++ expect5.45/exp_event.c  2014-09-30 12:50:18.733698995 -0700
@@ -277,6 +277,117 @@
     }
 }

+/* returns status, one of EOF, TIMEOUT, ERROR or DATA */
+/* can now return RECONFIGURE, too */
+/*ARGSUSED*/
+int exp_get_next_event_d(interp,esPtrs,n,esPtrOut,timeout,key)
+Tcl_Interp *interp;
+ExpState *(esPtrs[]);
+int n;         /* # of esPtrs */
+ExpState **esPtrOut;   /* 1st ready esPtr, not set if none */
+double timeout;        /* milliseconds */
+int key;
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
+
+    ExpState *esPtr;
+    int i; /* index into in-array */
+#ifdef HAVE_PTYTRAP
+    struct request_info ioctl_info;
+#endif
+
+    int old_configure_count = exp_configure_count;
+
+    int timerFired = FALSE;
+    Tcl_TimerToken timerToken = 0;/* handle to Tcl timehandler descriptor */
+    /* We must delete any timer before returning.  Doing so throughout
+     * the code makes it unreadable; isolate the unreadable nonsense here.
+     */
+#define RETURN(x) { \
+   if (timerToken) Tcl_DeleteTimerHandler(timerToken); \
+   return(x); \
+    }
+
+    for (;;) {
+   /* if anything has been touched by someone else, report that */
+   /* an event has been received */
+
+   for (i=0;i<n;i++) {
+       tsdPtr->rr++;
+       if (tsdPtr->rr >= n) tsdPtr->rr = 0;
+
+       esPtr = esPtrs[tsdPtr->rr];
+
+       if (esPtr->key != key) {
+       esPtr->key = key;
+       esPtr->force_read = FALSE;
+       *esPtrOut = esPtr;
+       RETURN(EXP_DATA_OLD);
+       } else if ((!esPtr->force_read) && (!expSizeZero(esPtr))) {
+       *esPtrOut = esPtr;
+       RETURN(EXP_DATA_OLD);
+       } else if (esPtr->notified) {
+       /* this test of the mask should be redundant but SunOS */
+       /* raises both READABLE and EXCEPTION (for no */
+       /* apparent reason) when selecting on a plain file */
+       if (esPtr->notifiedMask & TCL_READABLE) {
+           *esPtrOut = esPtr;
+           esPtr->notified = FALSE;
+           RETURN(EXP_DATA_NEW);
+       }
+       /*
+        * at this point we know that the event must be TCL_EXCEPTION
+        * indicating either EOF or HP ptytrap.
+        */
+#ifndef HAVE_PTYTRAP
+       RETURN(EXP_EOF);
+#else
+       if (ioctl(esPtr->fdin,TIOCREQCHECK,&ioctl_info) < 0) {
+           expDiagLog("ioctl error on TIOCREQCHECK: %s", Tcl_PosixError(interp));
+           RETURN(EXP_TCLERROR);
+       }
+       if (ioctl_info.request == TIOCCLOSE) {
+           RETURN(EXP_EOF);
+       }
+       if (ioctl(esPtr->fdin, TIOCREQSET, &ioctl_info) < 0) {
+           expDiagLog("ioctl error on TIOCREQSET after ioctl or open on slave: %s", Tcl_ErrnoMsg(errno));
+       }
+       /* presumably, we trapped an open here */
+       /* so simply continue by falling thru */
+#endif /* !HAVE_PTYTRAP */
+       }
+   }
+
+   if (!timerToken) {
+       if (timeout >= 0) {
+       timerToken = Tcl_CreateTimerHandler((int)timeout,
+           exp_timehandler,
+           (ClientData)&timerFired);
+       }
+   }
+
+   /* make sure that all fds that should be armed are */
+   for (i=0;i<n;i++) {
+       esPtr = esPtrs[i];
+       /*printf("CreateChannelHandler: %s\r\n",esPtr->name);*/
+       Tcl_CreateChannelHandler(
+                    esPtr->channel,
+                    TCL_READABLE | TCL_EXCEPTION,
+                    exp_channelhandler,
+                    (ClientData)esPtr);
+       esPtr->fg_armed = TRUE;
+   }
+
+   Tcl_DoOneEvent(0);  /* do any event */
+   
+   if (timerFired) return(EXP_TIMEOUT);
+   
+   if (old_configure_count != exp_configure_count) {
+       RETURN(EXP_RECONFIGURE);
+   }
+    }
+}
+
 /* Having been told there was an event for a specific ExpState, get it */
 /* This returns status, one of EOF, TIMEOUT, ERROR or DATA */
 /*ARGSUSED*/
--- expect5.45_orig/expect.c    2010-10-26 15:09:36.000000000 -0700
+++ expect5.45/expect.c 2014-09-30 13:01:42.693800013 -0700
@@ -41,6 +41,12 @@
 #include "tcldbg.h"
 #endif

+#define TclUtfToUniChar(str, chPtr) \
+   ((((unsigned char) *(str)) < 0xC0) ?        \
+       ((*(chPtr) = (Tcl_UniChar) *(str)), 1)  \
+       : Tcl_UtfToUniChar(str, chPtr))
+
+
 #include "retoglob.c" /* RE 2 GLOB translator C variant */

 /* initial length of strings that we can guarantee patterns can match */
@@ -123,6 +129,7 @@
    int duration;           /* permanent or temporary */
    int timeout_specified_by_flag;  /* if -timeout flag used */
    int timeout;            /* timeout period if flag used */
+   double timeout_double;  /* if timeout < -1 */
    struct exp_cases_descriptor ecd;
    struct exp_i *i_list;
 } exp_cmds[4];
@@ -559,6 +566,11 @@
            goto error;
        }
        eg->timeout_specified_by_flag = TRUE;
+       if (eg->timeout < -1) {
+           eg->timeout_double = (double)eg->timeout * -1.;
+       } else {
+           eg->timeout_double = (double)eg->timeout * 1000.;
+       }
        break;
        case EXP_ARG_NOBRACE:
        /* nobrace does nothing but take up space */
@@ -1812,6 +1824,74 @@
     return cc; 
 }

+/* returns # of bytes read or (non-positive) error of form EXP_XXX */
+/* returns 0 for end of file */
+/* If timeout is non-zero, set an alarm before doing the read, else assume */
+/* the read will complete immediately. */
+/*ARGSUSED*/
+static int
+expIRead_d( /* INTL */
+    Tcl_Interp *interp,
+    ExpState *esPtr,
+    double timeout,
+    int save_flags)
+{
+    int cc = EXP_TIMEOUT;
+    int size;
+
+    /* We drop one third when are at least 2/3 full */
+    /* condition is (size >= max*2/3) <=> (size*3 >= max*2) */
+    if (expSizeGet(esPtr)*3 >= esPtr->input.max*2)
+   exp_buffer_shuffle(interp,esPtr,save_flags,EXPECT_OUT,"expect");
+    size = expSizeGet(esPtr);
+
+#ifdef SIMPLE_EVENT
+ restart:
+
+    alarm_fired = FALSE;
+
+    if (timeout > -1) {
+       if (timeout > 0) {
+           usleep((int)timeout * 1000);
+       } else {
+           usleep(1000 * 1); /* ?? is 1 ms enough ??? */
+       }
+    }
+#endif
+
+    cc = Tcl_ReadChars(esPtr->channel, esPtr->input.newchars,
+              esPtr->input.max - esPtr->input.use,
+              0 /* no append */);
+    i_read_errno = errno;
+
+    if (cc > 0) {
+        memcpy (esPtr->input.buffer + esPtr->input.use,
+       Tcl_GetUnicodeFromObj (esPtr->input.newchars, NULL),
+       cc * sizeof (Tcl_UniChar));
+   esPtr->input.use += cc;
+    }
+
+#ifdef SIMPLE_EVENT
+    alarm(0);
+
+    if (cc == -1) {
+   /* check if alarm went off */
+   if (i_read_errno == EINTR) {
+       if (alarm_fired) {
+       return EXP_TIMEOUT;
+       } else {
+       if (Tcl_AsyncReady()) {
+           int rc = Tcl_AsyncInvoke(interp,TCL_OK);
+           if (rc != TCL_OK) return(exp_tcl2_returnvalue(rc));
+       }
+       goto restart;
+       }
+   }
+    }
+#endif
+    return cc; 
+}
+
 /*
  * expRead() does the logical equivalent of a read() for the expect command.
  * This includes figuring out which descriptor should be read from.
@@ -1932,6 +2012,126 @@
     }
     return(cc);
 }
+/*
+ * expRead_d() does the logical equivalent of a read() for the expect command.
+ * This includes figuring out which descriptor should be read from.
+ *
+ * The result of the read() is left in a spawn_id buffer rather than
+ * explicitly passing it back.  Note that if someone else has modified a buffer
+ * either before or while this expect is running (i.e., if we or some event has
+ * called Tcl_Eval which did another expect/interact), expRead will also call
+ * this a successful read (for the purposes if needing to pattern match against
+ * it).
+ */
+
+/* if it returns a negative number, it corresponds to a EXP_XXX result */
+/* if it returns a non-negative number, it means there is data */
+/* (0 means nothing new was actually read, but it should be looked at again) */
+int
+expRead_d(
+    Tcl_Interp *interp,
+    ExpState *(esPtrs[]),      /* If 0, then esPtrOut already known and set */
+    int esPtrsMax,         /* number of esPtrs */
+    ExpState **esPtrOut,       /* Out variable to leave new ExpState. */
+    double timeout,
+    int key)
+{
+    ExpState *esPtr;
+
+    int size;
+    int cc;
+    int write_count;
+    int tcl_set_flags; /* if we have to discard chars, this tells */
+           /* whether to show user locally or globally */
+
+    if (esPtrs == 0) {
+   /* we already know the ExpState, just find out what happened */
+   cc = exp_get_next_event_info(interp,*esPtrOut);
+   tcl_set_flags = TCL_GLOBAL_ONLY;
+    } else {
+   cc = exp_get_next_event_d(interp,esPtrs,esPtrsMax,esPtrOut,timeout,key);
+   tcl_set_flags = 0;
+    }
+
+    esPtr = *esPtrOut;
+
+    if (cc == EXP_DATA_NEW) {
+   /* try to read it */
+   cc = expIRead_d(interp,esPtr,timeout,tcl_set_flags);
+   
+   /* the meaning of 0 from i_read means eof.  Muck with it a */
+   /* little, so that from now on it means "no new data arrived */
+   /* but it should be looked at again anyway". */
+   if (cc == 0) {
+       cc = EXP_EOF;
+   } else if (cc > 0) {
+       /* successfully read data */
+   } else {
+       /* failed to read data - some sort of error was encountered such as
+        * an interrupt with that forced an error return
+        */
+   }
+    } else if (cc == EXP_DATA_OLD) {
+   cc = 0;
+    } else if (cc == EXP_RECONFIGURE) {
+   return EXP_RECONFIGURE;
+    }
+
+    if (cc == EXP_ABEOF) { /* abnormal EOF */
+   /* On many systems, ptys produce EIO upon EOF - sigh */
+   if (i_read_errno == EIO) {
+       /* Sun, Cray, BSD, and others */
+       cc = EXP_EOF;
+   } else if (i_read_errno == EINVAL) {
+       /* Solaris 2.4 occasionally returns this */
+       cc = EXP_EOF;
+   } else {
+       if (i_read_errno == EBADF) {
+       exp_error(interp,"bad spawn_id (process died earlier?)");
+       } else {
+       exp_error(interp,"i_read(spawn_id fd=%d): %s",esPtr->fdin,
+           Tcl_PosixError(interp));
+       if (esPtr->close_on_eof) {
+       exp_close(interp,esPtr);
+       }
+       }
+       return(EXP_TCLERROR);
+       /* was goto error; */
+   }
+    }
+
+    /* EOF, TIMEOUT, and ERROR return here */
+    /* In such cases, there is no need to update screen since, if there */
+    /* was prior data read, it would have been sent to the screen when */
+    /* it was read. */
+    if (cc < 0) return (cc);
+
+    /*
+     * update display
+     */
+
+    size = expSizeGet(esPtr);
+    if (size) write_count = size - esPtr->printed;
+    else write_count = 0;
+    
+    if (write_count) {
+   /*
+    * Show chars to user if they've requested it, UNLESS they're seeing it
+    * already because they're typing it and tty driver is echoing it.
+    * Also send to Diag and Log if appropriate.
+    */
+   expLogInteractionU(esPtr,esPtr->input.buffer + esPtr->printed, write_count);
+       
+   /*
+    * strip nulls from input, since there is no way for Tcl to deal with
+    * such strings.  Doing it here lets them be sent to the screen, just
+    * in case they are involved in formatting operations
+    */
+   if (esPtr->rm_nulls) size = expNullStrip(&esPtr->input,esPtr->printed);
+   esPtr->printed = size; /* count'm even if not logging */
+    }
+    return(cc);
+}

 /* when buffer fills, copy second half over first and */
 /* continue, so we can do matches over multiple buffers */
@@ -2363,7 +2563,12 @@

    /* "!e" means no case matched - transfer by default */
    if (!e || e->transfer) {
-       int remainder = numchars-match;
+       int remainder;
+       if (match > numchars) {
+       match = numchars;
+       eo->matchlen = match;
+       }
+       remainder = numchars-match;
        /* delete matched chars from input buffer */
        esPtr->printed -= match;
        if (numchars != 0) {
@@ -2548,6 +2753,11 @@
     time_t current_time = 0;   /* current time (when we last looked)*/
     time_t end_time;       /* future time at which to give up */

+    double start_time_total_d; /* time at beginning of this procedure */
+    double start_time_d = 0.;  /* time when restart label hit */
+    double current_time_d = 0.;    /* current time (when we last looked)*/
+    double end_time_d;         /* future time at which to give up */
+
     ExpState *last_esPtr;  /* for differentiating when multiple f */
                /* to print out better debugging messages */
     int last_case;     /* as above but for case */
@@ -2556,8 +2766,9 @@
     int key;           /* identify this expect command instance */
     int configure_count;   /* monitor exp_configure_count */

-    int timeout;       /* seconds */
+    int timeout;       /* seconds   (or milliseconds if less than -1) */
     int remtime;       /* remaining time in timeout */
+    double remtime_d;  /* remaining time in timeout (milliseconds) */
     int reset_timer;       /* should timer be reset after continue? */
     Tcl_Time temp_time;
     Tcl_Obj* new_cmd = NULL;
@@ -2585,7 +2796,9 @@

     Tcl_GetTime (&temp_time);
     start_time_total = temp_time.sec;
+    start_time_total_d = temp_time.sec * 1000. + temp_time.usec / 1000.;
     start_time = start_time_total;
+    start_time_d = start_time_total_d;
     reset_timer = TRUE;

     if (&StdinoutPlaceholder == (ExpState *)clientData) {
@@ -2641,6 +2854,7 @@
     else {
         Tcl_GetTime (&temp_time);
    start_time = temp_time.sec;
+       start_time_d = temp_time.sec * 1000. + temp_time.usec / 1000.;
     }

     if (eg.timeout_specified_by_flag) {
@@ -2669,7 +2883,9 @@
    if (reset_timer) {
        Tcl_GetTime (&temp_time);
        current_time = temp_time.sec;
+       current_time_d = temp_time.sec * 1000. + temp_time.usec / 1000.;
        end_time = current_time + timeout;
+       end_time_d = current_time_d - timeout;
    } else {
        reset_timer = TRUE;
    }
@@ -2677,12 +2893,20 @@

     /* remtime and current_time updated at bottom of loop */
     remtime = timeout;
+   remtime_d = timeout * -1.;

     for (;;) {
-   if ((timeout != EXP_TIME_INFINITY) && (remtime < 0)) {
+
+   if ((timeout > EXP_TIME_INFINITY) && (remtime < 0)) {
+       cc = EXP_TIMEOUT;
+   } else if ((timeout < EXP_TIME_INFINITY) && (remtime_d < 0.)) {
        cc = EXP_TIMEOUT;
    } else {
+       if (timeout >= EXP_TIME_INFINITY) {
        cc = expRead(interp,esPtrs,mcount,&esPtr,remtime,key);
+       } else {
+           cc = expRead_d(interp,esPtrs,mcount,&esPtr,remtime_d,key);
+       }
    }

    /*SUPPRESS 530*/
@@ -2732,7 +2956,9 @@
    if (timeout != EXP_TIME_INFINITY) {
        Tcl_GetTime (&temp_time);
        current_time = temp_time.sec;
+       current_time_d = temp_time.sec * 1000. + temp_time.usec / 1000.;
        remtime = end_time - current_time;
+       remtime_d = end_time_d - current_time_d;
    }
     }

      

-1


source







All Articles