要编写更健壮的脚本,“忘记”expect缓冲区的内容以确保仅在最近收到的输入上进行匹配是很有用的:
# 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
是否可以在不修补预期来源的情况下使超时小于1秒?
注意:set timeout 0
将无效,因为第一个期望不会将新传入的数据保留在缓冲区中。
答案 0 :(得分:0)
我不确定如何在tcl解释器中刷新缓冲区。
我不确定您的用例,但我发现远程shell脚本编写的最可靠格式超出预期,最简单的方法是在每个脚本末尾包含一个#randomnumber
发送,期望#randomnumber
,这可以确保缓冲区同步到我发送到生成进程的最后一行。如果产生的过程没有回显您发送的字符,您的里程会有所不同。
如果你可以从TCL实现转移到python,那么来自pexpect的纯python实现是很好的。缓冲区的工作方式略有不同,因此需要一些时间来适应。如果您正在通过远程shell执行命令,我建议python-remote(我写的)
您可以通过上面使用的方法加载缓冲区
import pexpect
spawn = pexpect.spawn(command)
stuff_inbuffer = spawn.read_nonblocking(size=100000, timeout=0.1)
在repsonse之前发送随机字符串以同步缓冲区
import random, pexpect
spawn = pexpect.spawn(command)
rand = random.random()
spawn.sendline(command + " #%s" %(rand))
spawn.expect("%s\r\n" %(rand))
然后你可以使用和期望获取缓冲区,或者读取哪个将等到缓冲区大小,或者超时超时。
results = spwan.read(size=100000, timeout=10)
spawn.expect("something")
results = spawn.buffer
或
results = spawn.before
答案 1 :(得分:-1)
补丁期望很容易...使用负超时毫秒(除了-1,这是特殊的):
# set timeout to 100 milliseconds
set timeout -100
以下命名为milliExpect.patch ... cd到expect5.45目录并执行
patch -Np1 -i milliExpect.patch.
然后通常(可能必须规定tcl在配置中的位置)......
./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's 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's */
/* 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;
}
}