Index: /trunk/Ohana/src/opihi/cmd.data/push.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/push.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/cmd.data/push.c	(revision 4697)
@@ -3,8 +3,28 @@
 int push (int argc, char **argv) {
   
+  int N, Unique, Replace, Key;
   Queue *queue;
 
+  Unique = FALSE;
+  if ((N = get_argument (argc, argv, "-uniq"))) {
+    remove_argument (N, &argc, argv);
+    Unique = TRUE;
+  }
+
+  Replace = FALSE;
+  if ((N = get_argument (argc, argv, "-replace"))) {
+    remove_argument (N, &argc, argv);
+    Replace = TRUE;
+  }
+
+  Key = -1;
+  if ((N = get_argument (argc, argv, "-key"))) {
+    remove_argument (N, &argc, argv);
+    Key = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
   if (argc != 3) {
-    fprintf (stderr, "USAGE: push (queue) (value)\n");
+    fprintf (stderr, "USAGE: push (queue) (value) [-key N] [-uniq] [-replace]\n");
     return (FALSE);
   }
@@ -12,5 +32,14 @@
   /* will create a queue if none exists */
   queue = CreateQueue (argv[1]);
-  PushQueue (queue, argv[2]);
+
+  if (Unique) {
+    PushQueueUnique (queue, argv[2], Key);
+  }
+  if (Replace) {
+    PushQueueReplace (queue, argv[2], Key);
+  }
+  if (!Unique && !Replace) {
+    PushQueue (queue, argv[2]);
+  }
   return (TRUE);
 }
Index: /trunk/Ohana/src/opihi/include/data.h
===================================================================
--- /trunk/Ohana/src/opihi/include/data.h	(revision 4696)
+++ /trunk/Ohana/src/opihi/include/data.h	(revision 4697)
@@ -31,4 +31,5 @@
 int FlushIOBuffer (IOBuffer *buffer);
 int ReadtoIOBuffer (IOBuffer *buffer, int fd);
+int EmptyIOBuffer (IOBuffer *buffer, int Nmax, int fd);
 void FreeIOBuffer (IOBuffer *buffer);
 
@@ -39,5 +40,8 @@
 Queue *CreateQueue (char *name);
 void PushQueue (Queue *queue, char *line);
+void PushNamedQueue (char *name, char *line);
 char *PopQueue (Queue *queue);
+void PushQueueUnique (Queue *queue, char *line, int Key);
+void PushQueueReplace (Queue *queue, char *line, int Key);
 
 /* in sort.c */
Index: unk/Ohana/src/opihi/include/inherent.h
===================================================================
--- /trunk/Ohana/src/opihi/include/inherent.h	(revision 4696)
+++ 	(revision )
@@ -1,47 +1,0 @@
-/******* List of the Inherent Commands *********/
-int cd         PROTO((int, char **));
-int echo       PROTO((int, char **));
-int exec_break PROTO((int, char **));
-int exec_continue PROTO((int, char **));
-int help       PROTO((int, char **));
-int input      PROTO((int, char **));
-int list_help  PROTO((int, char **));
-int list_vars  PROTO((int, char **));
-int macro      PROTO((int, char **));
-int output     PROTO((int, char **));
-int pwd        PROTO((int, char **));
-int quit       PROTO((int, char **));
-int run_while  PROTO((int, char **));
-int run_for    PROTO((int, char **));
-int run_if     PROTO((int, char **));
-int list       PROTO((int, char **));
-int scan       PROTO((int, char **));
-int shell      PROTO((int, char **));
-int wait       PROTO((int, char **));
-int which      PROTO((int, char **));
-
-/** mapping of the command names to command functions **/
-static Command inherent[] = {  
-  {"cd",      cd,                 "change directory"},
-  {"echo",    echo,               "type this line *"},
-  {"break",   exec_break,         "escape from function *"},
-  {"continue",exec_continue,      "next loop iteration"},
-  {"help",    help,               "get help on a function *"},
-  {"input",   input,              "read command lines from a file *"},
-  {"?",       list_help,          "list commands *"},
-  {"??",      list_vars,          "list variables *"},
-  {"list",    list,               "get variable list"},
-  {"macro",   macro,              "deal with the macros *"}, 
-  {"output",  output,             "redirect output to file"},
-  {"pwd",     pwd,                "print current working directory"},
-  {"exit",    quit,               "exit program *"}, 
-  {"quit",    quit,               "exit program *"},
-  {"while",   run_while,          "while loop"}, 
-  {"for",     run_for,            "for loop"}, 
-  {"if",      run_if,             "logical cases *"}, 
-  {"scan",    scan,               "scan line from keyboard or file to variable *"},
-  {"!",       shell,              "system call"},
-  {"exec",    shell,              "system call"},
-  {"wait",    wait,               "wait until return is typed"},
-  {"which",   which,              "show command *"}
-};
Index: /trunk/Ohana/src/opihi/include/psched.h
===================================================================
--- /trunk/Ohana/src/opihi/include/psched.h	(revision 4696)
+++ /trunk/Ohana/src/opihi/include/psched.h	(revision 4697)
@@ -23,4 +23,17 @@
 
 enum {RANGE_ABS, RANGE_DAY, RANGE_WEEK};
+
+enum {TASK_NONE, 
+      TASK_EMPTY, 
+      TASK_COMMENT, 
+      TASK_NMAX, 
+      TASK_TRANGE, 
+      TASK_END, 
+      TASK_HOST, 
+      TASK_COMMAND, 
+      TASK_PERIODS, 
+      TASK_EXIT, 
+      TASK_EXEC
+} TaskHashResults;
 
 typedef struct {
@@ -91,6 +104,6 @@
   JobMode     mode;			/* local or controller? */
 
-  int         stdout_size;		/* stdout pipe (local only) */
-  int         stderr_size;		/* stderr pipe (local only) */
+  int         stdout_size;		/* size of pending stdout buffer (controller) */
+  int         stderr_size;		/* size of pending stderr buffer (controller) */
   int         stdout_fd;		/* stdout pipe (local only) */
   int         stderr_fd;		/* stderr pipe (local only) */
@@ -110,8 +123,9 @@
 int FreeTask (Task *task);
 Task *CreateTask (char *name);
-int ValidateTask (Task *task);
+int ValidateTask (Task *task, int RequireStatic);
 int RegisterNewTask ();
 int DeleteNewTask ();
 Task *GetNewTask ();
+Task *GetActiveTask ();
 void SetTaskTimer (struct timeval *timer);
 double GetTaskTimer (struct timeval start);
Index: /trunk/Ohana/src/opihi/lib.data/IOBufferOps.c
===================================================================
--- /trunk/Ohana/src/opihi/lib.data/IOBufferOps.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/lib.data/IOBufferOps.c	(revision 4697)
@@ -1,3 +1,3 @@
-# include "pclient.h"
+# include "data.h"
 
 int InitIOBuffer (IOBuffer *buffer, int Nalloc) {
@@ -61,4 +61,19 @@
 }
 
+/* read until buffer is empty (Nmax retries) */
+int EmptyIOBuffer (IOBuffer *buffer, int Nmax, int fd) {
+
+  int i, status;
+
+  status = -1;
+  for (i = 0; (status != 0) && (i < Nmax); i++) {
+    status = ReadtoIOBuffer (buffer, fd);
+    if (status == -1) usleep (10000);
+    if (status > 0) i = 0;
+  }
+  if (status == -1) return (FALSE);
+  return (TRUE);
+}
+
 void FreeIOBuffer (IOBuffer *buffer) {
 
Index: /trunk/Ohana/src/opihi/lib.data/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/lib.data/Makefile	(revision 4696)
+++ /trunk/Ohana/src/opihi/lib.data/Makefile	(revision 4697)
@@ -36,4 +36,5 @@
 $(SDIR)/open_graph.$(ARCH).o            \
 $(SDIR)/open_image.$(ARCH).o            \
+$(SDIR)/queues.$(ARCH).o		\
 $(SDIR)/IOBufferOps.$(ARCH).o           \
 $(SDIR)/PlotVectors.$(ARCH).o
Index: /trunk/Ohana/src/opihi/lib.data/convert.c
===================================================================
--- /trunk/Ohana/src/opihi/lib.data/convert.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/lib.data/convert.c	(revision 4697)
@@ -1,4 +1,4 @@
 # include "convert.h"
-# define _XOPEN_SOURCE /* glibc2 needs this */
+# define _XOPEN_SOURCE /* glibc2 (strptime) needs this */
 # include <time.h>
 
Index: /trunk/Ohana/src/opihi/lib.data/queues.c
===================================================================
--- /trunk/Ohana/src/opihi/lib.data/queues.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/lib.data/queues.c	(revision 4697)
@@ -1,3 +1,3 @@
-# include "opihi.h"
+# include "data.h"
 
 /* These functions are somewhat fragile: we cannot do multiple CreateQueue
@@ -56,15 +56,165 @@
 }
 
+void PushNamedQueue (char *name, char *line) {
+
+  Queue *queue;
+  
+  queue = FindQueue (name);
+  if (queue == NULL) {
+    queue = CreateQueue (name);
+  }
+  PushQueue (queue, line);
+  return;
+}
+
+/* push line onto queue.  return chars create new lines */
 void PushQueue (Queue *queue, char *line) {
 
   int N;
-
+  char *p, *q;
+
+  p = line;
+  q = strchr (line, '\n');
   N = queue[0].Nlines;
-  queue[0].lines[N] = strcreate (line);
-  queue[0].Nlines ++;
-  if (queue[0].Nlines == queue[0].NLINES) {
-    queue[0].NLINES += 10;
-    REALLOCATE (queue[0].lines, char *, queue[0].NLINES);
-  }    
+  while (q != NULL) {
+    queue[0].lines[N] = strncreate (p, q - p);
+    N++;
+    CHECK_REALLOCATE (queue[0].lines, char *, queue[0].NLINES, N, 16);
+    p = q + 1;
+    q = strchr (p, '\n');
+  }    
+  if (*p) {
+    queue[0].lines[N] = strcreate (p);
+    N++;
+    CHECK_REALLOCATE (queue[0].lines, char *, queue[0].NLINES, N, 16);
+  }
+  queue[0].Nlines = N;
+  return;
+}
+
+char *ChooseKey (char *line, int Key) {
+
+  int i;
+  char *key, *p;
+
+  key = line;
+  if (Key != -1) {
+    for (i = 0; (i < Key) && (key != NULL); i++) {
+      p = nextword (key);
+      key = p;
+    }
+    key = thisword (key);
+  } 
+  return (key);
+}
+
+/* push line onto queue, skipping existing matches (optionally by key) */
+void PushQueueUnique (Queue *queue, char *line, int Key) {
+
+  int i, j, N, found;
+  char *p, *q, *key1, *key2;
+  Queue tmp;
+
+  /* init tmp queue */
+  tmp.Nlines = 0;
+  tmp.NLINES = 16;
+  ALLOCATE (tmp.lines, char *, tmp.NLINES);
+
+  /* push entries on tmp queue */
+  p = line;
+  q = strchr (line, '\n');
+  N = tmp.Nlines;
+  while (q != NULL) {
+    tmp.lines[N] = strncreate (p, q - p);
+    N++;
+    CHECK_REALLOCATE (tmp.lines, char *, tmp.NLINES, N, 16);
+    p = q + 1;
+    q = strchr (p, '\n');
+  }    
+  if (*p) {
+    tmp.lines[N] = strcreate (p);
+    N++;
+    CHECK_REALLOCATE (tmp.lines, char *, tmp.NLINES, N, 16);
+  }
+  tmp.Nlines = N;
+
+  /* add unique entries in tmp to queue */
+  for (i = 0; i < tmp.Nlines; i++) {
+    key1 = ChooseKey (tmp.lines[i], Key);
+    if (key1 == NULL) continue;
+    found = FALSE;
+    for (j = 0; !found && (j < queue[0].Nlines); j++) {
+      key2 = ChooseKey (queue[0].lines[j], Key);
+      if (key2 == NULL) continue;
+      if (strcmp (key1, key2)) {
+	if (Key != -1) free (key2);
+	continue;
+      }
+      found = TRUE;
+      if (Key != -1) free (key2);
+    }      
+    if (!found) PushQueue (queue, tmp.lines[i]);
+    if (Key != -1) free (key1);
+  }
+  for (i = 0; i < tmp.Nlines; i++) {
+    free (tmp.lines[i]);
+  } 
+  free (tmp.lines);
+  return;
+}
+
+/* push line onto queue, replacing matches (optionally by Key) */
+void PushQueueReplace (Queue *queue, char *line, int Key) {
+
+  int i, j, N, found;
+  char *p, *q, *key1, *key2;
+  Queue tmp;
+
+  /* init tmp queue */
+  tmp.Nlines = 0;
+  tmp.NLINES = 16;
+  ALLOCATE (tmp.lines, char *, tmp.NLINES);
+
+  /* push entries on tmp queue */
+  p = line;
+  q = strchr (line, '\n');
+  N = tmp.Nlines;
+  while (q != NULL) {
+    tmp.lines[N] = strncreate (p, q - p);
+    N++;
+    CHECK_REALLOCATE (tmp.lines, char *, tmp.NLINES, N, 16);
+    p = q + 1;
+    q = strchr (p, '\n');
+  }    
+  if (*p) {
+    tmp.lines[N] = strcreate (p);
+    N++;
+    CHECK_REALLOCATE (tmp.lines, char *, tmp.NLINES, N, 16);
+  }
+  tmp.Nlines = N;
+
+  /* add unique entries in tmp to queue */
+  for (i = 0; i < tmp.Nlines; i++) {
+    key1 = ChooseKey (tmp.lines[i], Key);
+    if (key1 == NULL) continue;
+    found = FALSE;
+    for (j = 0; !found && (j < queue[0].Nlines); j++) {
+      key2 = ChooseKey (queue[0].lines[j], Key);
+      if (key2 == NULL) continue;
+      if (strcmp (key1, key2)) {
+	if (Key != -1) free (key2);
+	continue;
+      }
+      queue[0].lines[j] = strcreate (tmp.lines[i]);
+      found = TRUE;
+      if (Key != -1) free (key2);
+    }      
+    if (!found) PushQueue (queue, tmp.lines[i]);
+    if (Key != -1) free (key1);
+  }
+  for (i = 0; i < tmp.Nlines; i++) {
+    free (tmp.lines[i]);
+  } 
+  free (tmp.lines);
   return;
 }
Index: /trunk/Ohana/src/opihi/lib.shell/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/lib.shell/Makefile	(revision 4696)
+++ /trunk/Ohana/src/opihi/lib.shell/Makefile	(revision 4697)
@@ -55,5 +55,4 @@
 $(SDIR)/dvomath.$(ARCH).o               \
 $(SDIR)/stack_math.$(ARCH).o		\
-$(SDIR)/queues.$(ARCH).o		\
 $(SDIR)/variable.$(ARCH).o	   	\
 $(SDIR)/opihi.$(ARCH).o
Index: /trunk/Ohana/src/opihi/pantasks/CheckJobs.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/CheckJobs.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/pantasks/CheckJobs.c	(revision 4697)
@@ -29,6 +29,6 @@
 	/* XXX this will break on 0 values in output streams */
 	if (VerboseMode()) fprintf (stderr, "job %s (%d) crash\n", job[0].task[0].name, job[0].JobID);
-	set_str_variable ("stdout", job[0].stdout.buffer);
-	set_str_variable ("stderr", job[0].stderr.buffer);
+	PushNamedQueue ("stdout", job[0].stdout.buffer);
+	PushNamedQueue ("stderr", job[0].stderr.buffer);
 	if (job[0].task[0].crash != NULL) {
 	  exec_loop (job[0].task[0].crash);
@@ -40,6 +40,6 @@
       case JOB_EXIT:
 	if (VerboseMode()) fprintf (stderr, "job %s (%d) exit\n", job[0].task[0].name, job[0].JobID);
-	set_str_variable ("stdout", job[0].stdout.buffer);
-	set_str_variable ("stderr", job[0].stderr.buffer);
+	PushNamedQueue ("stdout", job[0].stdout.buffer);
+	PushNamedQueue ("stderr", job[0].stderr.buffer);
 	/* run corresponding task[0].exit macro, if it exists */
 	macro = job[0].task[0].def;
Index: /trunk/Ohana/src/opihi/pantasks/CheckTasks.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/CheckTasks.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/pantasks/CheckTasks.c	(revision 4697)
@@ -22,4 +22,5 @@
       if (!status) continue;
     }
+    if (!ValidateTask (task, TRUE)) continue;
 
     /* construct job from task */
Index: /trunk/Ohana/src/opihi/pantasks/ControllerOps.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/ControllerOps.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/pantasks/ControllerOps.c	(revision 4697)
@@ -3,4 +3,5 @@
 # define CONNECT_TIMEOUT 300
 
+/* local static variables to hold the connection to the controller */
 static int status = FALSE;
 static int stdin_cntl, stdout_cntl, stderr_cntl;
@@ -8,8 +9,10 @@
 static IOBuffer stderr_buffer;
 
+/* test if the controller is running */
 int CheckControllerStatus () {
   return (status);
 }
 
+/* check job / get output if done */
 int CheckControllerJob (Job *job) {
 
@@ -23,4 +26,5 @@
 }
 
+/* ask controller about job status */
 int CheckControllerJobStatus (Job *job) {
 
Index: /trunk/Ohana/src/opihi/pantasks/LocalJob.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/LocalJob.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/pantasks/LocalJob.c	(revision 4697)
@@ -1,3 +1,102 @@
 # include "psched.h"
+
+/* local jobs are forked in the background 
+   we might need to limit the maximum number of local jobs.
+   should we have a queue/stack of pending local jobs, much
+   like controller? */
+
+/* update current state, drain stdout/stderr buffers */
+int CheckLocalJob (Job *job) {
+
+  int Nread;
+
+  // XXX do something useful with exit status?
+  CheckLocalJobStatus (job);
+
+  if ((job[0].state == JOB_EXIT) || (job[0].state == JOB_CRASH)) {
+    EmptyIOBuffer (&job[0].stdout, 10, job[0].stdout_fd);
+    EmptyIOBuffer (&job[0].stderr, 10, job[0].stderr_fd);
+    close (job[0].stdout_fd);
+    close (job[0].stderr_fd);
+  } else {
+    /* read stdout buffer */
+    Nread = ReadtoIOBuffer (&job[0].stdout, job[0].stdout_fd);
+    switch (Nread) {
+      case -2:  /* error in read (programming error?  system level error?) */
+	fprintf (stderr, "serious IO error\n");
+	exit (2);
+      case -1:  /* no data in pipe */
+      case 0:   /* pipe is closed, change child state? **/
+      default:  /* data in pipe */
+	break;
+    }
+  
+    /* read stderr buffer */
+    Nread = ReadtoIOBuffer (&job[0].stderr, job[0].stderr_fd);
+    switch (Nread) {
+      case -2:  /* error in read (programming error?  system level error?) */
+	fprintf (stderr, "serious IO error\n");
+	exit (2);
+      case -1:  /* no data in pipe */
+      case 0:   /* pipe is closed, change child state? **/
+      default:  /* data in pipe */
+	break;
+    }
+  }
+  return (TRUE);
+}
+
+int CheckLocalJobStatus (Job *job) {
+
+  int result, waitstatus;
+
+  /* check local job status */
+  result = waitpid (job[0].pid, &waitstatus, WNOHANG);
+  switch (result) {
+    case -1:  /* error with waitpid */
+      switch (errno) {
+	case ECHILD:
+	  fprintf (stderr, "unknown PID, not a child proc\n");
+	  fprintf (stderr, "did process already exit?  programming error?\n");
+	  job[0].state = JOB_NONE;
+	  job[0].exit_status = 0;
+	  return (FALSE);
+	case EINVAL:
+	  fprintf (stderr, "error EINVAL (waitpid): programming error\n");
+	  exit (1);
+	case EINTR:
+	  fprintf (stderr, "error EINTR (waitpid): programming error\n");
+	  exit (1);
+	default:
+	  fprintf (stderr, "unknown error for waitpid (%d): programming error\n", errno);
+	  exit (1);
+      }
+      break;
+      
+    case 0:  /* process not exited */
+      job[0].state = JOB_BUSY;
+      job[0].exit_status = 0;
+      return (TRUE);
+
+    default:
+      if (result != job[0].pid) {
+	fprintf (stderr, "waitpid error: mis-matched PID (%d vs %d).  programming error\n", result, job[0].pid);
+	exit (1);
+      }
+      if (WIFEXITED(waitstatus)) {
+	job[0].state = JOB_EXIT;
+	job[0].exit_status = WEXITSTATUS(waitstatus);
+      }
+      if (WIFSIGNALED(waitstatus)) {
+	job[0].state = JOB_CRASH;
+	job[0].exit_status = WTERMSIG(waitstatus);
+      }
+      if (WIFSTOPPED(waitstatus)) {
+	fprintf (stderr, "waitpid returns 'stopped': programming error\n");
+	exit (1);
+      }
+  }
+  return (FALSE);
+}
 
 /* this could be written a just a one-way pipe */
@@ -58,99 +157,5 @@
 }
 
-/* update current state, drain stdout/stderr buffers */
-int CheckLocalJob (Job *job) {
-
-  int Nread;
-
-  // XXX do something useful with exit status?
-  CheckLocalJobStatus (job);
-
-  if ((job[0].state == JOB_EXIT) || (job[0].state == JOB_CRASH)) {
-    EmptyIOBuffer (&job[0].stdout, job[0].stdout_fd);
-    EmptyIOBuffer (&job[0].stderr, job[0].stderr_fd);
-  } else {
-    /* read stdout buffer */
-    Nread = ReadtoIOBuffer (&job[0].stdout, job[0].stdout_fd);
-    switch (Nread) {
-      case -2:  /* error in read (programming error?  system level error?) */
-	fprintf (stderr, "serious IO error\n");
-	exit (2);
-      case -1:  /* no data in pipe */
-      case 0:   /* pipe is closed, change child state? **/
-      default:  /* data in pipe */
-	break;
-    }
-  
-    /* read stderr buffer */
-    Nread = ReadtoIOBuffer (&job[0].stderr, job[0].stderr_fd);
-    switch (Nread) {
-      case -2:  /* error in read (programming error?  system level error?) */
-	fprintf (stderr, "serious IO error\n");
-	exit (2);
-      case -1:  /* no data in pipe */
-      case 0:   /* pipe is closed, change child state? **/
-      default:  /* data in pipe */
-	break;
-    }
-  }
-  return (TRUE);
-}
-
-int CheckLocalJobStatus (Job *job) {
-
-  int result, waitstatus;
-
-  /*** if child has exited, does the pipe hang around until it is flushed? ***/
-  /*** who closes the child stdout/stderr fd? */
-
-  /* check local job status */
-  result = waitpid (job[0].pid, &waitstatus, WNOHANG);
-  switch (result) {
-    case -1:  /* error with waitpid */
-      switch (errno) {
-	case ECHILD:
-	  fprintf (stderr, "unknown PID, not a child proc\n");
-	  fprintf (stderr, "did process already exit?  programming error?\n");
-	  job[0].state = JOB_NONE;
-	  job[0].exit_status = 0;
-	  return (FALSE);
-	case EINVAL:
-	  fprintf (stderr, "error EINVAL (waitpid): programming error\n");
-	  exit (1);
-	case EINTR:
-	  fprintf (stderr, "error EINTR (waitpid): programming error\n");
-	  exit (1);
-	default:
-	  fprintf (stderr, "unknown error for waitpid (%d): programming error\n", errno);
-	  exit (1);
-      }
-      break;
-      
-    case 0:  /* process not exited */
-      job[0].state = JOB_BUSY;
-      job[0].exit_status = 0;
-      return (TRUE);
-
-    default:
-      if (result != job[0].pid) {
-	fprintf (stderr, "waitpid error: mis-matched PID (%d vs %d).  programming error\n", result, job[0].pid);
-	exit (1);
-      }
-      if (WIFEXITED(waitstatus)) {
-	job[0].state = JOB_EXIT;
-	job[0].exit_status = WEXITSTATUS(waitstatus);
-      }
-      if (WIFSIGNALED(waitstatus)) {
-	job[0].state = JOB_CRASH;
-	job[0].exit_status = WTERMSIG(waitstatus);
-      }
-      if (WIFSTOPPED(waitstatus)) {
-	fprintf (stderr, "waitpid returns 'stopped': programming error\n");
-	exit (1);
-      }
-  }
-  return (FALSE);
-}
-
+/* should this function close the fd's? */
 int KillLocalJob (Job *job) {
 
Index: /trunk/Ohana/src/opihi/pantasks/TaskOps.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/TaskOps.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/pantasks/TaskOps.c	(revision 4697)
@@ -68,5 +68,9 @@
       fprintf (stderr, "- ");
     }
-    fprintf (stderr, "%-15s %4d   %-20s\n", tasks[i][0].name, tasks[i][0].Njobs, tasks[i][0].argv[0]);
+    if (tasks[i][0].argv == NULL) {
+      fprintf (stderr, "%-15s %4d   %-20s\n", tasks[i][0].name, tasks[i][0].Njobs, "dynamic");
+    } else {
+      fprintf (stderr, "%-15s %4d   %-20s\n", tasks[i][0].name, tasks[i][0].Njobs, tasks[i][0].argv[0]);
+    }
     if (verbose) {
       fprintf (stderr, "    spawn period: %f, polling period: %f, timeout period: %f\n", 
@@ -209,15 +213,30 @@
 }
 
-int ValidateTask (Task *task) {
-
-  if (task[0].argc == 0) {
+int ValidateTask (Task *task, int RequireStatic) {
+
+  int i, hash;
+
+  /* is a static command defined? */
+  if (task[0].argc != 0) {
+    if (task[0].argv == NULL) {
+      fprintf (stderr, "task command arguments not defined (programming error)\n");
+      return (FALSE);
+    }
+    return (TRUE);
+  }
+  if (RequireStatic) {
     fprintf (stderr, "task command not defined\n");
     return (FALSE);
   }
-  if (task[0].argv == NULL) {
-    fprintf (stderr, "task command arguments not defined (programming error)\n");
-    return (FALSE);
-  }
-  return (TRUE);
+
+  /* no static command; dynamic command? */
+  if (task[0].exec != NULL) {
+    for (i = 0; i < task[0].exec[0].Nlines; i++) {
+      hash = TaskHash (task[0].exec[0].line[i]);
+      if (hash == TASK_COMMAND) return (TRUE);
+    }
+  }
+  fprintf (stderr, "task command not defined\n");
+  return (FALSE);
 }
 
@@ -250,4 +269,35 @@
 }
 
+Task *GetActiveTask () {
+  Task *task;
+  if (ActiveTask < 0) return (NULL);
+  task = tasks[ActiveTask];
+  return (task);
+}
+
+int TaskHash (char *input) {
+  
+  int hash;
+  char *command;
+
+  hash = TASK_NONE;
+
+  command = thisword (input);
+  if (command == NULL) return (TASK_EMPTY);
+
+  if (command[0] == '#')                  hash = TASK_COMMENT;
+  if (!strcasecmp (command, "END"))       hash = TASK_END;
+  if (!strcasecmp (command, "HOST"))      hash = TASK_HOST;
+  if (!strcasecmp (command, "NMAX"))      hash = TASK_NMAX;
+  if (!strcasecmp (command, "TRANGE"))    hash = TASK_TRANGE;
+  if (!strcasecmp (command, "COMMAND"))   hash = TASK_COMMAND;
+  if (!strcasecmp (command, "PERIODS"))   hash = TASK_PERIODS;
+  if (!strcasecmp (command, "TASK.EXIT")) hash = TASK_EXIT;
+  if (!strcasecmp (command, "TASK.EXEC")) hash = TASK_EXEC;
+
+  free (command);
+  return (hash);
+}
+
 /*** task timer functions ***/
 
Index: /trunk/Ohana/src/opihi/pantasks/task.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/task.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/pantasks/task.c	(revision 4697)
@@ -2,9 +2,7 @@
 # define prompt "> "
 
-enum {TASK_NONE, TASK_EMPTY, TASK_COMMENT, TASK_NMAX, TASK_TRANGE, TASK_END, TASK_HOST, TASK_COMMAND, TASK_PERIODS, TASK_EXIT, TASK_EXEC};
-
 int task (int argc, char **argv) {
 
-  int N;
+  int N, hash;
   int ThisList, status;
   char *input, *outline;
@@ -66,5 +64,6 @@
 
     stripwhite (input);
-    switch (TaskHash (input)) {
+    hash = TaskHash (input);
+    switch (hash) {
 
       case TASK_EMPTY:
@@ -78,5 +77,5 @@
 	free (input);
 	/* validate the new task: all mandatory elements defined? */ 
-	if (!ValidateTask (task)) {
+	if (!ValidateTask (task, FALSE)) {
 	  DeleteNewTask ();
 	  return (FALSE);
@@ -107,26 +106,2 @@
   return (FALSE);
 }
-
-int TaskHash (char *input) {
-  
-  int hash;
-  char *command;
-
-  hash = TASK_NONE;
-
-  command = thisword (input);
-  if (command == NULL) return (TASK_EMPTY);
-
-  if (command[0] == '#')                  hash = TASK_COMMENT;
-  if (!strcasecmp (command, "END"))       hash = TASK_END;
-  if (!strcasecmp (command, "HOST"))      hash = TASK_HOST;
-  if (!strcasecmp (command, "NMAX"))      hash = TASK_NMAX;
-  if (!strcasecmp (command, "TRANGE"))    hash = TASK_TRANGE;
-  if (!strcasecmp (command, "COMMAND"))   hash = TASK_COMMAND;
-  if (!strcasecmp (command, "PERIODS"))   hash = TASK_PERIODS;
-  if (!strcasecmp (command, "TASK.EXIT")) hash = TASK_EXIT;
-  if (!strcasecmp (command, "TASK.EXEC")) hash = TASK_EXEC;
-
-  free (command);
-  return (hash);
-}
Index: /trunk/Ohana/src/opihi/pantasks/task_command.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/task_command.c	(revision 4696)
+++ /trunk/Ohana/src/opihi/pantasks/task_command.c	(revision 4697)
@@ -14,6 +14,9 @@
   task = GetNewTask ();
   if (task == NULL) {
-    fprintf (stderr, "ERROR: not defining or running a task\n");
-    return (FALSE);
+    task = GetActiveTask ();
+    if (task == NULL) {
+      fprintf (stderr, "ERROR: not defining or running a task\n");
+      return (FALSE);
+    }
   }
 
Index: /trunk/Ohana/src/opihi/scripts/sched.pro
===================================================================
--- /trunk/Ohana/src/opihi/scripts/sched.pro	(revision 4696)
+++ /trunk/Ohana/src/opihi/scripts/sched.pro	(revision 4697)
@@ -3,7 +3,7 @@
 
 task test
-  command ls /data/eugene 
+  command ls
   periods -poll 1.0
-  periods -exec 5.0
+  periods -exec 2.0
   periods -timeout 10.0
   # trange 07:09 07:10
@@ -18,15 +18,15 @@
   end
 
+  # stdout / stderr lines on named queues
   task.exit 0
-    # note that $stdout/$stderr may contain return characters
-    # which will interfere with 'exec'
-    # push ls.list $stdout
-    echo $stdout
+    echo "task exit 0"
+    queuesize stdout
+    queuesize stderr
   end
 
   task.exit 1
-    output failure.log
-    echo $stdout
-    output stdout
+    echo "task exit 1"
+    queuesize stdout
+    queuesize stderr
   end
 
