summaryrefslogtreecommitdiff
path: root/history
diff options
context:
space:
mode:
Diffstat (limited to 'history')
-rw-r--r--history/Makefile6
-rw-r--r--history/history.1234
-rw-r--r--history/history.c333
3 files changed, 573 insertions, 0 deletions
diff --git a/history/Makefile b/history/Makefile
new file mode 100644
index 0000000..4655947
--- /dev/null
+++ b/history/Makefile
@@ -0,0 +1,6 @@
+CC=gcc -g -O
+
+history: history.c
+ $(CC) -o history history.c
+ @echo 'Now install "history" as -, --, -p and --p in your bin directory'
+ @echo '(for example, by making them links to the same file)'
diff --git a/history/history.1 b/history/history.1
new file mode 100644
index 0000000..18ed28f
--- /dev/null
+++ b/history/history.1
@@ -0,0 +1,234 @@
+.\" history.1
+.\"-------
+.\" See rc.1 for man page portability notes.
+.\"-------
+.\" Dd distance to space vertically before a "display"
+.\" These are what n/troff use for interparagraph distance
+.\"-------
+.if t .nr Dd .4v
+.if n .nr Dd 1v
+.\"-------
+.\" Ds begin a display, indented .5 inches from the surrounding text.
+.\"
+.\" Note that uses of Ds and De may NOT be nested.
+.\"-------
+.de Ds
+.\" .RS \\$1
+.sp \\n(Ddu
+.in +0.5i
+.nf
+..
+.\"-------
+.\" De end a display (no trailing vertical spacing)
+.\"-------
+.de De
+.fi
+.in
+.\" .RE
+..
+.\"-------
+.\" I stole the Xf macro from the -man macros on my machine (originally
+.\" "}S", I renamed it so that it won't conflict).
+.\"-------
+.\" Set Cf to the name of the constant width font.
+.\" It will be "C" or "(CW", typically.
+.\" NOTEZ BIEN the lines defining Cf must have no trailing white space:
+.\"-------
+.if t .ds Cf C
+.if n .ds Cf R
+.\"-------
+.\" Rc - Alternate Roman and Courier
+.\"-------
+.de Rc
+.Xf R \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Ic - Alternate Italic and Courier
+.\"-------
+.de Ic
+.Xf I \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Bc - Alternate Bold and Courier
+.\"-------
+.de Bc
+.Xf B \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Cr - Alternate Courier and Roman
+.\"-------
+.de Cr
+.Xf \\*(Cf R \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Ci - Alternate Courier and Italic
+.\"-------
+.de Ci
+.Xf \\*(Cf I \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Cb - Alternate Courier and Bold
+.\"-------
+.de Cb
+.Xf \\*(Cf B \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Xf - Alternate fonts
+.\"
+.\" \$1 - first font
+.\" \$2 - second font
+.\" \$3 - desired word with embedded font changes, built up by recursion
+.\" \$4 - text for first font
+.\" \$5 - \$9 - remaining args
+.\"
+.\" Every time we are called:
+.\"
+.\" If there is something in \$4
+.\" then Call ourself with the fonts switched,
+.\" with a new word made of the current word (\$3) and \$4
+.\" rendered in the first font,
+.\" and with the remaining args following \$4.
+.\" else We are done recursing. \$3 holds the desired output
+.\" word. We emit \$3, change to Roman font, and restore
+.\" the point size to the default.
+.\" fi
+.\"
+.\" Use Xi to add a little bit of space after italic text.
+.\"-------
+.de Xf
+.ds Xi
+.\"-------
+.\" I used to test for the italic font both by its font position
+.\" and its name. Now just test by its name.
+.\"
+.\" .if "\\$1"2" .if !"\\$5"" .ds Xi \^
+.\"-------
+.if "\\$1"I" .if !"\\$5"" .ds Xi \^
+.\"-------
+.\" This is my original code to deal with the recursion.
+.\" Evidently some nroffs can't deal with it.
+.\"-------
+.\" .ie !"\\$4"" \{\
+.\" . Xf \\$2 \\$1 "\\$3\\f\\$1\\$4\\*(Xi" "\\$5" "\\$6" "\\$7" "\\$8" "\\$9"
+.\" .\}
+.\" .el \{\\$3
+.\" . ft R \" Restore the default font, since we don't know
+.\" . \" what the last font change was.
+.\" . ps 10 \" Restore the default point size, since it might
+.\" . \" have been changed by an argument to this macro.
+.\" .\}
+.\"-------
+.\" Here is more portable (though less pretty) code to deal with
+.\" the recursion.
+.\"-------
+.if !"\\$4"" .Xf \\$2 \\$1 "\\$3\\f\\$1\\$4\\*(Xi" "\\$5" "\\$6" "\\$7" "\\$8" "\\$9"
+.if "\\$4"" \\$3\fR\s10
+..
+.TH HISTORY 1 "30 July 1991"
+.SH NAME
+\-, \-\|\-, \-p, \-\|\-p \- shell history programs
+.SH SYNOPSIS
+.B \-
+.RI [ pattern ...]
+.RI [ substitution ...]
+.SH DESCRIPTION
+This set of programs provides a crude history mechanism for the shell
+.IR rc (1).
+It is based on the v8 UNIX programs
+.IR = ,
+.IR == ,
+etc.
+.PP
+The program
+.RI `` \- ''
+runs the shell on the command it is requested to find.
+The program
+.RI `` \-\|\- ''
+edits that command first.
+The programs
+.RI `` \-p ''
+and
+.RI `` \-\|\-p ''
+are similar, except that they print the final command on their standard
+output instead of running the shell.
+.PP
+The commands work by looking for a file
+named by the environment variable
+.Cr $history ,
+and by searching for previous commands in this file.
+Old commands can be edited,
+or simply re-executed according to the rules below:
+.PP
+A command is searched for by examining the lines in
+.Cr $history
+in reverse order.
+Lines which contain a previous invocation of the history
+program itself are ignored.
+If one or more
+.I pattern
+is supplied on the command line,
+then the patterns are used as a means of
+limiting the search.
+Patterns match any substring of a previous command,
+and if more than one pattern is present then all patterns must be
+matched before a command is selected.
+.PP
+Substitutions may also be specified on the command line.
+These have the syntax:
+.Ds
+.Ic old : new
+.De
+.PP
+(Note that the
+.I old
+pattern is used as a search-limiting pattern also.)
+Substitutions happen on the first match.
+.PP
+Finally, a command may be edited in a crude line-mode fashion:
+The line to be edited is printed out, and below it the user
+supplies modifications to the command:
+.TP
+.B any character
+Replaces the character above.
+.TP
+.B space or tab
+Skips over the above character(s).
+.TP
+.B #
+Deletes one character.
+.TP
+.B %
+Replaces one character with a space.
+.TP
+.B ^
+Inserts the rest of the typed line just before the character.
+.TP
+.B $
+Deletes the rest of the line from that character on, and replaces
+it with the rest of the typed line.
+.TP
+.B +
+Appends the rest of the typed line.
+.TP
+.B \-
+Backs up to a previous command satisfying the same matching
+constraints.
+.TP
+.B end of file
+If an end-of-file is read from the keyboard by the editor,
+it aborts with exit status 1 and does not produce any output.
+.SH EXAMPLES
+The history programs work best when their output is reinterpreted by
+the shell using an
+.Cr eval
+command.
+This can be achieved by writing a shell function to perform the
+reinterpretation:
+.Ds
+.Cr "fn - -- {"
+.Cr " comm = \`{$0^p $*}"
+.Cr " if (! ~ $#comm 0) {"
+.Cr " echo $comm >[1=2]"
+.Cr " eval $comm"
+.Cr " }"
+.Cr "}"
diff --git a/history/history.c b/history/history.c
new file mode 100644
index 0000000..5f2cece
--- /dev/null
+++ b/history/history.c
@@ -0,0 +1,333 @@
+/*
+ history.c -- primitive history mechanism
+
+ Paul Haahr & Byron Rakitzis, July 1991.
+
+ This program mimics the att v8 = and == history programs.
+ The edit() algorithm was adapted from a similar program
+ that Boyd Roberts wrote, but otherwise all the code has
+ been written from scratch.
+
+ edit() was subsequently redone by Hugh Redelmeier in order
+ to correctly deal with tab characters in the source line.
+
+ BUGS:
+ There is an implicit assumption that commands are no
+ more than 1k characters long.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char *id = "@(#) history.c 8/91";
+
+#undef FALSE
+#undef TRUE
+typedef enum { FALSE, TRUE } bool;
+
+#define CHUNKSIZE 65536
+
+static struct {
+ char *old, *new;
+} *replace;
+
+static char **search, *progname, *history;
+static char me; /* typically ':' or '-' */
+static bool editit = FALSE, printit = FALSE;
+static int nreplace = 0, nsearch = 0;
+static FILE *fp;
+
+static void *ealloc(size_t n) {
+ void *p = (void *) malloc(n);
+ if (p == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ return p;
+}
+
+static void *erealloc(void *p, size_t n) {
+ p = (void *) realloc(p, n);
+ if (p == NULL) {
+ perror("realloc");
+ exit(1);
+ }
+ return p;
+}
+
+static char *newstr() {
+ return ealloc((size_t)1024);
+}
+
+static char *basename(char *s) {
+ char *t = strrchr(s, '/');
+ return (t == NULL) ? s : t + 1;
+}
+
+/* stupid O(n^2) substring matching routine */
+
+static char *isin(char *target, char *pattern) {
+ size_t plen = strlen(pattern);
+ size_t tlen = strlen(target);
+ for (; tlen >= plen; target++, --tlen)
+ if (strncmp(target, pattern, plen) == 0)
+ return target;
+ return NULL;
+}
+
+/* replace the first match in the string with "new" */
+static char *sub(char *s, char *old, char *new) {
+ char *t, *u;
+
+ t = isin(s, old);
+ u = newstr();
+
+ *t = '\0';
+ while (*old != '\0')
+ old++, t++;
+ strcpy(u, s);
+ strcat(u, new);
+ strcat(u, t);
+ return u;
+}
+
+static char *edit(char *s) {
+ char *final, *f, *end;
+ int col;
+ bool ins;
+
+start:
+ fprintf(stderr, "%s\n", s);
+ f = final = newstr();
+ end = s + strlen(s);
+ col = 0;
+ ins = FALSE;
+
+ for (;; col++) {
+ int c = getchar();
+
+ if (c == me && col == 0) {
+ int peekc = getchar();
+ if (peekc == '\n')
+ return NULL;
+ ungetc(peekc, stdin);
+ }
+ if (c == '\n') {
+ if (col == 0)
+ return s;
+
+ while (s < end) /* copy remainder of string */
+ *f++ = *s++;
+ *f = '\0';
+ s = final;
+ goto start;
+ } else if (ins || s>=end) {
+ /* col need not be accurate -- tabs need not be interpreted */
+ *f++ = c;
+ } else {
+ switch (c) {
+ case '+':
+ while (s < end)
+ *f++ = *s++;
+ *f = '\0';
+ continue;
+ case '%':
+ c = ' ';
+ /* FALLTHROUGH */
+ default:
+ *f++ = c;
+ break;
+ case EOF:
+ exit(1);
+ /* NOTREACHED */
+ case ' ':
+ if (*s == '\t') {
+ int oldcol = col;
+
+ for (;; col++) {
+ int peekc;
+
+ if ((col&07) == 07) {
+ *f++ = '\t'; /* we spaced past a tab */
+ break;
+ }
+ peekc = getchar();
+ if (peekc != ' ') {
+ ungetc(peekc, stdin);
+ if (peekc != '\n') {
+ /* we spaced partially into a tab */
+ do {
+ *f++ = ' ';
+ oldcol++;
+ } while (oldcol <= col);
+ }
+ break;
+ }
+ }
+ } else {
+ *f++ = *s;
+ }
+ break;
+ case '#':
+ break;
+ case '$':
+ end = s; /* truncate s */
+ continue; /* skip incrementing s */
+ case '^':
+ ins = TRUE;
+ continue; /* skip incrementing s */
+ case '\t':
+ for (;; col++) {
+ if ((*f++ = s<end? *s++ : '\t') == '\t') {
+ col = col | 07; /* advance to before next tabstop */
+ }
+ if ((col&07) == 07) /* stop before tabstop */
+ break;
+ }
+ continue; /* skip incrementing s */
+ }
+ if (s<end && (*s!='\t' || (col&07)==07))
+ s++;
+ }
+ }
+}
+
+static char *readhistoryfile(char **last) {
+ char *buf;
+ size_t count, size;
+ long nread;
+
+ if ((history = getenv("history")) == NULL) {
+ fprintf(stderr, "$history not set\n");
+ exit(1);
+ }
+ fp = fopen(history, "r+");
+ if (fp == NULL) {
+ perror(history);
+ exit(1);
+ }
+
+ size = 0;
+ count = 0;
+ buf = ealloc(size = CHUNKSIZE);
+ while ((nread = fread(buf + count, sizeof (char), size - count, fp)) > 0) {
+ count += nread;
+ if (size - count == 0)
+ buf = erealloc(buf, size *= 4);
+ }
+ if (nread == -1) {
+ perror(history);
+ exit(1);
+ }
+ *last = buf + count;
+ return buf;
+}
+
+static char *getcommand() {
+ char *s, *t;
+ static char *hist = NULL, *last;
+
+ if (hist == NULL) {
+ hist = readhistoryfile(&last);
+ *--last = '\0'; /* trim final newline */
+ }
+
+again: s = last;
+ if (s < hist)
+ return NULL;
+ while (*--s != '\n')
+ if (s <= hist) {
+ last = hist - 1;
+ return hist;
+ }
+ *s = '\0';
+ last = s++;
+
+ /*
+ * if the command contains the "me" character at the start of the line
+ * or after any of [`{|()] then try again
+ */
+
+ for (t = s; *t != '\0'; t++)
+ if (*t == me) {
+ char *u = t - 1;
+ while (u >= s && (*u == ' ' || *u == '\t'))
+ --u;
+ if (u < s || *u == '`' || *u == '{' || *u == '|' || *u == '(' || *u == ')')
+ goto again;
+ }
+ return s;
+}
+
+int main(int argc, char **argv) {
+ int i;
+ char *s;
+
+ s = progname = basename(argv[0]);
+ me = *s++;
+ if (*s == me) {
+ s++;
+ editit = TRUE;
+ }
+ if (*s == 'p') {
+ s++;
+ printit = TRUE;
+ }
+/* Nahh...
+ if (*s != '\0') {
+ fprintf(stderr, "\"%s\": bad name for history program\n", progname);
+ exit(1);
+ }
+*/
+
+ if (argc > 1) {
+ replace = ealloc((argc - 1) * sizeof *replace);
+ search = ealloc((argc - 1) * sizeof *search);
+ }
+ for (i = 1; i < argc; i++)
+ if ((s = strchr(argv[i], ':')) == NULL)
+ search[nsearch++] = argv[i];
+ else {
+ *(char *)s = '\0'; /* do we confuse ps too much? */
+ replace[nreplace].old = argv[i];
+ replace[nreplace].new = s + 1;
+ nreplace++;
+ }
+
+next: s = getcommand();
+ if (s == NULL) {
+ fprintf(stderr, "command not matched\n");
+ return 1;
+ }
+ for (i = 0; i < nsearch; i++)
+ if (!isin(s, search[i]))
+ goto next;
+ for (i = 0; i < nreplace; i++)
+ if (!isin(s, replace[i].old))
+ goto next;
+ else
+ s = sub(s, replace[i].old, replace[i].new);
+ if (editit) {
+ s = edit(s);
+ if (s == NULL)
+ goto next;
+ }
+ fseek(fp, 0, 2); /* 2 == end of file. i.e., append command to $history */
+ fprintf(fp, "%s\n", s);
+ fclose(fp);
+ if (printit)
+ printf("%s\n", s);
+ else {
+ char *shell = getenv("SHELL");
+
+ if (!editit)
+ fprintf(stderr, "%s\n", s);
+ if (shell == NULL)
+ shell = "/bin/sh";
+ execl(shell, basename(shell), "-c", s, NULL);
+ perror(shell);
+ exit(1);
+ }
+ return 0;
+}