From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=ham autolearn_force=no version=3.4.4 Received: (qmail 7168 invoked from network); 25 Feb 2023 16:57:21 -0000 Received: from 9front.inri.net (168.235.81.73) by inbox.vuxu.org with ESMTPUTF8; 25 Feb 2023 16:57:21 -0000 Received: from mimir.eigenstate.org ([206.124.132.107]) by 9front; Sat Feb 25 11:56:10 -0500 2023 Received: from stockyard.fios-router.home (pool-70-108-0-128.washdc.fios.verizon.net [70.108.0.128]) by mimir.eigenstate.org (OpenSMTPD) with ESMTPSA id a3f4aa61 (TLSv1.2:ECDHE-RSA-AES256-SHA:256:NO) for <9front@9front.org>; Sat, 25 Feb 2023 08:56:07 -0800 (PST) Message-ID: To: 9front@9front.org Date: Sat, 25 Feb 2023 11:56:04 -0500 From: ori@eigenstate.org In-Reply-To: MIME-Version: 1.0 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit List-ID: <9front.9front.org> List-Help: X-Glyph: ➈ X-Bullshit: XMPP storage-oriented browser hosting reduce/map generator Subject: Re: [9front] merge3: a first draft Reply-To: 9front@9front.org Precedence: bulk Quoth ori@eigenstate.org: > I've been annoyed that we have to use ape/diff3 to merge > files for a while, so I finally sat down this weekend and > hacked 3-way merge capabilities into diff(1). > > This is a rough draft, but here's the current approach. > > 3 way merging operates as follows: > Now bugfixed, and with tests. diff e6c6217b35c319127f0200fdb28ec86e1b774a4f uncommitted --- /dev/null +++ b/sys/src/cmd/diff/diff.c @@ -1,0 +1,84 @@ +#include +#include +#include +#include "diff.h" + +void +done(int status) +{ + switch(status) + { + case 0: + exits(""); + case 1: + exits("some"); + default: + exits("error"); + } +} + +void +usage(void) +{ + fprint(2, "usage: %s [-abcefmnrw] file1 ... file2\n", argv0); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + int i; + Dir *fsb, *tsb; + + Binit(&stdout, 1, OWRITE); + ARGBEGIN{ + case 'e': + case 'f': + case 'n': + case 'c': + case 'a': + case 'u': + mode = ARGC(); + break; + case 'w': + bflag = 2; + break; + + case 'b': + bflag = 1; + break; + + case 'r': + rflag = 1; + break; + + case 'm': + mflag = 1; + break; + + case 'h': + default: + usage(); + }ARGEND; + if (argc < 2) + usage(); + if ((tsb = dirstat(argv[argc-1])) == nil) + sysfatal("can't stat %s", argv[argc-1]); + if (argc > 2) { + if (!DIRECTORY(tsb)) + sysfatal("not directory: %s", argv[argc-1]); + mflag = 1; + } else { + if ((fsb = dirstat(argv[0])) == nil) + sysfatal("can't stat %s", argv[0]); + if (DIRECTORY(fsb) && DIRECTORY(tsb)) + mflag = 1; + free(fsb); + } + free(tsb); + for (i = 0; i < argc-1; i++) + diff(argv[i], argv[argc-1], 0); + + done(anychange); + /*NOTREACHED*/ +} --- a/sys/src/cmd/diff/diff.h +++ b/sys/src/cmd/diff/diff.h @@ -1,13 +1,53 @@ -typedef struct Line Line; +typedef struct Line Line; +typedef struct Cand Cand; +typedef struct Diff Diff; +typedef struct Change Change; struct Line { int serial; int value; }; -extern Line *file[2]; -extern int len[2]; -extern long *ixold, *ixnew; -extern int *J; + +struct Cand { + int x; + int y; + int pred; +}; + +struct Change +{ + int a; + int b; + int c; + int d; +}; + +struct Diff { + Cand cand; + Line *file[2], line; + int len[2]; + int binary; + Line *sfile[2]; /*shortened by pruning common prefix and suffix*/ + int slen[2]; + int pref, suff; /*length of prefix and suffix*/ + int *class; /*will be overlaid on file[0]*/ + int *member; /*will be overlaid on file[1]*/ + int *klist; /*will be overlaid on file[0] after class*/ + Cand *clist; /* merely a free storage pot for candidates */ + int clen; + int *J; /*will be overlaid on class*/ + long *ixold; /*will be overlaid on klist*/ + long *ixnew; /*will be overlaid on file[1]*/ + char *file1; + char *file2; + Biobuf *input[2]; + Biobuf *b0; + Biobuf *b1; + int firstchange; + Change *changes; + int nchanges; +}; + extern char mode; extern char bflag; extern char rflag; @@ -14,19 +54,24 @@ extern char mflag; extern int anychange; extern Biobuf stdout; -extern int binary; #define MAXPATHLEN 1024 +#define DIRECTORY(s) ((s)->qid.type&QTDIR) +#define REGULAR_FILE(s) ((s)->type == 'M' && !DIRECTORY(s)) + int mkpathname(char *, char *, char *); +char *mktmpfile(int, Dir **); +char *statfile(char *, Dir **); void *emalloc(unsigned); void *erealloc(void *, unsigned); void diff(char *, char *, int); +void diffreg(char*, char*, char*, char*); void diffdir(char *, char *, int); -void diffreg(char *, char *, char *, char *); -Biobuf *prepare(int, char *, char *); -void panic(int, char *, ...); -void check(Biobuf *, Biobuf *); -void change(int, int, int, int); -void flushchanges(void); - +void calcdiff(Diff *, char *, char *, char *, char *); +Biobuf *prepare(Diff*, int, char *, char *); +void check(Diff *, Biobuf *, Biobuf *); +void change(Diff *, int, int, int, int); +void freediff(Diff *); +void flushchanges(Diff *); +void fetch(Diff *d, long *f, int a, int b, Biobuf *bp, char *s); --- a/sys/src/cmd/diff/diffdir.c +++ b/sys/src/cmd/diff/diffdir.c @@ -111,3 +111,45 @@ free(dirf); free(dirt); } + +void +diff(char *f, char *t, int level) +{ + char *fp, *tp, *p, fb[MAXPATHLEN+1], tb[MAXPATHLEN+1]; + Dir *fsb, *tsb; + + fsb = nil; + tsb = nil; + if ((fp = statfile(f, &fsb)) == 0) + goto Return; + if ((tp = statfile(t, &tsb)) == 0) + goto Return; + if (DIRECTORY(fsb) && DIRECTORY(tsb)) { + if (rflag || level == 0) + diffdir(fp, tp, level); + else + Bprint(&stdout, "Common subdirectories: %s and %s\n", fp, tp); + } + else if (REGULAR_FILE(fsb) && REGULAR_FILE(tsb)) + diffreg(fp, f, tp, t); + else { + if (REGULAR_FILE(fsb)) { + if ((p = utfrrune(f, '/')) == 0) + p = f; + else + p++; + if (mkpathname(tb, tp, p) == 0) + diffreg(fp, f, tb, t); + } else { + if ((p = utfrrune(t, '/')) == 0) + p = t; + else + p++; + if (mkpathname(fb, fp, p) == 0) + diffreg(fb, f, tp, t); + } + } +Return: + free(fsb); + free(tsb); +} --- a/sys/src/cmd/diff/diffio.c +++ b/sys/src/cmd/diff/diffio.c @@ -4,10 +4,6 @@ #include #include "diff.h" -static Biobuf *input[2]; -static char *file1, *file2; -static int firstchange; - #define MAXLINELEN 4096 #define MIN(x, y) ((x) < (y) ? (x): (y)) @@ -104,7 +100,7 @@ } Biobuf * -prepare(int i, char *arg, char *orig) +prepare(Diff *d, int i, char *arg, char *orig) { Line *p; int j, h; @@ -115,10 +111,10 @@ bp = Bopen(arg, OREAD); if (!bp) { - panic(mflag ? 0: 2, "cannot open %s: %r\n", arg); + sysfatal("cannot open %s: %r", arg); return 0; } - if (binary) + if (d->binary) return bp; nbytes = Bread(bp, buf, MIN(1024, MAXLINELEN)); if (nbytes > 0) { @@ -130,7 +126,7 @@ */ cp += chartorune(&r, cp); if (r == 0 || (r > 0x7f && r <= 0xa0)) { - binary++; + d->binary++; return bp; } } @@ -139,14 +135,14 @@ p = emalloc(3*sizeof(Line)); for (j = 0; h = readhash(bp, buf); p[j].value = h) p = erealloc(p, (++j+3)*sizeof(Line)); - len[i] = j; - file[i] = p; - input[i] = bp; + d->len[i] = j; + d->file[i] = p; + d->input[i] = bp; if (i == 0) { - file1 = orig; - firstchange = 0; + d->file1 = orig; + d->firstchange = 0; } else - file2 = orig; + d->file2 = orig; return bp; } @@ -175,31 +171,32 @@ * need to fix up for unexpected EOF's */ void -check(Biobuf *bf, Biobuf *bt) +check(Diff *d, Biobuf *bf, Biobuf *bt) { int f, t, flen, tlen; char fbuf[MAXLINELEN], tbuf[MAXLINELEN]; - ixold[0] = ixnew[0] = 0; - for (f = t = 1; f < len[0]; f++) { + d->ixold[0] = 0; + d->ixnew[0] = 0; + for (f = t = 1; f < d->len[0]; f++) { flen = readline(bf, fbuf); - ixold[f] = ixold[f-1] + flen + 1; /* ftell(bf) */ - if (J[f] == 0) + d->ixold[f] = d->ixold[f-1] + flen + 1; /* ftell(bf) */ + if (d->J[f] == 0) continue; do { tlen = readline(bt, tbuf); - ixnew[t] = ixnew[t-1] + tlen + 1; /* ftell(bt) */ - } while (t++ < J[f]); + d->ixnew[t] = d->ixnew[t-1] + tlen + 1; /* ftell(bt) */ + } while (t++ < d->J[f]); if (bflag) { flen = squishspace(fbuf); tlen = squishspace(tbuf); } if (flen != tlen || strcmp(fbuf, tbuf)) - J[f] = 0; + d->J[f] = 0; } - while (t < len[1]) { + while (t < d->len[1]) { tlen = readline(bt, tbuf); - ixnew[t] = ixnew[t-1] + tlen + 1; /* fseek(bt) */ + d->ixnew[t] = d->ixnew[t-1] + tlen + 1; /* fseek(bt) */ t++; } } @@ -212,8 +209,8 @@ Bprint(&stdout, "%s%d", separator, b); } -static void -fetch(long *f, int a, int b, Biobuf *bp, char *s) +void +fetch(Diff *d, long *f, int a, int b, Biobuf *bp, char *s) { char buf[MAXLINELEN]; int maxb; @@ -220,10 +217,10 @@ if(a <= 1) a = 1; - if(bp == input[0]) - maxb = len[0]; + if(bp == d->input[0]) + maxb = d->len[0]; else - maxb = len[1]; + maxb = d->len[1]; if(b > maxb) b = maxb; if(a > maxb) @@ -232,23 +229,12 @@ while (a++ <= b) { readline(bp, buf); Bprint(&stdout, "%s%s\n", s, buf); + Bflush(&stdout); } } -typedef struct Change Change; -struct Change -{ - int a; - int b; - int c; - int d; -}; - -Change *changes; -int nchanges; - void -change(int a, int b, int c, int d) +change(Diff *df, int a, int b, int c, int d) { char verb; char buf[4]; @@ -257,7 +243,7 @@ if (a > b && c > d) return; anychange = 1; - if (mflag && firstchange == 0) { + if (mflag && df->firstchange == 0) { if(mode) { buf[0] = '-'; buf[1] = mode; @@ -266,8 +252,8 @@ } else { buf[0] = '\0'; } - Bprint(&stdout, "diff %s%s %s\n", buf, file1, file2); - firstchange = 1; + Bprint(&stdout, "diff %s%s %s\n", buf, df->file1, df->file2); + df->firstchange = 1; } verb = a > b ? 'a': c > d ? 'd': 'c'; switch(mode) { @@ -281,10 +267,10 @@ range(c, d, ","); break; case 'n': - Bprint(&stdout, "%s:", file1); + Bprint(&stdout, "%s:", df->file1); range(a, b, ","); Bprint(&stdout, " %c ", verb); - Bprint(&stdout, "%s:", file2); + Bprint(&stdout, "%s:", df->file2); range(c, d, ","); break; case 'f': @@ -294,9 +280,9 @@ case 'c': case 'a': case 'u': - if(nchanges%1024 == 0) - changes = erealloc(changes, (nchanges+1024)*sizeof(changes[0])); - ch = &changes[nchanges++]; + if(df->nchanges%1024 == 0) + df->changes = erealloc(df->changes, (df->nchanges+1024)*sizeof(df->changes[0])); + ch = &df->changes[df->nchanges++]; ch->a = a; ch->b = b; ch->c = c; @@ -305,11 +291,11 @@ } Bputc(&stdout, '\n'); if (mode == 0 || mode == 'n') { - fetch(ixold, a, b, input[0], "< "); + fetch(df, df->ixold, a, b, df->input[0], "< "); if (a <= b && c <= d) Bprint(&stdout, "---\n"); } - fetch(ixnew, c, d, input[1], mode == 0 || mode == 'n' ? "> ": ""); + fetch(df, df->ixnew, c, d, df->input[1], mode == 0 || mode == 'n' ? "> ": ""); if (mode != 0 && mode != 'n' && c <= d) Bprint(&stdout, ".\n"); } @@ -320,69 +306,69 @@ }; int -changeset(int i) +changeset(Diff *d, int i) { - while(i changes[i+1].a) + while(i < d->nchanges && d->changes[i].b + 1 + 2*Lines > d->changes[i+1].a) i++; - if(inchanges) return i+1; - return nchanges; + return d->nchanges; } void -flushchanges(void) +flushchanges(Diff *df) { - int a, b, c, d, at, hdr; - int i, j; + vlong a, b, c, d, at, hdr; + vlong i, j; - if(nchanges == 0) + if(df->nchanges == 0) return; hdr = 0; - for(i=0; inchanges; ){ + j = changeset(df, i); + a = df->changes[i].a - Lines; + b = df->changes[j-1].b + Lines; + c = df->changes[i].c - Lines; + d = df->changes[j-1].d + Lines; if(a < 1) a = 1; if(c < 1) c = 1; - if(b > len[0]) - b = len[0]; - if(d > len[1]) - d = len[1]; + if(b > df->len[0]) + b = df->len[0]; + if(d > df->len[1]) + d = df->len[1]; if(mode == 'a'){ a = 1; - b = len[0]; + b = df->len[0]; c = 1; - d = len[1]; - j = nchanges; + d = df->len[1]; + j = df->nchanges; } if(mode == 'u'){ if(!hdr){ - Bprint(&stdout, "--- %s\n", file1); - Bprint(&stdout, "+++ %s\n", file2); + Bprint(&stdout, "--- %s\n", df->file1); + Bprint(&stdout, "+++ %s\n", df->file2); hdr = 1; } - Bprint(&stdout, "@@ -%d,%d +%d,%d @@\n", a, b-a+1, c, d-c+1); + Bprint(&stdout, "@@ -%lld,%lld +%lld,%lld @@\n", a, b-a+1, c, d-c+1); }else{ - Bprint(&stdout, "%s:", file1); + Bprint(&stdout, "%s:", df->file1); range(a, b, ","); Bprint(&stdout, " - "); - Bprint(&stdout, "%s:", file2); + Bprint(&stdout, "%s:", df->file2); range(c, d, ","); Bputc(&stdout, '\n'); } at = a; for(; iixold, at, df->changes[i].a-1, df->input[0], mode == 'u' ? " " : " "); + fetch(df, df->ixold, df->changes[i].a, df->changes[i].b, df->input[0], mode == 'u' ? "-" : "- "); + fetch(df, df->ixnew, df->changes[i].c, df->changes[i].d, df->input[1], mode == 'u' ? "+" : "+ "); + at = df->changes[i].b+1; } - fetch(ixold, at, b, input[0], mode == 'u' ? " " : " "); + fetch(df, df->ixold, at, b, df->input[0], mode == 'u' ? " " : " "); } - nchanges = 0; + df->nchanges = 0; } --- a/sys/src/cmd/diff/diffreg.c +++ b/sys/src/cmd/diff/diffreg.c @@ -66,30 +66,7 @@ * 3*(number of k-candidates installed), typically about * 6n words for files of length n. */ -typedef struct Cand Cand; -struct Cand { - int x; - int y; - int pred; -}; - -Cand cand; -Line *file[2], line; -int len[2]; -int binary; -Line *sfile[2]; /*shortened by pruning common prefix and suffix*/ -int slen[2]; -int pref, suff; /*length of prefix and suffix*/ -int *class; /*will be overlaid on file[0]*/ -int *member; /*will be overlaid on file[1]*/ -int *klist; /*will be overlaid on file[0] after class*/ -Cand *clist; /* merely a free storage pot for candidates */ -int clen; -int *J; /*will be overlaid on class*/ -long *ixold; /*will be overlaid on klist*/ -long *ixnew; /*will be overlaid on file[1]*/ - static void sort(Line *a, int n) /*shellsort CACM #201*/ { @@ -137,21 +114,21 @@ } static void -prune(void) +prune(Diff *d) { int i,j; - for(pref=0;prefpref = 0; d->pref < d->len[0] && d->pref < d->len[1] && + d->file[0][d->pref+1].value == d->file[1][d->pref+1].value; + d->pref++) ; + for(d->suff=0; d->suff < d->len[0] - d->pref && d->suff < d->len[1] - d->pref && + d->file[0][d->len[0] - d->suff].value == d->file[1][d->len[1] - d->suff].value; + d->suff++) ; for(j=0;j<2;j++) { - sfile[j] = file[j]+pref; - slen[j] = len[j]-pref-suff; - for(i=0;i<=slen[j];i++) - sfile[j][i].serial = i; + d->sfile[j] = d->file[j]+d->pref; + d->slen[j] = d->len[j]-d->pref-d->suff; + for(i=0;i<=d->slen[j];i++) + d->sfile[j][i].serial = i; } } @@ -184,30 +161,30 @@ } static int -newcand(int x, int y, int pred) +newcand(Diff *d, int x, int y, int pred) { Cand *q; - clist = erealloc(clist, (clen+1)*sizeof(Cand)); - q = clist + clen; + d->clist = erealloc(d->clist, (d->clen+1)*sizeof(Cand)); + q = d->clist + d->clen; q->x = x; q->y = y; q->pred = pred; - return clen++; + return d->clen++; } static int -search(int *c, int k, int y) +search(Diff *d, int *c, int k, int y) { int i, j, l; int t; - if(clist[c[k]].y < y) /*quick look for typical case*/ + if(d->clist[c[k]].y < y) /*quick look for typical case*/ return k+1; i = 0; j = k+1; while((l=(i+j)/2) > i) { - t = clist[c[l]].y; + t = d->clist[c[l]].y; if(t > y) j = l; else if(t < y) @@ -219,7 +196,7 @@ } static int -stone(int *a, int n, int *b, int *c) +stone(Diff *d, int *a, int n, int *b, int *c) { int i, k,y; int j, l; @@ -227,7 +204,7 @@ int oldl; k = 0; - c[0] = newcand(0,0,0); + c[0] = newcand(d, 0, 0, 0); for(i=1; i<=n; i++) { j = a[i]; if(j==0) @@ -236,20 +213,20 @@ oldl = 0; oldc = c[0]; do { - if(y <= clist[oldc].y) + if(y <= d->clist[oldc].y) continue; - l = search(c, k, y); + l = search(d, c, k, y); if(l!=oldl+1) oldc = c[l-1]; if(l<=k) { - if(clist[c[l]].y <= y) + if(d->clist[c[l]].y <= y) continue; tc = c[l]; - c[l] = newcand(i,y,oldc); + c[l] = newcand(d, i, y, oldc); oldc = tc; oldl = l; } else { - c[l] = newcand(i,y,oldc); + c[l] = newcand(d, i,y,oldc); k++; break; } @@ -259,61 +236,23 @@ } static void -unravel(int p) +unravel(Diff *d, int p) { int i; Cand *q; - for(i=0; i<=len[0]; i++) { - if (i <= pref) - J[i] = i; - else if (i > len[0]-suff) - J[i] = i+len[1]-len[0]; + for(i=0; i<=d->len[0]; i++) { + if (i <= d->pref) + d->J[i] = i; + else if (i > d->len[0]-d->suff) + d->J[i] = i+d->len[1] - d->len[0]; else - J[i] = 0; + d->J[i] = 0; } - for(q=clist+p;q->y!=0;q=clist+q->pred) - J[q->x+pref] = q->y+pref; + for(q=d->clist+p; q->y != 0; q= d->clist + q->pred) + d->J[q->x+d->pref] = q->y+d->pref; } -static void -output(void) -{ - int m, i0, i1, j0, j1; - - m = len[0]; - J[0] = 0; - J[m+1] = len[1]+1; - if (mode != 'e') { - for (i0 = 1; i0 <= m; i0 = i1+1) { - while (i0 <= m && J[i0] == J[i0-1]+1) - i0++; - j0 = J[i0-1]+1; - i1 = i0-1; - while (i1 < m && J[i1+1] == 0) - i1++; - j1 = J[i1+1]-1; - J[i1] = j1; - change(i0, i1, j0, j1); - } - } else { - for (i0 = m; i0 >= 1; i0 = i1-1) { - while (i0 >= 1 && J[i0] == J[i0+1]-1 && J[i0]) - i0--; - j0 = J[i0+1]-1; - i1 = i0+1; - while (i1 > 1 && J[i1-1] == 0) - i1--; - j1 = J[i1-1]+1; - J[i1] = j1; - change(i1 , i0, j1, j0); - } - } - if (m == 0) - change(1, 0, 1, len[1]); - flushchanges(); -} - #define BUF 4096 static int cmp(Biobuf* b1, Biobuf* b2) @@ -361,21 +300,20 @@ } void -diffreg(char *f, char *fo, char *t, char *to) +calcdiff(Diff *d, char *f, char *fo, char *t, char *to) { Biobuf *b0, *b1; int k; - binary = 0; - b0 = prepare(0, f, fo); + b0 = prepare(d, 0, f, fo); if (!b0) return; - b1 = prepare(1, t, to); + b1 = prepare(d, 1, t, to); if (!b1) { Bterm(b0); return; } - if (binary){ + if (d->binary){ // could use b0 and b1 but this is simpler. if (cmp(b0, b1)) print("binary files %s %s differ\n", f, t); @@ -383,38 +321,91 @@ Bterm(b1); return; } - clen = 0; - prune(); - sort(sfile[0], slen[0]); - sort(sfile[1], slen[1]); + d->clen = 0; + prune(d); + sort(d->sfile[0], d->slen[0]); + sort(d->sfile[1], d->slen[1]); - member = (int *)file[1]; - equiv(sfile[0], slen[0], sfile[1], slen[1], member); - member = erealloc(member, (slen[1]+2)*sizeof(int)); + d->member = (int *)d->file[1]; + equiv(d->sfile[0], d->slen[0], d->sfile[1], d->slen[1], d->member); + d->member = erealloc(d->member, (d->slen[1]+2)*sizeof(int)); - class = (int *)file[0]; - unsort(sfile[0], slen[0], class); - class = erealloc(class, (slen[0]+2)*sizeof(int)); + d->class = (int *)d->file[0]; + unsort(d->sfile[0], d->slen[0], d->class); + d->class = erealloc(d->class, (d->slen[0]+2)*sizeof(int)); - klist = emalloc((slen[0]+2)*sizeof(int)); - clist = emalloc(sizeof(Cand)); - k = stone(class, slen[0], member, klist); - free(member); - free(class); + d->klist = emalloc((d->slen[0]+2)*sizeof(int)); + d->clist = emalloc(sizeof(Cand)); + k = stone(d, d->class, d->slen[0], d->member, d->klist); + free(d->member); + free(d->class); - J = emalloc((len[0]+2)*sizeof(int)); - unravel(klist[k]); - free(clist); - free(klist); + d->J = emalloc((d->len[0]+2)*sizeof(int)); + unravel(d, d->klist[k]); + free(d->clist); + free(d->klist); - ixold = emalloc((len[0]+2)*sizeof(long)); - ixnew = emalloc((len[1]+2)*sizeof(long)); + d->ixold = emalloc((d->len[0]+2)*sizeof(long)); + d->ixnew = emalloc((d->len[1]+2)*sizeof(long)); Bseek(b0, 0, 0); Bseek(b1, 0, 0); - check(b0, b1); - output(); - free(J); - free(ixold); - free(ixnew); - Bterm(b0); - Bterm(b1); + check(d, b0, b1); +} + +static void +output(Diff *d) +{ + int m, i0, i1, j0, j1; + + m = d->len[0]; + d->J[0] = 0; + d->J[m+1] = d->len[1]+1; + if (mode != 'e') { + for (i0 = 1; i0 <= m; i0 = i1+1) { + while (i0 <= m && d->J[i0] == d->J[i0-1]+1) + i0++; + j0 = d->J[i0-1]+1; + i1 = i0-1; + while (i1 < m && d->J[i1+1] == 0) + i1++; + j1 = d->J[i1+1]-1; + d->J[i1] = j1; + change(d, i0, i1, j0, j1); + } + } else { + for (i0 = m; i0 >= 1; i0 = i1-1) { + while (i0 >= 1 && d->J[i0] == d->J[i0+1]-1 && d->J[i0]) + i0--; + j0 = d->J[i0+1]-1; + i1 = i0+1; + while (i1 > 1 && d->J[i1-1] == 0) + i1--; + j1 = d->J[i1-1]+1; + d->J[i1] = j1; + change(d, i1 , i0, j1, j0); + } + } + if (m == 0) + change(d, 1, 0, 1, d->len[1]); + flushchanges(d); +} + +void +diffreg(char *f, char *fo, char *t, char *to) +{ + Diff d; + + memset(&d, 0, sizeof(d)); + calcdiff(&d, f, fo, t, to); + output(&d); + freediff(&d); +} + +void +freediff(Diff *d) +{ + Bterm(d->input[0]); + Bterm(d->input[1]); + free(d->J); + free(d->ixold); + free(d->ixnew); } --- /dev/null +++ b/sys/src/cmd/diff/merge3.c @@ -1,0 +1,169 @@ +#include +#include +#include +#include "diff.h" + +static int +changecmp(void *a, void *b) +{ + return ((Change*)a)->a - ((Change*)b)->a; +} + +static void +addchange(Diff *df, int a, int b, int c, int d) +{ + Change *ch; + + if (a > b && c > d) + return; + if(df->nchanges%1024 == 0) + df->changes = erealloc(df->changes, (df->nchanges+1024)*sizeof(df->changes[0])); + ch = &df->changes[df->nchanges++]; + ch->a = a; + ch->b = b; + ch->c = c; + ch->d = d; +} + +static void +collect(Diff *d) +{ + int m, i0, i1, j0, j1; + + m = d->len[0]; + d->J[0] = 0; + d->J[m+1] = d->len[1]+1; + for (i0 = m; i0 >= 1; i0 = i1-1) { + while (i0 >= 1 && d->J[i0] == d->J[i0+1]-1 && d->J[i0]) + i0--; + j0 = d->J[i0+1]-1; + i1 = i0+1; + while (i1 > 1 && d->J[i1-1] == 0) + i1--; + j1 = d->J[i1-1]+1; + d->J[i1] = j1; + addchange(d, i1 , i0, j1, j0); + } + if (m == 0) + change(d, 1, 0, 1, d->len[1]); + qsort(d->changes, d->nchanges, sizeof(Change), changecmp); +} + +static int +overlaps(Change *l, Change *r) +{ + if(l == nil || r == nil) + return 0; + if(l->a <= r->a) + return l->b >= r->a; + else + return r->b >= l->a; +} + +char* +merge(Diff *l, Diff *r) +{ + int il, ir, x, y, δ; + Change *lc, *rc; + char *status; + vlong ln; + + il = 0; + ir = 0; + ln = 0; + status = nil; + collect(l); + collect(r); + while(il < l->nchanges || ir < r->nchanges){ + lc = nil; + rc = nil; + if(il < l->nchanges) + lc = &l->changes[il]; + if(ir < r->nchanges) + rc = &r->changes[ir]; + if(overlaps(lc, rc)){ + /* + * align the edges of the chunks + */ + if(lc->a < rc->a){ + x = lc->c; + δ = rc->a - lc->a; + rc->a -= δ; + rc->c -= δ; + }else{ + x = rc->c; + δ = lc->a - rc->a; + lc->a -= δ; + lc->c -= δ; + } + if(lc->b > rc->b){ + y = lc->d; + δ = lc->b - rc->b; + rc->b += δ; + rc->d += δ; + }else{ + y = rc->d; + δ = rc->b - lc->b; + lc->b += δ; + lc->d += δ; + } + fetch(l, l->ixold, ln, x-1, l->input[0], ""); + Bprint(&stdout, "<<<<<<<<<< %s\n", l->file2); + fetch(l, l->ixnew, lc->c, lc->d, l->input[1], ""); + Bprint(&stdout, "========== original\n"); + fetch(l, l->ixold, x, y, l->input[0], ""); + Bprint(&stdout, "========== %s\n", r->file2); + fetch(r, r->ixnew, rc->c, rc->d, r->input[1], ""); + Bprint(&stdout, ">>>>>>>>>>\n"); + ln = y+1; + il++; + ir++; + status = "conflict"; + }else if(rc == nil || (lc != nil && lc->a < rc->a)){ + fetch(l, l->ixold, ln, lc->a-1, l->input[0], ""); + fetch(l, l->ixnew, lc->c, lc->d, l->input[1], ""); + ln = lc->b+1; + il++; + }else if(lc == nil || (rc != nil && rc->a < lc->a)){ + fetch(l, l->ixold, ln, rc->a-1, l->input[0], ""); + fetch(r, r->ixnew, rc->c, rc->d, r->input[1], ""); + ln = rc->b+1; + ir++; + }else + abort(); + } + if(ln < l->len[0]) + fetch(l, l->ixold, ln, l->len[0], l->input[0], ""); + return status; +} + +void +usage(void) +{ + fprint(2, "usage: %s theirs base ours\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + Diff l, r; + char *x; + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + if(argc != 3) + usage(); + Binit(&stdout, 1, OWRITE); + memset(&l, 0, sizeof(l)); + memset(&r, 0, sizeof(r)); + calcdiff(&l, argv[1], argv[1], argv[0], argv[0]); + calcdiff(&r, argv[1], argv[1], argv[2], argv[2]); + x = merge(&l, &r); + freediff(&l); + freediff(&r); + exits(x); +} --- a/sys/src/cmd/diff/mkfile +++ b/sys/src/cmd/diff/mkfile @@ -1,13 +1,13 @@ < /$objtype/mkfile -TARG=diff +TARG=diff merge3 OFILES=\ diffdir.$O\ diffio.$O\ diffreg.$O\ - main.$O\ + util.$O HFILES=diff.h BIN=/$objtype/bin - 2, +then the corresponding range is determined by the last two addresses in +the +.IR n- tuple. +If only one address is expected, then the last address is used. + +Each address in a comma-delimited range is interpreted relative to the +current address. In a semi-colon-delimited range, the first address is +used to set the current address, and the second address is interpreted +relative to the first. + + +The following address symbols are recognized. + +.TP 8 +\&. +The current line (address) in the buffer. + +.TP 8 +$ +The last line in the buffer. + +.TP 8 +n +The +.IR n th, +line in the buffer +where +.I n +is a number in the range +.I [0,$]. + +.TP 8 +- or ^ +The previous line. +This is equivalent to +.I -1 +and may be repeated with cumulative effect. + +.TP 8 +-\fIn\fR or ^\fIn\fR +The +.IR n th +previous line, where +.I n +is a non-negative number. + +.TP 8 ++ +The +next line. +This is equivalent to +.I +1 +and may be repeated with cumulative effect. + +.TP 8 ++\fIn\fR or whitespace\fIn\fR +The +.IR n th +next line, where +.I n +is a non-negative number. +.I whitespace +followed by a number +.I n +is interpreted as +.IR +n . + +.TP 8 +, \fRor\fB % +The first through last lines in the buffer. This is equivalent to +the address range +.I 1,$. + +.TP 8 +; +The +current through last lines in the buffer. This is equivalent to +the address range +.I .,$. + +.TP 8 +.RI / re/ +The +next line containing the regular expression +.IR re . +The search wraps to the beginning of the buffer and continues down to the +current line, if necessary. +// repeats the last search. + +.TP 8 +.RI ? re? +The +previous line containing the regular expression +.IR re . +The search wraps to the end of the buffer and continues up to the +current line, if necessary. +?? repeats the last search. + +.TP 8 +.RI \' lc +The +line previously marked by a +.I `k' +(mark) command, where +.I lc +is a lower case letter. + +.SS REGULAR EXPRESSIONS +Regular expressions are patterns used in selecting text. +For example, the +.B ed +command +.sp +.RS +g/\fIstring\fR/ +.RE +.sp +prints all lines containing +.IR string . +Regular expressions are also +used by the +.I `s' +command for selecting old text to be replaced with new. + +In addition to a specifying string literals, regular expressions can +represent +classes of strings. Strings thus represented are said to be matched +by the corresponding regular expression. +If it is possible for a regular expression +to match several strings in a line, then the left-most longest match is +the one selected. + +The following symbols are used in constructing regular expressions: + +.TP 8 +c +Any character +.I c +not listed below, including `{', '}', `(', `)', `<' and `>', +matches itself. + +.TP 8 +\fR\e\fIc\fR +Any backslash-escaped character +.IR c , +except for `{', '}', `(', `)', `<' and `>', +matches itself. + +.TP 8 +\fR.\fR +Matches any single character. + +.TP 8 +.I [char-class] +Matches any single character in +.IR char-class . +To include a `]' +in +.IR char-class , +it must be the first character. +A range of characters may be specified by separating the end characters +of the range with a `-', e.g., `a-z' specifies the lower case characters. +The following literal expressions can also be used in +.I char-class +to specify sets of characters: +.sp +\ \ [:alnum:]\ \ [:cntrl:]\ \ [:lower:]\ \ [:space:] +.PD 0 +\ \ [:alpha:]\ \ [:digit:]\ \ [:print:]\ \ [:upper:] +.PD 0 +\ \ [:blank:]\ \ [:graph:]\ \ [:punct:]\ \ [:xdigit:] +.sp +If `-' appears as the first or last +character of +.IR char-class , +then it matches itself. +All other characters in +.I char-class +match themselves. +.sp +Patterns in +.I char-class +of the form: +.sp +\ \ [.\fIcol-elm\fR.] or, +.PD 0 +\ \ [=\fIcol-elm\fR=] +.sp +where +.I col-elm +is a +.I collating element +are interpreted according to +.IR locale (5) +(not currently supported). +See +.IR regex (3) +for an explanation of these constructs. + +.TP 8 +[^\fIchar-class\fR] +Matches any single character, other than newline, not in +.IR char-class . +.IR char-class +is defined +as above. + +.TP 8 +^ +If `^' is the first character of a regular expression, then it +anchors the regular expression to the beginning of a line. +Otherwise, it matches itself. + +.TP 8 +$ +If `$' is the last character of a regular expression, it +anchors the regular expression to the end of a line. +Otherwise, it matches itself. + +.TP 8 +\fR\e<\fR +Anchors the single character regular expression or subexpression +immediately following it to the beginning of a word. +(This may not be available) + +.TP 8 +\fR\e>\fR +Anchors the single character regular expression or subexpression +immediately following it to the end of a word. +(This may not be available) + +.TP 8 +\fR\e(\fIre\fR\e)\fR +Defines a subexpression +.IR re . +Subexpressions may be nested. +A subsequent backreference of the form \fI`\en'\fR, where +.I n +is a number in the range [1,9], expands to the text matched by the +.IR n th +subexpression. +For example, the regular expression `\e(.*\e)\e1' matches any string +consisting of identical adjacent substrings. +Subexpressions are ordered relative to +their left delimiter. + +.TP 8 +* +Matches the single character regular expression or subexpression +immediately preceding it zero or more times. If '*' is the first +character of a regular expression or subexpression, then it matches +itself. The `*' operator sometimes yields unexpected results. +For example, the regular expression `b*' matches the beginning of +the string `abbb' (as opposed to the substring `bbb'), since a null match +is the only left-most match. + +.TP 8 +\fR\e{\fIn,m\fR\e}\fR or \fR\e{\fIn,\fR\e}\fR or \fR\e{\fIn\fR\e}\fR +Matches the single character regular expression or subexpression +immediately preceding it at least +.I n +and at most +.I m +times. +If +.I m +is omitted, then it matches at least +.I n +times. +If the comma is also omitted, then it matches exactly +.I n +times. + +.LP +Additional regular expression operators may be defined depending on the +particular +.IR regex (3) +implementation. + +.SS COMMANDS +All +.B ed +commands are single characters, though some require additonal parameters. +If a command's parameters extend over several lines, then +each line except for the last +must be terminated with a backslash (\\). + +In general, at most one command is allowed per line. +However, most commands accept a print suffix, which is any of +.I `p' +(print), +.I `l' +(list) , +or +.I `n' +(enumerate), +to print the last line affected by the command. + +An interrupt (typically ^C) has the effect of aborting the current command +and returning the editor to command mode. + +.B ed +recognizes the following commands. The commands are shown together with +the default address or address range supplied if none is +specified (in parenthesis). + +.TP 8 +(.)a +Appends text to the buffer after the addressed line. +Text is entered in input mode. +The current address is set to last line entered. + +.TP 8 +(.,.)c +Changes lines in the buffer. The addressed lines are deleted +from the buffer, and text is appended in their place. +Text is entered in input mode. +The current address is set to last line entered. + +.TP 8 +(.,.)d +Deletes the addressed lines from the buffer. +If there is a line after the deleted range, then the current address is set +to this line. Otherwise the current address is set to the line +before the deleted range. + +.TP 8 +.RI e \ file +Edits +.IR file , +and sets the default filename. +If +.I file +is not specified, then the default filename is used. +Any lines in the buffer are deleted before +the new file is read. +The current address is set to the last line read. + +.TP 8 +.RI e \ !command +Edits the standard output of +.IR `!command' , +(see +.RI ! command +below). +The default filename is unchanged. +Any lines in the buffer are deleted before the output of +.I command +is read. +The current address is set to the last line read. + +.TP 8 +.RI E \ file +Edits +.I file +unconditionally. +This is similar to the +.I e +command, +except that unwritten changes are discarded without warning. +The current address is set to the last line read. + +.TP 8 +.RI f \ file +Sets the default filename to +.IR file . +If +.I file +is not specified, then the default unescaped filename is printed. + +.TP 8 +.RI (1,$)g /re/command-list +Applies +.I command-list +to each of the addressed lines matching a regular expression +.IR re . +The current address is set to the +line currently matched before +.I command-list +is executed. +At the end of the +.I `g' +command, the current address is set to the last line affected by +.IR command-list . + +Each command in +.I command-list +must be on a separate line, +and every line except for the last must be terminated by a backslash +(\\). +Any commands are allowed, except for +.IR `g' , +.IR `G' , +.IR `v' , +and +.IR `V' . +A newline alone in +.I command-list +is equivalent to a +.I `p' +command. + +.TP 8 +.RI (1,$)G /re/ +Interactively edits the addressed lines matching a regular expression +.IR re. +For each matching line, +the line is printed, +the current address is set, +and the user is prompted to enter a +.IR command-list . +At the end of the +.I `G' +command, the current address +is set to the last line affected by (the last) +.IR command-list . + +The format of +.I command-list +is the same as that of the +.I `g' +command. A newline alone acts as a null command list. +A single `&' repeats the last non-null command list. + +.TP 8 +H +Toggles the printing of error explanations. +By default, explanations are not printed. +It is recommended that ed scripts begin with this command to +aid in debugging. + +.TP 8 +h +Prints an explanation of the last error. + +.TP 8 +(.)i +Inserts text in the buffer before the current line. +Text is entered in input mode. +The current address is set to the last line entered. + +.TP 8 +(.,.+1)j +Joins the addressed lines. The addressed lines are +deleted from the buffer and replaced by a single +line containing their joined text. +The current address is set to the resultant line. + +.TP 8 +.RI (.)k lc +Marks a line with a lower case letter +.IR lc . +The line can then be addressed as +.I 'lc +(i.e., a single quote followed by +.I lc +) in subsequent commands. The mark is not cleared until the line is +deleted or otherwise modified. + +.TP 8 +(.,.)l +Prints the addressed lines unambiguously. +If a single line fills for than one screen (as might be the case +when viewing a binary file, for instance), a `--More--' +prompt is printed on the last line. +.B ed +waits until the RETURN key is pressed +before displaying the next screen. +The current address is set to the last line +printed. + +.TP 8 +(.,.)m(.) +Moves lines in the buffer. The addressed lines are moved to after the +right-hand destination address, which may be the address +.IR 0 +(zero). +The current address is set to the +last line moved. + +.TP 8 +(.,.)n +Prints the addressed lines along with +their line numbers. The current address is set to the last line +printed. + +.TP 8 +(.,.)p +Prints the addressed lines. The current address is set to the last line +printed. + +.TP 8 +P +Toggles the command prompt on and off. +Unless a prompt was specified by with command-line option +\fI-p string\fR, the command prompt is by default turned off. + +.TP 8 +q +Quits ed. + +.TP 8 +Q +Quits ed unconditionally. +This is similar to the +.I q +command, +except that unwritten changes are discarded without warning. + +.TP 8 +.RI ($)r \ file +Reads +.I file +to after the addressed line. If +.I file +is not specified, then the default +filename is used. If there was no default filename prior to the command, +then the default filename is set to +.IR file . +Otherwise, the default filename is unchanged. +The current address is set to the last line read. + +.TP 8 +.RI ($)r \ !command +Reads +to after the addressed line +the standard output of +.IR `!command' , +(see the +.RI ! command +below). +The default filename is unchanged. +The current address is set to the last line read. + +.HP +.RI (.,.)s /re/replacement/ +.PD 0 +.HP +.RI (.,.)s /re/replacement/\fRg\fR +.HP +.RI (.,.)s /re/replacement/n +.br +Replaces text in the addressed lines +matching a regular expression +.I re +with +.IR replacement . +By default, only the first match in each line is replaced. +If the +.I `g' +(global) suffix is given, then every match to be replaced. +The +.I `n' +suffix, where +.I n +is a postive number, causes only the +.IR n th +match to be replaced. +It is an error if no substitutions are performed on any of the addressed +lines. +The current address is set the last line affected. + +.I re +and +.I replacement +may be delimited by any character other than space and newline +(see the +.I `s' +command below). +If one or two of the last delimiters is omitted, then the last line +affected is printed as though the print suffix +.I `p' +were specified. + + +An unescaped `&' in +.I replacement +is replaced by the currently matched text. +The character sequence +\fI`\em'\fR, +where +.I m +is a number in the range [1,9], is replaced by the +.IR m th +backreference expression of the matched text. +If +.I replacement +consists of a single `%', then +.I replacement +from the last substitution is used. +Newlines may be embedded in +.I replacement +if they are escaped with a backslash (\\). + +.TP 8 +(.,.)s +Repeats the last substitution. +This form of the +.I `s' +command accepts a count suffix +.IR `n' , +or any combination of the characters +.IR `r' , +.IR `g' , +and +.IR `p' . +If a count suffix +.I `n' +is given, then only the +.IR n th +match is replaced. +The +.I `r' +suffix causes +the regular expression of the last search to be used instead of the +that of the last substitution. +The +.I `g' +suffix toggles the global suffix of the last substitution. +The +.I `p' +suffix toggles the print suffix of the last substitution +The current address is set to the last line affected. + +.TP 8 +(.,.)t(.) +Copies (i.e., transfers) the addressed lines to after the right-hand +destination address, which may be the address +.IR 0 +(zero). +The current address is set to the last line +copied. + +.TP 8 +u +Undoes the last command and restores the current address +to what it was before the command. +The global commands +.IR `g' , +.IR `G' , +.IR `v' , +and +.IR `V' . +are treated as a single command by undo. +.I `u' +is its own inverse. + +.TP 8 +.RI (1,$)v /pat/command-list +Applies +.I command-list +to each of the addressed lines not matching a regular expression +.IR re . +This is similar to the +.I `g' +command. + +.TP 8 +.RI (1,$)V /re/ +Interactively edits the addressed lines not matching a regular expression +.IR re. +This is similar to the +.I `G' +command. + +.TP 8 +.RI (1,$)w \ file +Writes the addressed lines to +.IR file . +Any previous contents of +.I file +is lost without warning. +If there is no default filename, then the default filename is set to +.IR file, +otherwise it is unchanged. If no filename is specified, then the default +filename is used. +The current address is unchanged. + +.TP 8 +.RI (1,$)wq \ file +Writes the addressed lines to +.IR file , +and then executes a +.I `q' +command. + +.TP 8 +.RI (1,$)w \ !command +Writes the addressed lines to the standard input of +.IR `!command' , +(see the +.RI ! command +below). +The default filename and current address are unchanged. + +.TP 8 +.RI (1,$)W \ file +Appends the addressed lines to the end of +.IR file . +This is similar to the +.I `w' +command, expect that the previous contents of file is not clobbered. +The current address is unchanged. + +.TP 8 +x +Prompts for an encryption key which is used in subsequent reads and +writes. If a newline alone is entered as the key, then encryption is +turned off. Otherwise, echoing is disabled while a key is read. +Encryption/decryption is done using the bdes(1) algorithm. + +.TP 8 +.RI (.+1)z n +Scrolls +.I n +lines at a time starting at addressed line. If +.I n +is not specified, then the current window size is used. +The current address is set to the last line printed. + +.TP 8 +.RI ! command +Executes +.I command +via +.IR sh (1). +If the first character of +.I command +is `!', then it is replaced by text of the +previous +.IR `!command' . +.B ed +does not process +.I command +for backslash (\\) escapes. +However, an unescaped +.I `%' +is replaced by the default filename. +When the shell returns from execution, a `!' +is printed to the standard output. +The current line is unchanged. + +.TP 8 +($)= +Prints the line number of the addressed line. + +.TP 8 +(.+1)newline +Prints the addressed line, and sets the current address to +that line. + +.SH FILES +.TP 20 +/tmp/ed.* +Buffer file +.PD 0 +.TP 20 +ed.hup +The file to which +.B ed +attempts to write the buffer if the terminal hangs up. + +.SH SEE ALSO + +.IR vi (1), +.IR sed (1), +.IR regex (3), +.IR bdes (1), +.IR sh (1). + +USD:12-13 + +B. W. Kernighan and P. J. Plauger, +.I Software Tools in Pascal , +Addison-Wesley, 1981. + +.SH LIMITATIONS +.B ed +processes +.I file +arguments for backslash escapes, i.e., in a filename, +any characters preceded by a backslash (\\) are +interpreted literally. + +If a text (non-binary) file is not terminated by a newline character, +then +.B ed +appends one on reading/writing it. In the case of a binary file, +.B ed +does not append a newline on reading/writing. + +per line overhead: 4 ints + +.SH DIAGNOSTICS +When an error occurs, +.B ed +prints a `?' and either returns to command mode +or exits if its input is from a script. +An explanation of the last error can be +printed with the +.I `h' +(help) command. + +Since the +.I `g' +(global) command masks any errors from failed searches and substitutions, +it can be used to perform conditional operations in scripts; e.g., +.sp +.RS +g/\fIold\fR/s//\fInew\fR/ +.RE +.sp +replaces any occurrences of +.I old +with +.IR new . +If the +.I `u' +(undo) command occurs in a global command list, then +the command list is executed only once. + +If diagnostics are not disabled, attempting to quit +.B ed +or edit another file before writing a modified buffer +results in an error. +If the command is entered a second time, it succeeds, +but any changes to the buffer are lost. --- /dev/null +++ b/sys/src/cmd/diff/test/diff-t11.2 @@ -1,0 +1,908 @@ +.\" $OpenBSD: t11.2,v 1.1 2003/07/21 20:16:21 otto Exp $ +.\" +.Dd May 2, 1993 +.Dt ED 1 +.Os +.Sh NAME +.Nm ed +.Nd text editor +.Sh SYNOPSIS +.Nm ed +.Op Fl +.Op Fl sx +.Op Fl p Ar string +.Op Ar file +.Sh DESCRIPTION +.Nm +is a line-oriented text editor. +It is used to create, display, modify, and otherwise manipulate text files. +If invoked with a +.Ar file +argument, then a copy of +.Ar file +is read into the editor's buffer. +Changes are made to this copy and not directly to +.Ar file +itself. +Upon quitting +.Nm ed , +any changes not explicitly saved with a +.Em w +command are lost. +.Pp +Editing is done in two distinct modes: +.Em command +and +.Em input . +When first invoked, +.Nm +is in command mode. +In this mode, commands are read from the standard input and +executed to manipulate the contents of the editor buffer. +.Pp +A typical command might look like: +.Bd -literal -offset indent +,s/old/new/g +.Ed +.Pp +which replaces all occurrences of the string +.Pa old +with +.Pa new . +.Pp +When an input command, such as +.Em a +(append), +.Em i +(insert), +or +.Em c +(change) is given, +.Nm +enters input mode. +This is the primary means of adding text to a file. +In this mode, no commands are available; +instead, the standard input is written directory to the editor buffer. +Lines consist of text up to and including a newline character. +Input mode is terminated by entering a single period +.Pq Ql \&. +on a line. +.Pp +All +.Nm +commands operate on whole lines or ranges of lines; e.g., +the +.Em d +command deletes lines; the +.Em m +command moves lines, and so on. +It is possible to modify only a portion of a line by means of replacement, +as in the example above. +However, even here, the +.Em s +command is applied to whole lines at a time. +.Pp +In general, +.Nm +commands consist of zero or more line addresses, followed by a single +character command and possibly additional parameters; i.e., +commands have the structure: +.Bd -literal -offset indent +[address [,address]]command[parameters] +.Ed +.Pp +The address(es) indicate the line or range of lines to be affected by the +command. +If fewer addresses are given than the command accepts, then +default addresses are supplied. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl +Same as the +.Fl s +option (deprecated). +.It Fl s +Suppress diagnostics. +This should be used if +.Nm +standard input is from a script. +.Fl s +flag. +.It Fl x +Prompt for an encryption key to be used in subsequent reads and writes +(see the +.Em x +command). +.It Fl p Ar string +Specifies a command prompt. +This may be toggled on and off with the +.Em P +command. +.It Ar file +Specifies the name of a file to read. +If +.Ar file +is prefixed with a +bang +.Pq Ql \&! , +then it is interpreted as a shell command. +In this case, what is read is the standard output of +.Ar file +executed via +.Xr sh 1 . +To read a file whose name begins with a bang, prefix the +name with a backslash +.Pq Ql \e . +The default filename is set to +.Ar file +only if it is not prefixed with a bang. +.El +.Ss LINE ADDRESSING +An address represents the number of a line in the buffer. +.Nm +maintains a +.Em current address +which is typically supplied to commands as the default address +when none is specified. +When a file is first read, the current address is set to the last line +of the file. +In general, the current address is set to the last line affected by a command. +.Pp +A line address is +constructed from one of the bases in the list below, optionally followed +by a numeric offset. +The offset may include any combination of digits, operators (i.e., +.Em + , +.Em - , +and +.Em ^ ) , +and whitespace. +Addresses are read from left to right, and their values are computed +relative to the current address. +.Pp +One exception to the rule that addresses represent line numbers is the +address +.Em 0 +(zero). +This means +.Dq before the first line , +and is legal wherever it makes sense. +.Pp +An address range is two addresses separated either by a comma or semi-colon. +The value of the first address in a range cannot exceed the +value of the second. +If only one address is given in a range, +then the second address is set to the given address. +If an +.Em n Ns No -tuple +of addresses is given where +.Em n > 2 , +then the corresponding range is determined by the last two addresses in the +.Em n Ns No -tuple. +If only one address is expected, then the last address is used. +.Pp +Each address in a comma-delimited range is interpreted relative to the +current address. +In a semi-colon-delimited range, the first address is +used to set the current address, and the second address is interpreted +relative to the first. +.Pp +The following address symbols are recognized: +.Bl -tag -width Ds +.It Em \&. +The current line (address) in the buffer. +.It Em $ +The last line in the buffer. +.It Em n +The +.Em n Ns No th +line in the buffer where +.Em n +is a number in the range +.Em [0,$] . +.It Em - No or Em ^ +The previous line. +This is equivalent to +.Em -1 +and may be repeated with cumulative effect. +.It Em -n No or Em ^n +The +.Em n Ns No th +previous line, where +.Em n +is a non-negative number. +.It Em + +The next line. +This is equivalent to +.Em +1 +and may be repeated with cumulative effect. +.It Em +n +The +.Em n Ns No th +next line, where +.Em n +is a non-negative number. +.It Em \&, No or Em % +The first through last lines in the buffer. +This is equivalent to the address range +.Em 1,$ . +.It Em \&; +The current through last lines in the buffer. +This is equivalent to the address range +.Em .,$ . +.It Em / Ns No re Ns Em / +The next line containing the regular expression +.Em re . +The search wraps to the beginning of the buffer and continues down to the +current line, if necessary. +.Em // +repeats the last search. +.It Em ? Ns No re Ns Em ? +The previous line containing the regular expression +.Em re . +The search wraps to the end of the buffer and continues up to the +current line, if necessary. +.Em ?? +repeats the last search. +.It Em \&\' Ns No lc +The line previously marked by a +.Em k +(mark) command, where +.Em lc +is a lower case letter. +.El +.Ss REGULAR EXPRESSIONS +Regular expressions are patterns used in selecting text. +For example, the +.Nm +command +.Bd -literal -offset indent +g/string/ +.Ed +.Pp +prints all lines containing +.Em string . +Regular expressions are also used by the +.Em s +command for selecting old text to be replaced with new. +.Pp +In addition to a specifying string literals, regular expressions can +represent classes of strings. +Strings thus represented are said to be matched by the +corresponding regular expression. +If it is possible for a regular expression to match several strings in +a line, then the leftmost longest match is the one selected. +.Pp +The following symbols are used in constructing regular expressions: +.Bl -tag -width Dsasdfsd +.It Em c +Any character +.Em c +not listed below, including +.Em { Ns No , +.Em } Ns No , +.Em \&( Ns No , +.Em \&) Ns No , +.Em < Ns No , +and +.Em > +matches itself. +.It Em \ec +Any backslash-escaped character +.Em c Ns No , +except for +.Em { Ns No , +.Em } Ns No , +.Em \&( Ns No , +.Em \&) Ns No , +.Em < Ns No , and +.Em > +matches itself. +.It Em \&. +Matches any single character. +.It Em [char-class] +Matches any single character in +.Em char-class . +To include a +.Ql \&] +in +.Em char-class Ns No , +it must be the first character. +A range of characters may be specified by separating the end characters +of the range with a +.Ql - ; +e.g., +.Em a-z +specifies the lower case characters. +The following literal expressions can also be used in +.Em char-class +to specify sets of characters: +.Pp +.Em \ \ [:alnum:]\ \ [:cntrl:]\ \ [:lower:]\ \ [:space:] +.Em \ \ [:alpha:]\ \ [:digit:]\ \ [:print:]\ \ [:upper:] +.Em \ \ [:blank:]\ \ [:graph:]\ \ [:punct:]\ \ [:xdigit:] +.Pp +If +.Ql - +appears as the first or last character of +.Em char-class Ns No , +then it matches itself. +All other characters in +.Em char-class +match themselves. +.Pp +Patterns in +.Em char-class +of the form +.Em [.col-elm.] No or Em [=col-elm=] +where +.Em col-elm +is a collating element are interpreted according to +.Xr locale 5 +(not currently supported). +See +.Xr regex 3 +for an explanation of these constructs. +.It Em [^char-class] +Matches any single character, other than newline, not in +.Em char-class Ns No . +.Em char-class +is defined as above. +.It Em ^ +If +.Em ^ +is the first character of a regular expression, then it +anchors the regular expression to the beginning of a line. +Otherwise, it matches itself. +.It Em $ +If +.Em $ +is the last character of a regular expression, +it anchors the regular expression to the end of a line. +Otherwise, it matches itself. +.It Em \e< +Anchors the single character regular expression or subexpression +immediately following it to the beginning of a word. +(This may not be available.) +.It Em \e> +Anchors the single character regular expression or subexpression +immediately following it to the end of a word. +(This may not be available.) +.It Em \e( Ns No re Ns Em \e) +Defines a subexpression +.Em re . +Subexpressions may be nested. +A subsequent backreference of the form +.Em \en Ns No , +where +.Em n +is a number in the range [1,9], expands to the text matched by the +.Em n Ns No th +subexpression. +For example, the regular expression +.Em \e(.*\e)\e1 +matches any string consisting of identical adjacent substrings. +Subexpressions are ordered relative to their left delimiter. +.It Em * +Matches the single character regular expression or subexpression +immediately preceding it zero or more times. +If +.Em * +is the first character of a regular expression or subexpression, +then it matches itself. +The +.Em * +operator sometimes yields unexpected results. +For example, the regular expression +.Em b* +matches the beginning of the string +.Em abbb +(as opposed to the substring +.Em bbb Ns No ), +since a null match is the only leftmost match. +.Sm off +.It Xo Em \e{ No n,m +.Em \e}\ \e{ No n, Em \e}\ +.Em \e{ No n Em \e} +.Xc +.Sm on +Matches the single character regular expression or subexpression +immediately preceding it at least +.Em n +and at most +.Em m +times. +If +.Em m +is omitted, then it matches at least +.Em n +times. +If the comma is also omitted, then it matches exactly +.Em n +times. +.El +.Pp +Additional regular expression operators may be defined depending on the +particular +.Xr regex 3 +implementation. +.Ss COMMANDS +All +.Nm +commands are single characters, though some require additional parameters. +If a command's parameters extend over several lines, then +each line except for the last must be terminated with a backslash +.Pq Ql \e . +.Pp +In general, at most one command is allowed per line. +However, most commands accept a print suffix, which is any of +.Em p No (print), +.Em l No (list), +or +.Em n No (enumerate), +to print the last line affected by the command. +.Pp +An interrupt (typically ^C) has the effect of aborting the current command +and returning the editor to command mode. +.Pp +.Nm +recognizes the following commands. +The commands are shown together with +the default address or address range supplied if none is +specified (in parentheses), and other possible arguments on the right. +.Bl -tag -width Dxxs +.It (.) Ns Em a +Appends text to the buffer after the addressed line. +Text is entered in input mode. +The current address is set to last line entered. +.It (.,.) Ns Em c +Changes lines in the buffer. +The addressed lines are deleted from the buffer, +and text is appended in their place. +Text is entered in input mode. +The current address is set to last line entered. +.It (.,.) Ns Em d +Deletes the addressed lines from the buffer. +If there is a line after the deleted range, then the current address is set +to this line. +Otherwise the current address is set to the line before the deleted range. +.It Em e No file +Edits +.Em file Ns No , +and sets the default filename. +If +.Em file +is not specified, then the default filename is used. +Any lines in the buffer are deleted before the new file is read. +The current address is set to the last line read. +.It Em e No !command +Edits the standard output of +.Em !command Ns No , +(see +.Em ! No command +below). +The default filename is unchanged. +Any lines in the buffer are deleted before the output of +.Em command +is read. +The current address is set to the last line read. +.It Em E No file +Edits +.Em file +unconditionally. +This is similar to the +.Em e +command, except that unwritten changes are discarded without warning. +The current address is set to the last line read. +.It Em f No file +Sets the default filename to +.Em file Ns No . +If +.Em file +is not specified, then the default unescaped filename is printed. +.It (1,$) Ns Em g Ns No /re/command-list +Applies +.Em command-list +to each of the addressed lines matching a regular expression +.Em re Ns No . +The current address is set to the line currently matched before +.Em command-list +is executed. +At the end of the +.Em g +command, the current address is set to the last line affected by +.Em command-list Ns No . +.Pp +Each command in +.Em command-list +must be on a separate line, +and every line except for the last must be terminated by +.Em \e No (backslash). +Any commands are allowed, except for +.Em g Ns No , +.Em G Ns No , +.Em v Ns No , +and +.Em V Ns No . +A newline alone in +.Em command-list +is equivalent to a +.Em p +command. +.It (1,$) Ns Em G Ns No /re/ +Interactively edits the addressed lines matching a regular expression +.Em re Ns No . +For each matching line, the line is printed, the current address is set, +and the user is prompted to enter a +.Em command-list Ns No . +At the end of the +.Em g +command, the current address is set to the last line affected by (the last) +.Em command-list Ns No . +.Pp +The format of +.Em command-list +is the same as that of the +.Em g +command. +A newline alone acts as a null command list. +A single +.Em & +repeats the last non-null command list. +.It Em H +Toggles the printing of error explanations. +By default, explanations are not printed. +It is recommended that +.Nm +scripts begin with this command to aid in debugging. +.It Em h +Prints an explanation of the last error. +.It (.) Ns Em i +Inserts text in the buffer before the current line. +Text is entered in input mode. +The current address is set to the last line entered. +.It (.,.+1) Ns Em j +Joins the addressed lines. +The addressed lines are deleted from the buffer and replaced by a single +line containing their joined text. +The current address is set to the resultant line. +.It (.) Ns Em klc +Marks a line with a lower case letter +.Em lc Ns No \&. +The line can then be addressed as +.Em \&'lc +(i.e., a single quote followed by +.Em lc Ns No ) +in subsequent commands. +The mark is not cleared until the line is deleted or otherwise modified. +.It (.,.) Ns Em l +Prints the addressed lines unambiguously. +If a single line fills more than one screen (as might be the case +when viewing a binary file, for instance), a +.Dq --More-- +prompt is printed on the last line. +.Nm +waits until the RETURN key is pressed before displaying the next screen. +The current address is set to the last line printed. +.It (.,.) Ns Em m Ns No (.) +Moves lines in the buffer. +The addressed lines are moved to after the +right-hand destination address, which may be the address +.Em 0 +(zero). +The current address is set to the last line moved. +.It (.,.) Ns Em n +Prints the addressed lines along with their line numbers. +The current address is set to the last line printed. +.It (.,.) Ns Em p +Prints the addressed lines. +The current address is set to the last line printed. +.It Em P +Toggles the command prompt on and off. +Unless a prompt was specified by with command-line option +.Fl p Ar string Ns No , +the command prompt is by default turned off. +.It Em q +Quits +.Nm ed . +.It Em Q +Quits +.Nm +unconditionally. +This is similar to the +.Em q +command, except that unwritten changes are discarded without warning. +.It ($) Ns Em r No file +Reads +.Em file +to after the addressed line. +If +.Em file +is not specified, then the default filename is used. +If there was no default filename prior to the command, +then the default filename is set to +.Em file Ns No . +Otherwise, the default filename is unchanged. +The current address is set to the last line read. +.It ($) Ns Em r No !command +Reads to after the addressed line the standard output of +.Em !command Ns No , +(see the +.Em ! +command below). +The default filename is unchanged. +The current address is set to the last line read. +.Sm off +.It Xo (.,.) Em s No /re/replacement/ , \ (.,.) +.Em s No /re/replacement/ Em g , No \ (.,.) +.Em s No /re/replacement/ Em n +.Xc +.Sm on +Replaces text in the addressed lines matching a regular expression +.Em re +with +.Em replacement Ns No . +By default, only the first match in each line is replaced. +If the +.Em g +(global) suffix is given, then every match to be replaced. +The +.Em n +suffix, where +.Em n +is a positive number, causes only the +.Em n Ns No th +match to be replaced. +It is an error if no substitutions are performed on any of the addressed +lines. +The current address is set the last line affected. +.Pp +.Em re +and +.Em replacement +may be delimited by any character other than space and newline +(see the +.Em s +command below). +If one or two of the last delimiters is omitted, then the last line +affected is printed as though the print suffix +.Em p +were specified. +.Pp +An unescaped +.Ql \e +in +.Em replacement +is replaced by the currently matched text. +The character sequence +.Em \em Ns No , +where +.Em m +is a number in the range [1,9], is replaced by the +.Em m Ns No th +backreference expression of the matched text. +If +.Em replacement +consists of a single +.Ql % , +then +.Em replacement +from the last substitution is used. +Newlines may be embedded in +.Em replacement +if they are escaped with a backslash +.Pq Ql \e . +.It (.,.) Ns Em s +Repeats the last substitution. +This form of the +.Em s +command accepts a count suffix +.Em n Ns No , +or any combination of the characters +.Em r Ns No , +.Em g Ns No , +and +.Em p Ns No . +If a count suffix +.Em n +is given, then only the +.Em n Ns No th +match is replaced. +The +.Em r +suffix causes +the regular expression of the last search to be used instead of the +that of the last substitution. +The +.Em g +suffix toggles the global suffix of the last substitution. +The +.Em p +suffix toggles the print suffix of the last substitution +The current address is set to the last line affected. +.It (.,.) Ns Em t Ns No (.) +Copies (i.e., transfers) the addressed lines to after the right-hand +destination address, which may be the address +.Em 0 +(zero). +The current address is set to the last line copied. +.It Em u +Undoes the last command and restores the current address +to what it was before the command. +The global commands +.Em g Ns No , +.Em G Ns No , +.Em v Ns No , +and +.Em V Ns No . +are treated as a single command by undo. +.Em u +is its own inverse. +.It (1,$) Ns Em v Ns No /re/command-list +Applies +.Em command-list +to each of the addressed lines not matching a regular expression +.Em re Ns No . +This is similar to the +.Em g +command. +.It (1,$) Ns Em V Ns No /re/ +Interactively edits the addressed lines not matching a regular expression +.Em re Ns No . +This is similar to the +.Em G +command. +.It (1,$) Ns Em w No file +Writes the addressed lines to +.Em file Ns No . +Any previous contents of +.Em file +is lost without warning. +If there is no default filename, then the default filename is set to +.Em file Ns No , +otherwise it is unchanged. +If no filename is specified, then the default filename is used. +The current address is unchanged. +.It (1,$) Ns Em wq No file +Writes the addressed lines to +.Em file Ns No , +and then executes a +.Em q +command. +.It (1,$) Ns Em w No !command +Writes the addressed lines to the standard input of +.Em !command Ns No , +(see the +.Em ! +command below). +The default filename and current address are unchanged. +.It (1,$) Ns Em W No file +Appends the addressed lines to the end of +.Em file Ns No . +This is similar to the +.Em w +command, expect that the previous contents of file is not clobbered. +The current address is unchanged. +.It Em x +Prompts for an encryption key which is used in subsequent reads and writes. +If a newline alone is entered as the key, then encryption is turned off. +Otherwise, echoing is disabled while a key is read. +Encryption/decryption is done using the +.Xr bdes 1 +algorithm. +.It (.+1) Ns Em z Ns No n +Scrolls +.Em n +lines at a time starting at addressed line. +If +.Em n +is not specified, then the current window size is used. +The current address is set to the last line printed. +.It ($) Ns Em = +Prints the line number of the addressed line. +.It (.+1) Ns Em newline +Prints the addressed line, and sets the current address to that line. +.It Em ! Ns No command +Executes +.Em command +via +.Xr sh 1 . +If the first character of +.Em command +is +.Em ! Ns No , +then it is replaced by text of the previous +.Em !command Ns No . +.Nm +does not process +.Em command +for +.Em \e +(backslash) escapes. +However, an unescaped +.Em % +is replaced by the default filename. +When the shell returns from execution, a +.Em ! +is printed to the standard output. +The current line is unchanged. +.El +.Sh LIMITATIONS +.Nm +processes +.Em file +arguments for backslash escapes, i.e., in a filename, +any characters preceded by a backslash +.Pq Ql \e +are interpreted literally. +.Pp +If a text (non-binary) file is not terminated by a newline character, +then +.Nm +appends one on reading/writing it. +In the case of a binary file, +.Nm +does not append a newline on reading/writing. +.Sh DIAGNOSTICS +When an error occurs, +.Nm +prints a +.Dq ? +and either returns to command mode or exits if its input is from a script. +An explanation of the last error can be printed with the +.Em h +(help) command. +.Pp +Since the +.Em g +(global) command masks any errors from failed searches and substitutions, +it can be used to perform conditional operations in scripts; e.g., +.Bd -literal -offset indent +g/old/s//new/ +.Ed +.Pp +replaces any occurrences of +.Em old +with +.Em new Ns No . +.Pp +If the +.Em u +(undo) command occurs in a global command list, then +the command list is executed only once. +.Pp +If diagnostics are not disabled, attempting to quit +.Nm +or edit another file before writing a modified buffer results in an error. +If the command is entered a second time, it succeeds, +but any changes to the buffer are lost. +.Sh FILES +.Bl -tag -width /tmp/ed.* -compact +.It Pa /tmp/ed.* +buffer file +.It Pa ed.hup +where +.Nm +attempts to write the buffer if the terminal hangs up +.El +.Sh SEE ALSO +.Xr bdes 1 , +.Xr sed 1 , +.Xr sh 1 , +.Xr vi 1 , +.Xr regex 3 +.Pp +USD:12-13 +.Rs +.%A B. W. Kernighan +.%A P. J. Plauger +.%B Software Tools in Pascal +.%O Addison-Wesley +.%D 1981 +.Re +.Sh HISTORY +An +.Nm +command appeared in +.At v1 . --- /dev/null +++ b/sys/src/cmd/diff/test/diff-t11.expected @@ -1,0 +1,1612 @@ +--- diff-t11.1 ++++ diff-t11.2 +@@ -1,1003 +1,908 @@ +-.\" $OpenBSD: t11.1,v 1.2 2007/11/27 16:22:12 martynas Exp $ +-.\" $NetBSD: ed.1,v 1.13 1995/03/21 09:04:38 cgd Exp $ ++.\" $OpenBSD: t11.2,v 1.1 2003/07/21 20:16:21 otto Exp $ + .\" +-.TH ED 1 "21 May 1993" +-.SH NAME +-.\" ed, red \- text editor +-ed \- text editor +-.SH SYNOPSIS +-ed [-] [-sx] [-p \fIstring\fR] [\fIfile\fR] +-.\" .LP +-.\" red [-] [-sx] [-p \fIstring\fR] [\fIfile\fR] +-.SH DESCRIPTION +-.B ed ++.Dd May 2, 1993 ++.Dt ED 1 ++.Os ++.Sh NAME ++.Nm ed ++.Nd text editor ++.Sh SYNOPSIS ++.Nm ed ++.Op Fl ++.Op Fl sx ++.Op Fl p Ar string ++.Op Ar file ++.Sh DESCRIPTION ++.Nm + is a line-oriented text editor. +-It is used to create, display, modify and otherwise manipulate text +-files. +-.\" .B red +-.\" is a restricted +-.\" .BR ed : +-.\" it can only edit files in the current +-.\" directory and cannot execute shell commands. +- ++It is used to create, display, modify, and otherwise manipulate text files. + If invoked with a +-.I file ++.Ar file + argument, then a copy of +-.I file ++.Ar file + is read into the editor's buffer. + Changes are made to this copy and not directly to +-.I file ++.Ar file + itself. + Upon quitting +-.BR ed , +-any changes not explicitly saved with a +-.I `w' ++.Nm ed , ++any changes not explicitly saved with a ++.Em w + command are lost. +- ++.Pp + Editing is done in two distinct modes: +-.I command ++.Em command + and +-.IR input . ++.Em input . + When first invoked, +-.B ed ++.Nm + is in command mode. +-In this mode commands are read from the standard input and ++In this mode, commands are read from the standard input and + executed to manipulate the contents of the editor buffer. ++.Pp + A typical command might look like: +-.sp +-.RS +-,s/\fIold\fR/\fInew\fR/g +-.RE +-.sp ++.Bd -literal -offset indent ++,s/old/new/g ++.Ed ++.Pp + which replaces all occurrences of the string +-.I old ++.Pa old + with +-.IR new . +- ++.Pa new . ++.Pp + When an input command, such as +-.I `a' ++.Em a + (append), +-.I `i' +-(insert) or +-.I `c' +-(change), is given, +-.B ed +-enters input mode. This is the primary means +-of adding text to a file. ++.Em i ++(insert), ++or ++.Em c ++(change) is given, ++.Nm ++enters input mode. ++This is the primary means of adding text to a file. + In this mode, no commands are available; +-instead, the standard input is written +-directly to the editor buffer. Lines consist of text up to and +-including a +-.IR newline +-character. +-Input mode is terminated by +-entering a single period (\fI.\fR) on a line. +- ++instead, the standard input is written directory to the editor buffer. ++Lines consist of text up to and including a newline character. ++Input mode is terminated by entering a single period ++.Pq Ql \&. ++on a line. ++.Pp + All +-.B ed ++.Nm + commands operate on whole lines or ranges of lines; e.g., + the +-.I `d' ++.Em d + command deletes lines; the +-.I `m' ++.Em m + command moves lines, and so on. + It is possible to modify only a portion of a line by means of replacement, +-as in the example above. However even here, the +-.I `s' ++as in the example above. ++However, even here, the ++.Em s + command is applied to whole lines at a time. +- ++.Pp + In general, +-.B ed ++.Nm + commands consist of zero or more line addresses, followed by a single + character command and possibly additional parameters; i.e., + commands have the structure: +-.sp +-.RS +-.I [address [,address]]command[parameters] +-.RE +-.sp ++.Bd -literal -offset indent ++[address [,address]]command[parameters] ++.Ed ++.Pp + The address(es) indicate the line or range of lines to be affected by the +-command. If fewer addresses are given than the command accepts, then ++command. ++If fewer addresses are given than the command accepts, then + default addresses are supplied. +- +-.SS OPTIONS +-.TP 8 +--s +-Suppresses diagnostics. This should be used if +-.BR ed 's ++.Pp ++The options are as follows: ++.Bl -tag -width Ds ++.It Fl ++Same as the ++.Fl s ++option (deprecated). ++.It Fl s ++Suppress diagnostics. ++This should be used if ++.Nm + standard input is from a script. +- +-.TP 8 +--x +-Prompts for an encryption key to be used in subsequent reads and writes ++.Fl s ++flag. ++.It Fl x ++Prompt for an encryption key to be used in subsequent reads and writes + (see the +-.I `x' ++.Em x + command). +- +-.TP 8 +-.RI \-p \ string +-Specifies a command prompt. This may be toggled on and off with the +-.I `P' ++.It Fl p Ar string ++Specifies a command prompt. ++This may be toggled on and off with the ++.Em P + command. +- +-.TP 8 +-.I file +-Specifies the name of a file to read. If +-.I file ++.It Ar file ++Specifies the name of a file to read. ++If ++.Ar file + is prefixed with a +-bang (!), then it is interpreted as a shell command. In this case, +-what is read is +-the standard output of +-.I file ++bang ++.Pq Ql \&! , ++then it is interpreted as a shell command. ++In this case, what is read is the standard output of ++.Ar file + executed via +-.IR sh (1). ++.Xr sh 1 . + To read a file whose name begins with a bang, prefix the +-name with a backslash (\\). ++name with a backslash ++.Pq Ql \e . + The default filename is set to +-.I file ++.Ar file + only if it is not prefixed with a bang. +- +-.SS LINE ADDRESSING ++.El ++.Ss LINE ADDRESSING + An address represents the number of a line in the buffer. +-.B ed ++.Nm + maintains a +-.I current address +-which is +-typically supplied to commands as the default address when none is specified. +-When a file is first read, the current address is set to the last line +-of the file. In general, the current address is set to the last line +-affected by a command. +- ++.Em current address ++which is typically supplied to commands as the default address ++when none is specified. ++When a file is first read, the current address is set to the last line ++of the file. ++In general, the current address is set to the last line affected by a command. ++.Pp + A line address is + constructed from one of the bases in the list below, optionally followed +-by a numeric offset. The offset may include any combination +-of digits, operators (i.e., +-.IR + , +-.I - ++by a numeric offset. ++The offset may include any combination of digits, operators (i.e., ++.Em + , ++.Em - , + and +-.IR ^ ) ++.Em ^ ) , + and whitespace. + Addresses are read from left to right, and their values are computed + relative to the current address. +- ++.Pp + One exception to the rule that addresses represent line numbers is the + address +-.I 0 ++.Em 0 + (zero). +-This means "before the first line," ++This means ++.Dq before the first line , + and is legal wherever it makes sense. +- +-An address range is two addresses separated either by a comma or +-semi-colon. The value of the first address in a range cannot exceed the +-value of the second. If only one address is given in a range, then +-the second address is set to the given address. If an +-.IR n- tuple ++.Pp ++An address range is two addresses separated either by a comma or semi-colon. ++The value of the first address in a range cannot exceed the ++value of the second. ++If only one address is given in a range, ++then the second address is set to the given address. ++If an ++.Em n Ns No -tuple + of addresses is given where +-.I n > 2, +-then the corresponding range is determined by the last two addresses in +-the +-.IR n- tuple. ++.Em n > 2 , ++then the corresponding range is determined by the last two addresses in the ++.Em n Ns No -tuple. + If only one address is expected, then the last address is used. +- ++.Pp + Each address in a comma-delimited range is interpreted relative to the +-current address. In a semi-colon-delimited range, the first address is ++current address. ++In a semi-colon-delimited range, the first address is + used to set the current address, and the second address is interpreted + relative to the first. +- +- +-The following address symbols are recognized. +- +-.TP 8 +-\&. ++.Pp ++The following address symbols are recognized: ++.Bl -tag -width Ds ++.It Em \&. + The current line (address) in the buffer. +- +-.TP 8 +-$ ++.It Em $ + The last line in the buffer. +- +-.TP 8 +-n ++.It Em n + The +-.IR n th, +-line in the buffer +-where +-.I n ++.Em n Ns No th ++line in the buffer where ++.Em n + is a number in the range +-.I [0,$]. +- +-.TP 8 +-- or ^ ++.Em [0,$] . ++.It Em - No or Em ^ + The previous line. + This is equivalent to +-.I -1 ++.Em -1 + and may be repeated with cumulative effect. +- +-.TP 8 +--\fIn\fR or ^\fIn\fR ++.It Em -n No or Em ^n + The +-.IR n th ++.Em n Ns No th + previous line, where +-.I n ++.Em n + is a non-negative number. +- +-.TP 8 +-+ +-The +-next line. ++.It Em + ++The next line. + This is equivalent to +-.I +1 ++.Em +1 + and may be repeated with cumulative effect. +- +-.TP 8 +-+\fIn\fR or whitespace\fIn\fR ++.It Em +n + The +-.IR n th ++.Em n Ns No th + next line, where +-.I n ++.Em n + is a non-negative number. +-.I whitespace +-followed by a number +-.I n +-is interpreted as +-.IR +n . +- +-.TP 8 +-, \fRor\fB % +-The first through last lines in the buffer. This is equivalent to +-the address range +-.I 1,$. +- +-.TP 8 +-; +-The +-current through last lines in the buffer. This is equivalent to +-the address range +-.I .,$. +- +-.TP 8 +-.RI / re/ +-The +-next line containing the regular expression +-.IR re . ++.It Em \&, No or Em % ++The first through last lines in the buffer. ++This is equivalent to the address range ++.Em 1,$ . ++.It Em \&; ++The current through last lines in the buffer. ++This is equivalent to the address range ++.Em .,$ . ++.It Em / Ns No re Ns Em / ++The next line containing the regular expression ++.Em re . + The search wraps to the beginning of the buffer and continues down to the + current line, if necessary. +-// repeats the last search. +- +-.TP 8 +-.RI ? re? +-The +-previous line containing the regular expression +-.IR re . ++.Em // ++repeats the last search. ++.It Em ? Ns No re Ns Em ? ++The previous line containing the regular expression ++.Em re . + The search wraps to the end of the buffer and continues up to the + current line, if necessary. +-?? repeats the last search. +- +-.TP 8 +-.RI \' lc +-The +-line previously marked by a +-.I `k' ++.Em ?? ++repeats the last search. ++.It Em \&\' Ns No lc ++The line previously marked by a ++.Em k + (mark) command, where +-.I lc ++.Em lc + is a lower case letter. +- +-.SS REGULAR EXPRESSIONS ++.El ++.Ss REGULAR EXPRESSIONS + Regular expressions are patterns used in selecting text. + For example, the +-.B ed ++.Nm + command +-.sp +-.RS +-g/\fIstring\fR/ +-.RE +-.sp ++.Bd -literal -offset indent ++g/string/ ++.Ed ++.Pp + prints all lines containing +-.IR string . +-Regular expressions are also +-used by the +-.I `s' ++.Em string . ++Regular expressions are also used by the ++.Em s + command for selecting old text to be replaced with new. +- ++.Pp + In addition to a specifying string literals, regular expressions can +-represent +-classes of strings. Strings thus represented are said to be matched +-by the corresponding regular expression. +-If it is possible for a regular expression +-to match several strings in a line, then the left-most longest match is +-the one selected. +- ++represent classes of strings. ++Strings thus represented are said to be matched by the ++corresponding regular expression. ++If it is possible for a regular expression to match several strings in ++a line, then the leftmost longest match is the one selected. ++.Pp + The following symbols are used in constructing regular expressions: +- +-.TP 8 +-c ++.Bl -tag -width Dsasdfsd ++.It Em c + Any character +-.I c +-not listed below, including `{', '}', `(', `)', `<' and `>', ++.Em c ++not listed below, including ++.Em { Ns No , ++.Em } Ns No , ++.Em \&( Ns No , ++.Em \&) Ns No , ++.Em < Ns No , ++and ++.Em > + matches itself. +- +-.TP 8 +-\fR\e\fIc\fR ++.It Em \ec + Any backslash-escaped character +-.IR c , +-except for `{', '}', `(', `)', `<' and `>', ++.Em c Ns No , ++except for ++.Em { Ns No , ++.Em } Ns No , ++.Em \&( Ns No , ++.Em \&) Ns No , ++.Em < Ns No , and ++.Em > + matches itself. +- +-.TP 8 +-\fR.\fR ++.It Em \&. + Matches any single character. +- +-.TP 8 +-.I [char-class] ++.It Em [char-class] + Matches any single character in +-.IR char-class . +-To include a `]' ++.Em char-class . ++To include a ++.Ql \&] + in +-.IR char-class , ++.Em char-class Ns No , + it must be the first character. + A range of characters may be specified by separating the end characters +-of the range with a `-', e.g., `a-z' specifies the lower case characters. ++of the range with a ++.Ql - ; ++e.g., ++.Em a-z ++specifies the lower case characters. + The following literal expressions can also be used in +-.I char-class ++.Em char-class + to specify sets of characters: +-.sp +-\ \ [:alnum:]\ \ [:cntrl:]\ \ [:lower:]\ \ [:space:] +-.PD 0 +-\ \ [:alpha:]\ \ [:digit:]\ \ [:print:]\ \ [:upper:] +-.PD 0 +-\ \ [:blank:]\ \ [:graph:]\ \ [:punct:]\ \ [:xdigit:] +-.sp +-If `-' appears as the first or last +-character of +-.IR char-class , ++.Pp ++.Em \ \ [:alnum:]\ \ [:cntrl:]\ \ [:lower:]\ \ [:space:] ++.Em \ \ [:alpha:]\ \ [:digit:]\ \ [:print:]\ \ [:upper:] ++.Em \ \ [:blank:]\ \ [:graph:]\ \ [:punct:]\ \ [:xdigit:] ++.Pp ++If ++.Ql - ++appears as the first or last character of ++.Em char-class Ns No , + then it matches itself. + All other characters in +-.I char-class ++.Em char-class + match themselves. +-.sp ++.Pp + Patterns in +-.I char-class +-of the form: +-.sp +-\ \ [.\fIcol-elm\fR.] or, +-.PD 0 +-\ \ [=\fIcol-elm\fR=] +-.sp ++.Em char-class ++of the form ++.Em [.col-elm.] No or Em [=col-elm=] + where +-.I col-elm +-is a +-.I collating element +-are interpreted according to +-.IR locale (5) ++.Em col-elm ++is a collating element are interpreted according to ++.Xr locale 5 + (not currently supported). + See +-.IR regex (3) ++.Xr regex 3 + for an explanation of these constructs. +- +-.TP 8 +-[^\fIchar-class\fR] ++.It Em [^char-class] + Matches any single character, other than newline, not in +-.IR char-class . +-.IR char-class +-is defined +-as above. +- +-.TP 8 +-^ +-If `^' is the first character of a regular expression, then it ++.Em char-class Ns No . ++.Em char-class ++is defined as above. ++.It Em ^ ++If ++.Em ^ ++is the first character of a regular expression, then it + anchors the regular expression to the beginning of a line. + Otherwise, it matches itself. +- +-.TP 8 +-$ +-If `$' is the last character of a regular expression, it +-anchors the regular expression to the end of a line. ++.It Em $ ++If ++.Em $ ++is the last character of a regular expression, ++it anchors the regular expression to the end of a line. + Otherwise, it matches itself. +- +-.TP 8 +-\fR\e<\fR ++.It Em \e< + Anchors the single character regular expression or subexpression + immediately following it to the beginning of a word. +-(This may not be available) +- +-.TP 8 +-\fR\e>\fR ++(This may not be available.) ++.It Em \e> + Anchors the single character regular expression or subexpression + immediately following it to the end of a word. +-(This may not be available) +- +-.TP 8 +-\fR\e(\fIre\fR\e)\fR ++(This may not be available.) ++.It Em \e( Ns No re Ns Em \e) + Defines a subexpression +-.IR re . ++.Em re . + Subexpressions may be nested. +-A subsequent backreference of the form \fI`\en'\fR, where +-.I n ++A subsequent backreference of the form ++.Em \en Ns No , ++where ++.Em n + is a number in the range [1,9], expands to the text matched by the +-.IR n th ++.Em n Ns No th + subexpression. +-For example, the regular expression `\e(.*\e)\e1' matches any string +-consisting of identical adjacent substrings. +-Subexpressions are ordered relative to +-their left delimiter. +- +-.TP 8 +-* ++For example, the regular expression ++.Em \e(.*\e)\e1 ++matches any string consisting of identical adjacent substrings. ++Subexpressions are ordered relative to their left delimiter. ++.It Em * + Matches the single character regular expression or subexpression +-immediately preceding it zero or more times. If '*' is the first +-character of a regular expression or subexpression, then it matches +-itself. The `*' operator sometimes yields unexpected results. +-For example, the regular expression `b*' matches the beginning of +-the string `abbb' (as opposed to the substring `bbb'), since a null match +-is the only left-most match. +- +-.TP 8 +-\fR\e{\fIn,m\fR\e}\fR or \fR\e{\fIn,\fR\e}\fR or \fR\e{\fIn\fR\e}\fR ++immediately preceding it zero or more times. ++If ++.Em * ++is the first character of a regular expression or subexpression, ++then it matches itself. ++The ++.Em * ++operator sometimes yields unexpected results. ++For example, the regular expression ++.Em b* ++matches the beginning of the string ++.Em abbb ++(as opposed to the substring ++.Em bbb Ns No ), ++since a null match is the only leftmost match. ++.Sm off ++.It Xo Em \e{ No n,m ++.Em \e}\ \e{ No n, Em \e}\ ++.Em \e{ No n Em \e} ++.Xc ++.Sm on + Matches the single character regular expression or subexpression + immediately preceding it at least +-.I n ++.Em n + and at most +-.I m ++.Em m + times. + If +-.I m ++.Em m + is omitted, then it matches at least +-.I n ++.Em n + times. + If the comma is also omitted, then it matches exactly +-.I n ++.Em n + times. +- +-.LP ++.El ++.Pp + Additional regular expression operators may be defined depending on the + particular +-.IR regex (3) ++.Xr regex 3 + implementation. +- +-.SS COMMANDS ++.Ss COMMANDS + All +-.B ed +-commands are single characters, though some require additonal parameters. ++.Nm ++commands are single characters, though some require additional parameters. + If a command's parameters extend over several lines, then +-each line except for the last +-must be terminated with a backslash (\\). +- ++each line except for the last must be terminated with a backslash ++.Pq Ql \e . ++.Pp + In general, at most one command is allowed per line. + However, most commands accept a print suffix, which is any of +-.I `p' +-(print), +-.I `l' +-(list) , ++.Em p No (print), ++.Em l No (list), + or +-.I `n' +-(enumerate), ++.Em n No (enumerate), + to print the last line affected by the command. +- ++.Pp + An interrupt (typically ^C) has the effect of aborting the current command + and returning the editor to command mode. +- +-.B ed +-recognizes the following commands. The commands are shown together with ++.Pp ++.Nm ++recognizes the following commands. ++The commands are shown together with + the default address or address range supplied if none is +-specified (in parenthesis). +- +-.TP 8 +-(.)a ++specified (in parentheses), and other possible arguments on the right. ++.Bl -tag -width Dxxs ++.It (.) Ns Em a + Appends text to the buffer after the addressed line. + Text is entered in input mode. + The current address is set to last line entered. +- +-.TP 8 +-(.,.)c +-Changes lines in the buffer. The addressed lines are deleted +-from the buffer, and text is appended in their place. ++.It (.,.) Ns Em c ++Changes lines in the buffer. ++The addressed lines are deleted from the buffer, ++and text is appended in their place. + Text is entered in input mode. + The current address is set to last line entered. +- +-.TP 8 +-(.,.)d ++.It (.,.) Ns Em d + Deletes the addressed lines from the buffer. + If there is a line after the deleted range, then the current address is set +-to this line. Otherwise the current address is set to the line +-before the deleted range. +- +-.TP 8 +-.RI e \ file ++to this line. ++Otherwise the current address is set to the line before the deleted range. ++.It Em e No file + Edits +-.IR file , ++.Em file Ns No , + and sets the default filename. + If +-.I file +-is not specified, then the default filename is used. +-Any lines in the buffer are deleted before +-the new file is read. ++.Em file ++is not specified, then the default filename is used. ++Any lines in the buffer are deleted before the new file is read. + The current address is set to the last line read. +- +-.TP 8 +-.RI e \ !command ++.It Em e No !command + Edits the standard output of +-.IR `!command' , ++.Em !command Ns No , + (see +-.RI ! command ++.Em ! No command + below). + The default filename is unchanged. + Any lines in the buffer are deleted before the output of +-.I command ++.Em command + is read. + The current address is set to the last line read. +- +-.TP 8 +-.RI E \ file ++.It Em E No file + Edits +-.I file ++.Em file + unconditionally. + This is similar to the +-.I e +-command, +-except that unwritten changes are discarded without warning. ++.Em e ++command, except that unwritten changes are discarded without warning. + The current address is set to the last line read. +- +-.TP 8 +-.RI f \ file ++.It Em f No file + Sets the default filename to +-.IR file . ++.Em file Ns No . + If +-.I file ++.Em file + is not specified, then the default unescaped filename is printed. +- +-.TP 8 +-.RI (1,$)g /re/command-list ++.It (1,$) Ns Em g Ns No /re/command-list + Applies +-.I command-list ++.Em command-list + to each of the addressed lines matching a regular expression +-.IR re . +-The current address is set to the +-line currently matched before +-.I command-list ++.Em re Ns No . ++The current address is set to the line currently matched before ++.Em command-list + is executed. + At the end of the +-.I `g' ++.Em g + command, the current address is set to the last line affected by +-.IR command-list . +- ++.Em command-list Ns No . ++.Pp + Each command in +-.I command-list ++.Em command-list + must be on a separate line, +-and every line except for the last must be terminated by a backslash +-(\\). ++and every line except for the last must be terminated by ++.Em \e No (backslash). + Any commands are allowed, except for +-.IR `g' , +-.IR `G' , +-.IR `v' , ++.Em g Ns No , ++.Em G Ns No , ++.Em v Ns No , + and +-.IR `V' . ++.Em V Ns No . + A newline alone in +-.I command-list +-is equivalent to a +-.I `p' ++.Em command-list ++is equivalent to a ++.Em p + command. +- +-.TP 8 +-.RI (1,$)G /re/ ++.It (1,$) Ns Em G Ns No /re/ + Interactively edits the addressed lines matching a regular expression +-.IR re. +-For each matching line, +-the line is printed, +-the current address is set, +-and the user is prompted to enter a +-.IR command-list . ++.Em re Ns No . ++For each matching line, the line is printed, the current address is set, ++and the user is prompted to enter a ++.Em command-list Ns No . + At the end of the +-.I `G' +-command, the current address +-is set to the last line affected by (the last) +-.IR command-list . +- ++.Em g ++command, the current address is set to the last line affected by (the last) ++.Em command-list Ns No . ++.Pp + The format of +-.I command-list ++.Em command-list + is the same as that of the +-.I `g' +-command. A newline alone acts as a null command list. +-A single `&' repeats the last non-null command list. +- +-.TP 8 +-H ++.Em g ++command. ++A newline alone acts as a null command list. ++A single ++.Em & ++repeats the last non-null command list. ++.It Em H + Toggles the printing of error explanations. + By default, explanations are not printed. +-It is recommended that ed scripts begin with this command to +-aid in debugging. +- +-.TP 8 +-h ++It is recommended that ++.Nm ++scripts begin with this command to aid in debugging. ++.It Em h + Prints an explanation of the last error. +- +-.TP 8 +-(.)i ++.It (.) Ns Em i + Inserts text in the buffer before the current line. + Text is entered in input mode. + The current address is set to the last line entered. +- +-.TP 8 +-(.,.+1)j +-Joins the addressed lines. The addressed lines are +-deleted from the buffer and replaced by a single ++.It (.,.+1) Ns Em j ++Joins the addressed lines. ++The addressed lines are deleted from the buffer and replaced by a single + line containing their joined text. + The current address is set to the resultant line. +- +-.TP 8 +-.RI (.)k lc ++.It (.) Ns Em klc + Marks a line with a lower case letter +-.IR lc . +-The line can then be addressed as +-.I 'lc ++.Em lc Ns No \&. ++The line can then be addressed as ++.Em \&'lc + (i.e., a single quote followed by +-.I lc +-) in subsequent commands. The mark is not cleared until the line is +-deleted or otherwise modified. +- +-.TP 8 +-(.,.)l ++.Em lc Ns No ) ++in subsequent commands. ++The mark is not cleared until the line is deleted or otherwise modified. ++.It (.,.) Ns Em l + Prints the addressed lines unambiguously. +-If a single line fills for than one screen (as might be the case +-when viewing a binary file, for instance), a `--More--' +-prompt is printed on the last line. +-.B ed +-waits until the RETURN key is pressed +-before displaying the next screen. +-The current address is set to the last line +-printed. +- +-.TP 8 +-(.,.)m(.) +-Moves lines in the buffer. The addressed lines are moved to after the ++If a single line fills more than one screen (as might be the case ++when viewing a binary file, for instance), a ++.Dq --More-- ++prompt is printed on the last line. ++.Nm ++waits until the RETURN key is pressed before displaying the next screen. ++The current address is set to the last line printed. ++.It (.,.) Ns Em m Ns No (.) ++Moves lines in the buffer. ++The addressed lines are moved to after the + right-hand destination address, which may be the address +-.IR 0 ++.Em 0 + (zero). +-The current address is set to the +-last line moved. +- +-.TP 8 +-(.,.)n +-Prints the addressed lines along with +-their line numbers. The current address is set to the last line +-printed. +- +-.TP 8 +-(.,.)p +-Prints the addressed lines. The current address is set to the last line +-printed. +- +-.TP 8 +-P ++The current address is set to the last line moved. ++.It (.,.) Ns Em n ++Prints the addressed lines along with their line numbers. ++The current address is set to the last line printed. ++.It (.,.) Ns Em p ++Prints the addressed lines. ++The current address is set to the last line printed. ++.It Em P + Toggles the command prompt on and off. + Unless a prompt was specified by with command-line option +-\fI-p string\fR, the command prompt is by default turned off. +- +-.TP 8 +-q +-Quits ed. +- +-.TP 8 +-Q +-Quits ed unconditionally. ++.Fl p Ar string Ns No , ++the command prompt is by default turned off. ++.It Em q ++Quits ++.Nm ed . ++.It Em Q ++Quits ++.Nm ++unconditionally. + This is similar to the +-.I q +-command, +-except that unwritten changes are discarded without warning. +- +-.TP 8 +-.RI ($)r \ file ++.Em q ++command, except that unwritten changes are discarded without warning. ++.It ($) Ns Em r No file + Reads +-.I file +-to after the addressed line. If +-.I file +-is not specified, then the default +-filename is used. If there was no default filename prior to the command, ++.Em file ++to after the addressed line. ++If ++.Em file ++is not specified, then the default filename is used. ++If there was no default filename prior to the command, + then the default filename is set to +-.IR file . ++.Em file Ns No . + Otherwise, the default filename is unchanged. + The current address is set to the last line read. +- +-.TP 8 +-.RI ($)r \ !command +-Reads +-to after the addressed line +-the standard output of +-.IR `!command' , ++.It ($) Ns Em r No !command ++Reads to after the addressed line the standard output of ++.Em !command Ns No , + (see the +-.RI ! command +-below). ++.Em ! ++command below). + The default filename is unchanged. + The current address is set to the last line read. +- +-.HP +-.RI (.,.)s /re/replacement/ +-.PD 0 +-.HP +-.RI (.,.)s /re/replacement/\fRg\fR +-.HP +-.RI (.,.)s /re/replacement/n +-.br +-Replaces text in the addressed lines +-matching a regular expression +-.I re ++.Sm off ++.It Xo (.,.) Em s No /re/replacement/ , \ (.,.) ++.Em s No /re/replacement/ Em g , No \ (.,.) ++.Em s No /re/replacement/ Em n ++.Xc ++.Sm on ++Replaces text in the addressed lines matching a regular expression ++.Em re + with +-.IR replacement . ++.Em replacement Ns No . + By default, only the first match in each line is replaced. + If the +-.I `g' ++.Em g + (global) suffix is given, then every match to be replaced. + The +-.I `n' ++.Em n + suffix, where +-.I n +-is a postive number, causes only the +-.IR n th ++.Em n ++is a positive number, causes only the ++.Em n Ns No th + match to be replaced. + It is an error if no substitutions are performed on any of the addressed + lines. + The current address is set the last line affected. +- +-.I re ++.Pp ++.Em re + and +-.I replacement ++.Em replacement + may be delimited by any character other than space and newline + (see the +-.I `s' ++.Em s + command below). + If one or two of the last delimiters is omitted, then the last line + affected is printed as though the print suffix +-.I `p' ++.Em p + were specified. +- +- +-An unescaped `&' in +-.I replacement ++.Pp ++An unescaped ++.Ql \e ++in ++.Em replacement + is replaced by the currently matched text. + The character sequence +-\fI`\em'\fR, ++.Em \em Ns No , + where +-.I m ++.Em m + is a number in the range [1,9], is replaced by the +-.IR m th ++.Em m Ns No th + backreference expression of the matched text. + If +-.I replacement +-consists of a single `%', then +-.I replacement ++.Em replacement ++consists of a single ++.Ql % , ++then ++.Em replacement + from the last substitution is used. + Newlines may be embedded in +-.I replacement +-if they are escaped with a backslash (\\). +- +-.TP 8 +-(.,.)s ++.Em replacement ++if they are escaped with a backslash ++.Pq Ql \e . ++.It (.,.) Ns Em s + Repeats the last substitution. + This form of the +-.I `s' ++.Em s + command accepts a count suffix +-.IR `n' , ++.Em n Ns No , + or any combination of the characters +-.IR `r' , +-.IR `g' , ++.Em r Ns No , ++.Em g Ns No , + and +-.IR `p' . ++.Em p Ns No . + If a count suffix +-.I `n' ++.Em n + is given, then only the +-.IR n th ++.Em n Ns No th + match is replaced. + The +-.I `r' ++.Em r + suffix causes + the regular expression of the last search to be used instead of the + that of the last substitution. + The +-.I `g' ++.Em g + suffix toggles the global suffix of the last substitution. + The +-.I `p' ++.Em p + suffix toggles the print suffix of the last substitution + The current address is set to the last line affected. +- +-.TP 8 +-(.,.)t(.) ++.It (.,.) Ns Em t Ns No (.) + Copies (i.e., transfers) the addressed lines to after the right-hand + destination address, which may be the address +-.IR 0 ++.Em 0 + (zero). +-The current address is set to the last line +-copied. +- +-.TP 8 +-u ++The current address is set to the last line copied. ++.It Em u + Undoes the last command and restores the current address + to what it was before the command. + The global commands +-.IR `g' , +-.IR `G' , +-.IR `v' , ++.Em g Ns No , ++.Em G Ns No , ++.Em v Ns No , + and +-.IR `V' . ++.Em V Ns No . + are treated as a single command by undo. +-.I `u' ++.Em u + is its own inverse. +- +-.TP 8 +-.RI (1,$)v /pat/command-list ++.It (1,$) Ns Em v Ns No /re/command-list + Applies +-.I command-list ++.Em command-list + to each of the addressed lines not matching a regular expression +-.IR re . ++.Em re Ns No . + This is similar to the +-.I `g' ++.Em g + command. +- +-.TP 8 +-.RI (1,$)V /re/ ++.It (1,$) Ns Em V Ns No /re/ + Interactively edits the addressed lines not matching a regular expression +-.IR re. ++.Em re Ns No . + This is similar to the +-.I `G' ++.Em G + command. +- +-.TP 8 +-.RI (1,$)w \ file ++.It (1,$) Ns Em w No file + Writes the addressed lines to +-.IR file . ++.Em file Ns No . + Any previous contents of +-.I file ++.Em file + is lost without warning. + If there is no default filename, then the default filename is set to +-.IR file, +-otherwise it is unchanged. If no filename is specified, then the default +-filename is used. ++.Em file Ns No , ++otherwise it is unchanged. ++If no filename is specified, then the default filename is used. + The current address is unchanged. +- +-.TP 8 +-.RI (1,$)wq \ file ++.It (1,$) Ns Em wq No file + Writes the addressed lines to +-.IR file , ++.Em file Ns No , + and then executes a +-.I `q' ++.Em q + command. +- +-.TP 8 +-.RI (1,$)w \ !command ++.It (1,$) Ns Em w No !command + Writes the addressed lines to the standard input of +-.IR `!command' , ++.Em !command Ns No , + (see the +-.RI ! command +-below). ++.Em ! ++command below). + The default filename and current address are unchanged. +- +-.TP 8 +-.RI (1,$)W \ file ++.It (1,$) Ns Em W No file + Appends the addressed lines to the end of +-.IR file . ++.Em file Ns No . + This is similar to the +-.I `w' ++.Em w + command, expect that the previous contents of file i