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! | -------------------------------------------------------------------