sbase

suckless unix tools
git clone git://git.suckless.org/sbase
Log | Files | Refs | README | LICENSE

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:
Med.1 | 12+++++++++---
Med.c | 39+++++++++++++++++++++++++--------------
Atests/0011-ed.sh | 25+++++++++++++++++++++++++
Atests/0012-ed.sh | 24++++++++++++++++++++++++
Atests/0013-ed.sh | 23+++++++++++++++++++++++
Atests/0014-ed.sh | 11+++++++++++
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