[comp.os.minix] Bourne shell with command history

gcs@nucleus.mi.org (Gary C. Strauss) (04/06/90)

Here is a modification to the standard Minix Bourne shell that keeps a small,
20 commands exceeding 4 characters, history and which allows command editing
using editing keys similar to those in elle.  It is 56k so wont impact small
memory machines excessivly.  This modification works like the history and
editing in MS-DOS products ced and 4dos and like the VMS command editing and
recall.  Enjoy!
		
				Gary

------------------------------ cut here -------------------------------
echo x - Makefile
sed '/^X/s///' > Makefile << '/'
XCFLAGS=-D_POSIX_SOURCE
Xl=/lib
X
Xshobj = sh1.s sh2.s sh3.s sh4.s sh5.s sh6.s hist.s
Xshh:	$(shobj) 
X	@cc -i -o shh -T.  $(shobj)
X	@chmem =22000 shh
X
Xclean:	
X	@rm -f *.bak *.s shh
X
Xhist.s:	hist.c hist.h
X
X
X
/
echo x - Readme
sed '/^X/s///' > Readme << '/'
XCommand:     shh - the standard Minix Bourne shell with command history
XSyntax:      shh
XFlags:       same as sh
XDescription: 
X	Shh is the standard Minix Bourne shell with a 20 command history
Xbuilt into it.  This allows editing of a command as it is entered by using
Xedit commands and keys similar to those used in Elle.  There are two 
Xbuilt in commands, shellver and history.  Shellver will display the current
Xversion of the shh shell while history will display the entire 20 command
Xhistory stack.  The following itemizes the special keys that are active in
Xshh:
X
X         arrows, left and right   - allow movement within the current 
X                                    command
X         arrows, up and down      - allow commands to be selected from
X                                    the history stack
X         ^a                       - move to the first character of the
X                                    current command
X         ^e                       - move to the last character of the
X                                    current command
X         ^i                       - toggle insert and overstrike modes
X         ^k                       - erase the current command
X         ^r n                     - make the command number n in the history
X                                    stack the current command
X         backspace                - delete the character preceeding the
X                                    cursor
X         delete                   - delete the character under the cursor
X         ^d                       - exit the shh shell
X         enter/return             - execute the current command
X
XIf a partial command is entered before the up or down arrow keys are used only
Xcommands that match the specified partial command will be selected.
X
XINSTALLATION
X
Xshh is delivered as cdifs to the version 1.5.5 sh plus four new files.  The 
Xfiles are:
X	1. Readme     - this document
X	2. Makefile   - make file for shh
X	3. hist.h     - definitions and configuration information for shh
X	4. hist.c     - the main history processing code
X	5. sh1.c.cdif - changes to sh1.c
X	6. sh2.c.cdif - changes to sh2.c
X	7. sh5.c.cdif - changes to sh3.c
X
XTo create a shh shell.
X	1. create a shh directory
X	2. copy sh.h and sh?.c from the sh directory into the new shh dir
X	3. copy the unshared shh files into the new shh dir
X	4. patch sh1.c, sh2.c and sh5.c
X	5. make
X
XThe shh that is created can then be used as your primary shell by modifying
Xthe /etc/passwd file entry to look like the following:
X      
X       gary::9:10:Gary C. Strauss:/usr/guest/gary:/usr/local/bin/shh
X                                                  ^                 ^
X                                                  |                 |
X                                                  |new part of entry|
X
XGary C. Strauss     gcs@nucleus.mi.org
/
echo x - hist.c
sed '/^X/s///' > hist.c << '/'
X#include <sgtty.h>
X#include "hist.h"
X
X#define FOREVER 1
X#define TRUE	1
X#define FALSE	0
X#define ON	1
X#define OFF	0
X#define FD	0
X
X/* global data */
Xstruct command cmd_array[CMD_MAX], *cur_command;
X
Xstruct sgttyb args;
Xstruct tchars tch;
X
Xint   col, ins_flag, noioreq;
Xchar *cur_loc, *optr, hprompt[81], hold_cmd[LINE_MAX];
X
Xhist_io(cadr)
Xchar *cadr;
X{
X    struct command *next_command, *r_command;
X    int   c, i;
X    char  nbuf[40], *nptr;
X
X    /* can we just return a character from the buffer? */
X    if (noioreq)
X    {
X	if (*optr == NULL || *optr == '\r')
X	{
X	    noioreq = FALSE;
X	    *cadr = '\n';
X	    return 1;
X	}
X	*cadr = *optr++;
X	return 1;
X    }
X
X    setprompt();
X
X    /* get current tty parameters */
X    ioctl(FD, TIOCGETP, &args);
X    ioctl(FD, TIOCGETC, &tch);
X
X    /* set tty device for raw input mode */
X    rawmode(ON);
X
X    col = 1 + strlen(hprompt);
X    CLEAR_LINE;
X    r_command = next_command = cur_command;
X    /* main processing loop */
X    display(cur_command);
X    c = readkey();
X    while (ON)
X    {
X	switch (c)
X	{
X	case DOWN:
X	case UP:
X	    strcpy(hold_cmd, cur_command->text);
X	    r_command = cur_command;
X	    while (c == UP || c == DOWN)
X	    {
X		if (c == UP)
X		{
X		    r_command = r_command->prev;
X		}
X		else
X		{
X		    r_command = r_command->next;
X		}
X		if (!strncmp(r_command->text,
X			     hold_cmd,
X			     strlen(hold_cmd)))
X		{
X		    strcpy(cur_command->text,
X			   r_command->text);
X		    cur_loc = cur_command->text;
X		    col = strlen(hprompt) + 1 + strlen(cur_loc);
X		    cur_loc += strlen(cur_loc);
X		    CLEAR_LINE;
X		    display(cur_command);
X		    c = readkey();
X		}
X	    }
X	    break;
X	case CMDRECALL:
X	    nptr = nbuf;
X	    prs("Command number? ");
X	    col += 16;
X	    while ((*nptr = readkey()) != '\r')
X	    {
X		putc(*nptr++);
X	    }
X	    *nptr = NULL;
X	    if ((i = atoi(nbuf)) < 0 || i >= CMD_MAX)
X		error("Invalid command number", "");
X	    r_command = &cmd_array[i];
X	    strcpy(cur_command->text, r_command->text);
X	    cur_loc = cur_command->text;
X	    col = strlen(hprompt) + 1 + strlen(cur_loc);
X	    cur_loc += strlen(cur_loc);
X	    CLEAR_LINE
X		display(cur_command);
X	    c = readkey();
X	    break;
X	case '\n':
X	case '\r':
X	    optr = cur_command->text;
X	    if (strlen(optr) == 0)
X	    {
X		c = readkey();
X		break;
X	    }
X	    if ((strlen(optr) < CMD_MIN_SIZE) ||
X		(strcmp(optr, "shellver") == 0) ||
X		(strcmp(optr, "history") == 0))
X	    {
X		strcpy(hold_cmd, optr);
X		optr = hold_cmd;
X	    }
X	    else
X	    {
X		next_command = cur_command->next;
X		cur_command = next_command;
X	    }
X	    cur_loc = cur_command->text;
X	    *cur_loc = NULL;
X	    col = strlen(hprompt) + 1;
X
X	    /* check for local commands */
X	    if (!strcmp(optr, "shellver"))
X	    {
X		shellver();
X		display(cur_command);
X		c = -1;
X	    }
X	    if (!strcmp(optr, "history"))
X	    {
X		history();
X		display(cur_command);
X		c = -1;
X	    }
X	    if (c == -1)
X		c = readkey();
X	    else
X		c = -1;		/* exit */
X	    break;
X	case LEFT:
X	    if (cur_loc != cur_command->text)
X	    {
X		cur_loc--;
X		col--;
X		CUR_LEFT
X	    }
X	    c = readkey();
X	    break;
X	case RIGHT:
X	    if (*cur_loc != NULL)
X	    {
X		cur_loc++;
X		col++;
X		CUR_RIGHT
X	    }
X	    c = readkey();
X	    break;
X	case INS:
X	    if (ins_flag == ON)
X		ins_flag = OFF;
X	    else
X		ins_flag = ON;
X	    c = readkey();
X	    break;
X	case DEL:
X	    if (*cur_loc != NULL)
X	    {
X		delete_ch(cur_loc);
X		DEL_CHAR
X	    }
X	    c = readkey();
X	    break;
X	case BS:
X	    if (cur_loc != cur_command->text)
X	    {
X		CUR_LEFT
X		    delete_ch(--cur_loc);
X		col--;
X		DEL_CHAR
X	    }
X	    c = readkey();
X	    break;
X	case ERASE:
X	    cur_loc = cur_command->text;
X	    col = strlen(hprompt) + 1;
X	    *cur_loc = NULL;
X	    CLEAR_LINE;
X	    display(cur_command);
X	    c = readkey();
X	    break;
X	case ENDLINE:
X	    while (*cur_loc)
X	    {
X		cur_loc++;
X		col++;
X	    }
X	    display(cur_command);
X	    c = readkey();
X	    break;
X	case STARTLINE:
X	    cur_loc = cur_command->text;
X	    col = strlen(hprompt) + 1;
X	    display(cur_command);
X	    c = readkey();
X	    break;
X	case XIT:
X	    *optr = 4;
X	    c = -1;
X	    break;
X	default:
X	    insert_ch(cur_loc++, c, ins_flag);
X	    c = readkey();
X	    break;
X	}
X	if (c < 0)
X	    break;
X    }
X
X    rawmode(OFF);
X    prs("\r\n");
X    noioreq = TRUE;
X    if (*optr == 4)
X    {
X	*cadr = 0x00;
X	return 1;
X    }
X    if (*optr == NULL)
X    {
X	*cadr == '\n';
X	optr++;
X    }
X    else
X	*cadr = *optr++;
X    return 1;
X}
X
Xdisplay(cmd)
Xstruct command *cmd;
X{
X    int   i;
X    char  buf[120];
X
X    CLEAR_LINE;
X    strcpy(buf, hprompt);
X    strcat(buf, cmd->text);
X    prs(buf);
X    pos_cursor(col);
X}
X
Xpos_cursor(x)
Xint   x;
X{
X    putc('\r');
X    prs("\033[");
X    prn(x);
X    putc('C');
X}
X
Xinsert_ch(a, c, f)
Xchar *a, c;
Xint   f;
X{
X    char *w;
X
X    if (f == ON)		/* insert mode */
X    {
X	w = a;
X	while (*w++);		/* find end of string */
X	while (w != a)		/* now make room for new character */
X	{
X	    *w = *(w - 1);
X	    w--;
X	}
X	*a = c;			/* put the character in the string */
X	INS_CHAR
X	    putc(c);
X    }
X    else
X    {				/* overstrike mode */
X	if (*a == NULL)
X	    *(a + 1) = NULL;	/* move the NULL over to make room */
X	*a = c;			/* insert the character */
X	putc(c);
X    }
X    col++;
X}
X
Xdelete_ch(a)
Xchar *a;
X{
X    char *w;
X
X    w = a;
X    while (*(w + 1))
X    {
X	*w = *(w + 1);
X	w++;
X    }
X    *w = *(w + 1);
X}
X
Xerror(s1, s2)
Xchar *s1, *s2;
X{
X    prs("\r\nshh: ");
X    prs(s1);
X    putc(' ');
X    prs(s2);
X    prs("\r\n$");
X}
X
X
Xrawmode(flag)
Xint   flag;
X{
X    if (flag == OFF)
X    {
X	args.sg_flags &= ~RAW;
X	args.sg_flags |= ECHO;
X    }
X    else
X    {
X	args.sg_flags &= ~ECHO;
X	args.sg_flags |= RAW;
X    }
X    ioctl(FD, TIOCSETP, &args);
X    ioctl(FD, TIOCSETC, &tch);
X}
X
X
Xreadkey()
X{
X    /*
X     * * readkey -	read the keyboard and translate special key sequences *
X     * nto specified values.
X     */
X
X    int   k;
X
X    k = getchar();
X    if (k != ESC)
X	return k;		/* not special return normal value */
X    else
X    {				/* this is a special key find out which one */
X	if ((k = getchar()) != BRACKET)
X	    return UNKNOWN;	/* key sequence not handled */
X	else
X	{
X	    k = getchar();
X	    switch (k)
X	    {
X	    case 'A':
X		return UP;
X	    case 'B':
X		return DOWN;
X	    case 'C':
X		return RIGHT;
X	    case 'D':
X		return LEFT;
X	    case 'H':
X		return HOME;
X	    case 'Y':
X		return END;
X	    case 'V':
X		return PGUP;
X	    case 'U':
X		return PGDN;
X	    default:
X		return UNKNOWN;
X	    }
X	}
X    }
X}
Xgetchar()
X{
X    int   c;
X
X    read(0, &c, 1);
X    return c;
X}
Xinit_hist()
X{
X    int   i;
X
X    /* initialize history table */
X    for (i = 0; i < CMD_MAX; i++)
X    {
X	cur_command = &cmd_array[i];
X	if (i == 0)		/* link first to last */
X	    cur_command->prev = &cmd_array[CMD_MAX - 1];
X	else
X	    cur_command->prev = &cmd_array[i - 1];
X	if (i == (CMD_MAX - 1))	/* link last to first */
X	    cur_command->next = &cmd_array[0];
X	else
X	    cur_command->next = &cmd_array[i + 1];
X	cur_command->text[0] = 0x00;
X	cur_command->index = i;
X    }
X    cur_command = &cmd_array[0];/* start here */
X    cur_loc = cur_command->text;
X    ins_flag = ON;		/* insert mode initially */
X    noioreq = FALSE;
X}
Xshellver()
X{
X    prs("\r\n");
X    prs(VERSION);
X    prs("\r\n$");
X}
Xhistory()
X{
X    struct command *cp;
X    int   i;
X
X    prs("\r\n");
X    cp = &cmd_array[0];
X    for (i = 0; i < CMD_MAX; i++)
X    {
X	if (i < 10)
X	    putc(' ');
X	prn(i);
X	prs(": ");
X	prs(cp->text);
X	prs("\r\n");
X	cp++;
X    }
X    putc('$');
X}
Xatoi(str)
Xchar *str;
X{
X    int   i = 0, c;
X
X    while (c = *str++)
X    {
X	if ((c = c - '0') < 0 || c > 9)
X	    break;
X	i = (i * 10) + c;
X    }
X    return i;
X}
Xsetprompt()
X{
X    char  dir[256], *ptr;
X
X    /* set hprompt string */
X#ifdef SHORT_PROMPT
X    strcpy(hprompt, PROMPT);
X#else
X    strcpy(hprompt, "(");
X    if ((ptr = getenv("LOGNAME")) == (char *) NULL)
X	strcat(hprompt, "?) ");
X    else
X    {
X	strcat(hprompt, ptr);
X	strcat(hprompt, ") ");
X    }
X    if ((ptr = getcwd(dir, 255)) == (char *) NULL)
X	strcat(hprompt, "?> ");
X    else
X    {
X	strcat(hprompt, getcwd(dir, 255));
X	strcat(hprompt, "> ");
X    }
X#endif
X}
/
echo x - hist.h
sed '/^X/s///' > hist.h << '/'
X#define VERSION		"shh version 1.1"
X#define LINE_MAX	101	/* maximum size of each command */
X#define CMD_MAX		 20	/* number of commands in history */
X#define CMD_MIN_SIZE      5	/* minimum length of command to save */
X/* #define SHORT_PROMPT		/* define eliminates user and path prompt */
X#define PROMPT		"$ "	/* prompt string */
X#define NULL		0x00
X#define CLEAR_LINE	prs("\r\033[C\033[K");
X#define CUR_LEFT	putc('\b');
X#define CUR_RIGHT	prs("\033[C");
X#define INS_CHAR	prs("\033[@");
X#define DEL_CHAR	prs("\033[P");
X
X#define ESC		 27	/* escape code */
X#define BRACKET		'['	/* special key indicator */
X
X/* special editing keys */
X#define BS      	  8	/* back space */
X#define INS     	  9     /* ^i */
X#define DEL     	127	/* delete */
X#define ERASE   	 11     /* ^k */
X#define DELINE  	377	/* not used */
X#define EDITX   	'\r'	/* return */
X#define XIT     	  4	/* ^d */
X#define ENDLINE		  5	/* ^e */
X#define STARTLINE	  1	/* ^a */
X#define CMDRECALL	 18	/* ^r */
X
X/* keypad and arrow keys */
X#define UP      	200	/* up arrow */
X#define DOWN    	201	/* down arrow */
X#define LEFT    	202	/* arrow */
X#define RIGHT   	203	/* arrow */
X#define HOME		204	/* home */
X#define END		205	/* end */
X#define PGUP		206	/* page up */
X#define PGDN		207	/* page down */
X#define UNKNOWN		208	/* unknown key */
X
Xstruct command
X{
X	struct command	*next;
X	struct command	*prev;
X	int		index;
X	char		text[LINE_MAX];
X};
X
Xchar *getenv();
Xchar *getcwd();
X
/
echo x - sh1.c.cdif
sed '/^X/s///' > sh1.c.cdif << '/'
X*** /usr/src/commands/sh/sh1.c	Tue Apr  3 08:23:14 1990
X--- ../sh1.c	Tue Apr  3 14:58:20 1990
X***************
X*** 41,46 ****
X--- 41,47 ----
X  	int (*iof)();
X  
X  	initarea();
X+ 	init_hist();
X  	if ((ap = environ) != NULL) {
X  		while (*ap)
X  			assign(*ap++, !COPYV);
/
echo x - sh2.c.cdif
sed '/^X/s///' > sh2.c.cdif << '/'
X*** /usr/src/commands/sh/sh2.c	Tue Apr  3 08:23:15 1990
X--- ../sh2.c	Tue Apr  3 14:58:22 1990
X***************
X*** 774,780 ****
X  
X  /* VARARGS1 */
X  /* ARGSUSED */
X! printf(s)	/* yyparse calls it */
X  char *s;
X  {
X  }
X--- 774,780 ----
X  
X  /* VARARGS1 */
X  /* ARGSUSED */
X! prntf(s)	/* yyparse calls it */
X  char *s;
X  {
X  }
/
echo x - sh5.c.cdif
sed '/^X/s///' > sh5.c.cdif << '/'
X*** /usr/src/commands/sh/sh5.c	Tue Apr  3 08:23:21 1990
X--- ../sh5.c	Tue Apr  3 14:58:28 1990
X***************
X*** 30,35 ****
X--- 30,36 ----
X  		gflg++;
X  		return(c);
X  	}
X+ 
X  	c = readc();
X   	if (ec != '\'' && ec != '`' && e.iop->task != XGRAVE) {
X  		if(c == '\\') {
X***************
X*** 85,93 ****
X  		        }
X  		    }
X  		    if (e.iop->task == XIO) {
X! 			if (multiline)
X! 			    return e.iop->prev = 0;
X! 			if (talking && e.iop == iostack+1)
X  			    prs(prompt->value);
X  		    }
X  		}
X--- 86,94 ----
X  		        }
X  		    }
X  		    if (e.iop->task == XIO) {
X! 			if (multiline) 
X! 			    return e.iop->prev = 0;
X! 			if (talking && e.iop == iostack+1) 
X  			    prs(prompt->value);
X  		    }
X  		}
X***************
X*** 292,301 ****
X  	  ap->afpos++;
X  	  return *bp->bufp++ & 0177;
X  	}
X! 
X! 	do {
X! 		i = read(ap->afile, &c, sizeof(c));
X! 	} while (i < 0 && errno == EINTR);
X  	return(i == sizeof(c)? c&0177: (closef(ap->afile), 0));
X  }
X  
X--- 293,309 ----
X  	  ap->afpos++;
X  	  return *bp->bufp++ & 0177;
X  	}
X! 	
X! 	if( ap->afile == 0 )
X! 	{
X! 		i=hist_io(&c);
X! 	}
X! 	else
X! 	{ 
X! 	    do {
X! 		i = read(ap->afile, &c, sizeof(c));
X! 	       } while (i < 0 && errno == EINTR);
X! 	} 
X  	return(i == sizeof(c)? c&0177: (closef(ap->afile), 0));
X  }
X  
/
 -------------------------------------------------------------------
| Gary C. Strauss gcs@nucleus.mi.org |        Anywhere but here!    |
| Vickers Inc.                       |        It's a shell game     |
 -------------------------------------------------------------------
|                   My opinions are probably my own!                |
 -------------------------------------------------------------------