commit 9a0d04fcf9ac486a4056a3641ecb098a227166af
parent 8adf85e686e8b95d00879e2882b2d925dbdbd681
Author: Roberto E. Vargas Caballero <k0ga@shike2.net>
Date: Sat, 13 Dec 2025 10:29:17 +0100
ed: Accept shell escapes in r, e and E commands
Diffstat:
6 files changed, 117 insertions(+), 17 deletions(-)
diff --git a/ed.1 b/ed.1
@@ -1,4 +1,4 @@
-.Dd December 27, 2016
+.Dd December 27, 2025
.Dt ED 1
.Os sbase
.Sh NAME
@@ -107,8 +107,15 @@ uses the currently remembered filename.
The remembered filename is set to
.Ar file
for later use.
+The current address is set to the last line read.
+.It e Ar !command
+Delete the contents of the buffer and load in the output of
+.Ar command .
+The remembered filename is not modified.
+The current address is set to the last line read.
.It E Ar file
-As above, but without warning if the current buffer has unsaved changes.
+As the command e,
+but without warning if the current buffer has unsaved changes.
.It f Ar file
Set the currently remembered filename to
.Ar file
@@ -235,4 +242,3 @@ The dot is unchanged.
POSIX.1-2013.
Except where noted here:
g and v operate on single commands rather than lists delimited with '\e'.
-e, E, r, w, and W commands cannot accept shell escapes.
diff --git a/ed.c b/ed.c
@@ -804,18 +804,29 @@ dowrite(const char *fname, int trunc)
static void
doread(const char *fname)
{
+ int r;
size_t cnt;
ssize_t len;
char *p;
FILE *aux;
static size_t n;
+ static int sh;
static char *s;
static FILE *fp;
- if (fp)
- fclose(fp);
- if ((fp = fopen(fname, "r")) == NULL)
+ if (fp) {
+ sh ? pclose(fp) : fclose(fp);
+ fp = NULL;
+ }
+
+ if(fname[0] == '!') {
+ sh = 1;
+ fname++;
+ if((fp = popen(fname, "r")) == NULL)
+ error("bad exec");
+ } else if ((fp = fopen(fname, "r")) == NULL) {
error("cannot open input file");
+ }
curln = line2;
for (cnt = 0; (len = getline(&s, &n, fp)) > 0; cnt += (size_t)len) {
@@ -836,7 +847,8 @@ doread(const char *fname)
aux = fp;
fp = NULL;
- if (fclose(aux))
+ r = sh ? pclose(aux) : fclose(aux);
+ if (r)
error("input/output error");
}
@@ -926,16 +938,16 @@ getfname(int comm)
if (savfname[0] == '\0')
error("no current filename");
return savfname;
- } else if (bp == &fname[FILENAME_MAX]) {
- error("file name too long");
- } else {
- *bp = '\0';
- if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
- strcpy(savfname, fname);
- return fname;
}
+ if (bp == &fname[FILENAME_MAX])
+ error("file name too long");
+ *bp = '\0';
- return NULL; /* not reached */
+ if (fname[0] == '!')
+ return fname;
+ if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
+ strcpy(savfname, fname);
+ return fname;
}
static void
@@ -1443,10 +1455,9 @@ repeat:
goto unexpected;
if (modflag)
goto modified;
- getfname(cmd);
setscratch();
deflines(curln, curln);
- doread(savfname);
+ doread(getfname(cmd));
clearundo();
break;
default:
diff --git a/tests/0011-ed.sh b/tests/0011-ed.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+tmp=tmp.$$
+
+trap 'rm -f $tmp' EXIT
+trap 'rm -f $tmp' HUP INT TERM
+
+cat <<EOF >$tmp
+y
+1
+x
+y
+EOF
+
+../ed -s /dev/null <<EOF | diff -u $tmp -
+a
+1
+2
+3
+.
+1r !printf 'x\ny\n'
+p
+1,3p
+w
+EOF
diff --git a/tests/0012-ed.sh b/tests/0012-ed.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+tmp=tmp.$$
+
+trap 'rm -f $tmp' EXIT
+trap 'rm -f $tmp' HUP INT TERM
+
+cat <<EOF >$tmp
+x
+y
+/dev/null
+EOF
+
+../ed -s /dev/null <<EOF | diff -u $tmp -
+a
+1
+2
+3
+.
+w
+e !printf 'x\ny\n'
+,p
+f
+EOF
diff --git a/tests/0013-ed.sh b/tests/0013-ed.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+tmp=tmp.$$
+
+trap 'rm -f $tmp' EXIT
+trap 'rm -f $tmp' HUP INT TERM
+
+cat <<EOF >$tmp
+x
+y
+/dev/null
+EOF
+
+../ed -s /dev/null <<EOF | diff -u $tmp -
+a
+1
+2
+3
+.
+E !printf 'x\ny\n'
+,p
+f
+EOF
diff --git a/tests/0014-ed.sh b/tests/0014-ed.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+../ed -s /dev/null <<EOF | (read a && test $a = a)
+a
+1
+2
+3
+.
+1w !sed s/1/a/
+w
+EOF