sh: Save/restore changed variables in optimized command substitution.
[dragonfly.git] / bin / sh / jobs.c
index 939ece5..39dba19 100644 (file)
  * SUCH DAMAGE.
  *
  * @(#)jobs.c  8.5 (Berkeley) 5/4/95
- * $FreeBSD: src/bin/sh/jobs.c,v 1.72 2006/10/07 16:51:16 stefanf Exp $
- * $DragonFly: src/bin/sh/jobs.c,v 1.9 2007/01/13 21:27:45 pavalos Exp $
+ * $FreeBSD: src/bin/sh/jobs.c,v 1.94 2011/06/12 23:06:04 jilles Exp $
  */
 
-#include <fcntl.h>
-#include <signal.h>
-#include <errno.h>
-#include <paths.h>
-#include <unistd.h>
-#include <stdlib.h>
+#include <sys/ioctl.h>
 #include <sys/param.h>
-#include <sys/wait.h>
-#include <sys/time.h>
 #include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <paths.h>
-#include <sys/ioctl.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
 
 #include "shell.h"
 #if JOBS
 #include "memalloc.h"
 #include "error.h"
 #include "mystring.h"
+#include "var.h"
 
 
-STATIC struct job *jobtab;     /* array of jobs */
-STATIC int njobs;              /* size of array */
+static struct job *jobtab;     /* array of jobs */
+static int njobs;              /* size of array */
 MKINIT pid_t backgndpid = -1;  /* pid of last background process */
+MKINIT struct job *bgjob = NULL; /* last background process */
 #if JOBS
-STATIC struct job *jobmru;     /* most recently used job list */
-STATIC pid_t initialpgrp;      /* pgrp of shell on invocation */
+static struct job *jobmru;     /* most recently used job list */
+static pid_t initialpgrp;      /* pgrp of shell on invocation */
 #endif
 int in_waitcmd = 0;            /* are we in waitcmd()? */
 int in_dowait = 0;             /* are we in dowait()? */
@@ -85,20 +86,22 @@ volatile sig_atomic_t breakwaitcmd = 0;     /* should wait be terminated? */
 static int ttyfd = -1;
 
 #if JOBS
-STATIC void restartjob(struct job *);
+static void restartjob(struct job *);
 #endif
-STATIC void freejob(struct job *);
-STATIC struct job *getjob(char *);
-STATIC pid_t dowait(int, struct job *);
-STATIC pid_t waitproc(int, int *);
-STATIC void cmdtxt(union node *);
-STATIC void cmdputs(const char *);
+static void freejob(struct job *);
+static struct job *getjob(char *);
+static pid_t dowait(int, struct job *);
+static pid_t waitproc(int, int *);
+static void checkzombies(void);
+static void cmdtxt(union node *);
+static void cmdputs(const char *);
 #if JOBS
-STATIC void setcurjob(struct job *);
-STATIC void deljob(struct job *);
-STATIC struct job *getcurjob(struct job *);
+static void setcurjob(struct job *);
+static void deljob(struct job *);
+static struct job *getcurjob(struct job *);
 #endif
-STATIC void showjob(struct job *, pid_t, int);
+static void printjobcmd(struct job *);
+static void showjob(struct job *, int);
 
 
 /*
@@ -146,14 +149,12 @@ setjobctl(int on)
                do { /* while we are in the background */
                        initialpgrp = tcgetpgrp(ttyfd);
                        if (initialpgrp < 0) {
-out:                           out2str("sh: can't access tty; job control turned off\n");
+out:                           out2fmt_flush("sh: can't access tty; job control turned off\n");
                                mflag = 0;
                                return;
                        }
-                       if (initialpgrp == -1)
-                               initialpgrp = getpgrp();
-                       else if (initialpgrp != getpgrp()) {
-                               killpg(0, SIGTTIN);
+                       if (initialpgrp != getpgrp()) {
+                               kill(0, SIGTTIN);
                                continue;
                        }
                } while (0);
@@ -176,21 +177,6 @@ out:                               out2str("sh: can't access tty; job control turned off\n");
 #endif
 
 
-#ifdef mkinit
-INCLUDE <sys/types.h>
-INCLUDE <stdlib.h>
-
-SHELLPROC {
-       backgndpid = -1;
-#if JOBS
-       jobctl = 0;
-#endif
-}
-
-#endif
-
-
-
 #if JOBS
 int
 fgcmd(int argc __unused, char **argv)
@@ -202,8 +188,7 @@ fgcmd(int argc __unused, char **argv)
        jp = getjob(argv[1]);
        if (jp->jobctl == 0)
                error("job not created under job control");
-       out1str(jp->ps[0].cmd);
-       out1c('\n');
+       printjobcmd(jp);
        flushout(&output);
        pgrp = jp->ps[0].pid;
        tcsetpgrp(ttyfd, pgrp);
@@ -219,7 +204,6 @@ fgcmd(int argc __unused, char **argv)
 int
 bgcmd(int argc, char **argv)
 {
-       char s[64];
        struct job *jp;
 
        do {
@@ -230,16 +214,14 @@ bgcmd(int argc, char **argv)
                        continue;
                restartjob(jp);
                jp->foreground = 0;
-               fmtstr(s, 64, "[%td] ", jp - jobtab + 1);
-               out1str(s);
-               out1str(jp->ps[0].cmd);
-               out1c('\n');
+               out1fmt("[%td] ", jp - jobtab + 1);
+               printjobcmd(jp);
        } while (--argc > 1);
        return 0;
 }
 
 
-STATIC void
+static void
 restartjob(struct job *jp)
 {
        struct procstat *ps;
@@ -249,7 +231,7 @@ restartjob(struct job *jp)
                return;
        setcurjob(jp);
        INTOFF;
-       killpg(jp->ps[0].pid, SIGCONT);
+       kill(-jp->ps[0].pid, SIGCONT);
        for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
                if (WIFSTOPPED(ps->status)) {
                        ps->status = -1;
@@ -293,19 +275,33 @@ jobscmd(int argc, char *argv[])
                showjobs(0, mode);
        else
                while ((id = *argv++) != NULL)
-                       showjob(getjob(id), 0, mode);
+                       showjob(getjob(id), mode);
 
        return (0);
 }
 
-STATIC void
-showjob(struct job *jp, pid_t pid, int mode)
+static void
+printjobcmd(struct job *jp)
+{
+       struct procstat *ps;
+       int i;
+
+       for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
+               out1str(ps->cmd);
+               if (i > 0)
+                       out1str(" | ");
+       }
+       out1c('\n');
+}
+
+static void
+showjob(struct job *jp, int mode)
 {
        char s[64];
+       char statestr[64];
        struct procstat *ps;
        struct job *j;
        int col, curr, i, jobno, prev, procno;
-       pid_t ppid;
        char c;
 
        procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs;
@@ -318,16 +314,44 @@ showjob(struct job *jp, pid_t pid, int mode)
                        prev = j - jobtab + 1;
        }
 #endif
+       ps = jp->ps + jp->nprocs - 1;
+       if (jp->state == 0) {
+               strcpy(statestr, "Running");
+#if JOBS
+       } else if (jp->state == JOBSTOPPED) {
+               while (!WIFSTOPPED(ps->status) && ps > jp->ps)
+                       ps--;
+               if (WIFSTOPPED(ps->status))
+                       i = WSTOPSIG(ps->status);
+               else
+                       i = -1;
+               if (i > 0 && i < sys_nsig && sys_siglist[i])
+                       strcpy(statestr, sys_siglist[i]);
+               else
+                       strcpy(statestr, "Suspended");
+#endif
+       } else if (WIFEXITED(ps->status)) {
+               if (WEXITSTATUS(ps->status) == 0)
+                       strcpy(statestr, "Done");
+               else
+                       fmtstr(statestr, 64, "Done(%d)",
+                           WEXITSTATUS(ps->status));
+       } else {
+               i = WTERMSIG(ps->status);
+               if (i > 0 && i < sys_nsig && sys_siglist[i])
+                       strcpy(statestr, sys_siglist[i]);
+               else
+                       fmtstr(statestr, 64, "Signal %d", i);
+               if (WCOREDUMP(ps->status))
+                       strcat(statestr, " (core dumped)");
+       }
+
        for (ps = jp->ps ; ; ps++) {    /* for each process */
                if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) {
-                       ppid = (mode == SHOWJOBS_PIDS) ? ps->pid :
-                           getpgid(ps->pid);
-                       out1fmt("%d\n", (int)ppid);
+                       out1fmt("%d\n", (int)ps->pid);
                        goto skip;
                }
-               if (mode != SHOWJOBS_VERBOSE && ps != jp->ps && pid == 0)
-                       goto skip;
-               if (pid != 0 && pid != ps->pid)
+               if (mode != SHOWJOBS_VERBOSE && ps != jp->ps)
                        goto skip;
                if (jobno == curr && ps == jp->ps)
                        c = '+';
@@ -346,39 +370,19 @@ showjob(struct job *jp, pid_t pid, int mode)
                        out1str(s);
                        col += strlen(s);
                }
-               s[0] = '\0';
-               if (ps != jp->ps) {
-                       *s = '\0';
-               } else if (ps->status == -1) {
-                       strcpy(s, "Running");
-               } else if (WIFEXITED(ps->status)) {
-                       if (WEXITSTATUS(ps->status) == 0)
-                               strcpy(s, "Done");
-                       else
-                               fmtstr(s, 64, "Done (%d)",
-                                   WEXITSTATUS(ps->status));
-               } else {
-#if JOBS
-                       if (WIFSTOPPED(ps->status))
-                               i = WSTOPSIG(ps->status);
-                       else
-#endif
-                               i = WTERMSIG(ps->status);
-                       if ((i & 0x7F) < sys_nsig && sys_siglist[i & 0x7F])
-                               scopy(sys_siglist[i & 0x7F], s);
-                       else
-                               fmtstr(s, 64, "Signal %d", i & 0x7F);
-                       if (WCOREDUMP(ps->status))
-                               strcat(s, " (core dumped)");
+               if (ps == jp->ps) {
+                       out1str(statestr);
+                       col += strlen(statestr);
                }
-               out1str(s);
-               col += strlen(s);
                do {
                        out1c(' ');
                        col++;
                } while (col < 30);
-               out1str(ps->cmd);
-               out1c('\n');
+               if (mode == SHOWJOBS_VERBOSE) {
+                       out1str(ps->cmd);
+                       out1c('\n');
+               } else
+                       printjobcmd(jp);
 skip:          if (--procno <= 0)
                        break;
        }
@@ -400,7 +404,7 @@ showjobs(int change, int mode)
        struct job *jp;
 
        TRACE(("showjobs(%d) called\n", change));
-       while (dowait(0, NULL) > 0);
+       checkzombies();
        for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) {
                if (! jp->used)
                        continue;
@@ -410,9 +414,13 @@ showjobs(int change, int mode)
                }
                if (change && ! jp->changed)
                        continue;
-               showjob(jp, 0, mode);
+               showjob(jp, mode);
                jp->changed = 0;
-               if (jp->state == JOBDONE) {
+               /* Hack: discard jobs for which $! has not been referenced
+                * in interactive mode when they terminate.
+                */
+               if (jp->state == JOBDONE && !jp->remembered &&
+                               (iflag || jp != bgjob)) {
                        freejob(jp);
                }
        }
@@ -423,13 +431,15 @@ showjobs(int change, int mode)
  * Mark a job structure as unused.
  */
 
-STATIC void
+static void
 freejob(struct job *jp)
 {
        struct procstat *ps;
        int i;
 
        INTOFF;
+       if (bgjob == jp)
+               bgjob = NULL;
        for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) {
                if (ps->cmd != nullstr)
                        ckfree(ps->cmd);
@@ -476,12 +486,27 @@ waitcmd(int argc, char **argv)
 #endif
                                else
                                        retval = WTERMSIG(status) + 128;
-                               if (! iflag)
+                               if (! iflag || ! job->changed)
                                        freejob(job);
+                               else {
+                                       job->remembered = 0;
+                                       if (job == bgjob)
+                                               bgjob = NULL;
+                               }
                                in_waitcmd--;
                                return retval;
                        }
                } else {
+                       for (jp = jobtab ; jp < jobtab + njobs; jp++)
+                               if (jp->used && jp->state == JOBDONE) {
+                                       if (! iflag || ! jp->changed)
+                                               freejob(jp);
+                                       else {
+                                               jp->remembered = 0;
+                                               if (jp == bgjob)
+                                                       bgjob = NULL;
+                                       }
+                               }
                        for (jp = jobtab ; ; jp++) {
                                if (jp >= jobtab + njobs) {     /* no running procs */
                                        in_waitcmd--;
@@ -519,7 +544,7 @@ jobidcmd(int argc __unused, char **argv)
  * Convert a job name to a job structure.
  */
 
-STATIC struct job *
+static struct job *
 getjob(char *name)
 {
        int jobno;
@@ -591,6 +616,14 @@ currentjob:        if ((jp = getcurjob(NULL)) == NULL)
 }
 
 
+pid_t
+getjobpgrp(char *name)
+{
+       struct job *jp;
+
+       jp = getjob(name);
+       return -jp->ps[0].pid;
+}
 
 /*
  * Return a new job structure,
@@ -622,6 +655,8 @@ makejob(union node *node __unused, int nprocs)
                                                jp[i].next = &jp[jp[i].next -
                                                    jobtab];
 #endif
+                               if (bgjob != NULL)
+                                       bgjob = &jp[bgjob - jobtab];
                                /* Relocate `ps' pointers */
                                for (i = 0; i < njobs; i++)
                                        if (jp[i].ps == &jobtab[i].ps0)
@@ -643,6 +678,7 @@ makejob(union node *node __unused, int nprocs)
        jp->changed = 0;
        jp->nprocs = 0;
        jp->foreground = 0;
+       jp->remembered = 0;
 #if JOBS
        jp->jobctl = jobctl;
        jp->next = NULL;
@@ -653,13 +689,13 @@ makejob(union node *node __unused, int nprocs)
                jp->ps = &jp->ps0;
        }
        INTON;
-       TRACE(("makejob(%p, %d) returns %%%d\n", (void *)node, nprocs,
+       TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs,
            jp - jobtab + 1));
        return jp;
 }
 
 #if JOBS
-STATIC void
+static void
 setcurjob(struct job *cj)
 {
        struct job *jp, *prev;
@@ -679,7 +715,7 @@ setcurjob(struct job *cj)
        jobmru = cj;
 }
 
-STATIC void
+static void
 deljob(struct job *j)
 {
        struct job *jp, *prev;
@@ -699,7 +735,7 @@ deljob(struct job *j)
  * Return the most recently used job that isn't `nj', and preferably one
  * that is stopped.
  */
-STATIC struct job *
+static struct job *
 getcurjob(struct job *nj)
 {
        struct job *jp;
@@ -739,9 +775,11 @@ forkshell(struct job *jp, union node *n, int mode)
        pid_t pid;
        pid_t pgrp;
 
-       TRACE(("forkshell(%%%d, %p, %d) called\n", jp - jobtab, (void *)n,
+       TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n,
            mode));
        INTOFF;
+       if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0))
+               checkzombies();
        flushall();
        pid = fork();
        if (pid == -1) {
@@ -757,8 +795,10 @@ forkshell(struct job *jp, union node *n, int mode)
                TRACE(("Child shell %d\n", (int)getpid()));
                wasroot = rootshell;
                rootshell = 0;
+               handler = &main_handler;
                closescript();
                INTON;
+               forcelocal = 0;
                clear_traps();
 #if JOBS
                jobctl = 0;             /* do job control only in root shell */
@@ -781,7 +821,7 @@ forkshell(struct job *jp, union node *n, int mode)
                            ! fd0_redirected_p ()) {
                                close(0);
                                if (open(_PATH_DEVNULL, O_RDONLY) != 0)
-                                       error("Can't open %s: %s",
+                                       error("cannot open %s: %s",
                                            _PATH_DEVNULL, strerror(errno));
                        }
                }
@@ -793,7 +833,7 @@ forkshell(struct job *jp, union node *n, int mode)
                            ! fd0_redirected_p ()) {
                                close(0);
                                if (open(_PATH_DEVNULL, O_RDONLY) != 0)
-                                       error("Can't open %s: %s",
+                                       error("cannot open %s: %s",
                                            _PATH_DEVNULL, strerror(errno));
                        }
                }
@@ -817,8 +857,13 @@ forkshell(struct job *jp, union node *n, int mode)
                        pgrp = jp->ps[0].pid;
                setpgid(pid, pgrp);
        }
-       if (mode == FORK_BG)
+       if (mode == FORK_BG) {
+               if (bgjob != NULL && bgjob->state == JOBDONE &&
+                   !bgjob->remembered && !iflag)
+                       freejob(bgjob);
                backgndpid = pid;               /* set $! */
+               bgjob = jp;
+       }
        if (jp) {
                struct procstat *ps = &jp->ps[jp->nprocs++];
                ps->pid = pid;
@@ -862,12 +907,13 @@ waitforjob(struct job *jp, int *origstatus)
 {
 #if JOBS
        pid_t mypgrp = getpgrp();
+       int propagate_int = jp->jobctl && jp->foreground;
 #endif
        int status;
        int st;
 
        INTOFF;
-       TRACE(("waitforjob(%%%d) called\n", jp - jobtab + 1));
+       TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1));
        while (jp->state == 0)
                if (dowait(1, jp) == -1)
                        dotrap();
@@ -894,11 +940,14 @@ waitforjob(struct job *jp, int *origstatus)
        if (! JOBS || jp->state == JOBDONE)
                freejob(jp);
        if (int_pending()) {
-               if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
-                       kill(getpid(), SIGINT);
-               else
+               if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT)
                        CLEAR_PENDING_INT;
        }
+#if JOBS
+       else if (rootshell && iflag && propagate_int &&
+                       WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
+               kill(getpid(), SIGINT);
+#endif
        INTON;
        return st;
 }
@@ -909,7 +958,7 @@ waitforjob(struct job *jp, int *origstatus)
  * Wait for a process to terminate.
  */
 
-STATIC pid_t
+static pid_t
 dowait(int block, struct job *job)
 {
        pid_t pid;
@@ -920,7 +969,7 @@ dowait(int block, struct job *job)
        int done;
        int stopped;
        int sig;
-       int i;
+       int coredump;
 
        in_dowait++;
        TRACE(("dowait(%d) called\n", block));
@@ -946,7 +995,7 @@ dowait(int block, struct job *job)
        INTOFF;
        thisjob = NULL;
        for (jp = jobtab ; jp < jobtab + njobs ; jp++) {
-               if (jp->used) {
+               if (jp->used && jp->nprocs > 0) {
                        done = 1;
                        stopped = 1;
                        for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) {
@@ -967,47 +1016,46 @@ dowait(int block, struct job *job)
                        if (stopped) {          /* stopped or done */
                                int state = done? JOBDONE : JOBSTOPPED;
                                if (jp->state != state) {
-                                       TRACE(("Job %d: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state));
+                                       TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state));
                                        jp->state = state;
+                                       if (jp != job) {
+                                               if (done && !jp->remembered &&
+                                                   !iflag && jp != bgjob)
+                                                       freejob(jp);
 #if JOBS
-                                       if (done)
-                                               deljob(jp);
+                                               else if (done)
+                                                       deljob(jp);
 #endif
+                                       }
                                }
                        }
                }
        }
        INTON;
-       if (! rootshell || ! iflag || (job && thisjob == job)) {
-#if JOBS
-               if (WIFSTOPPED(status))
-                       sig = WSTOPSIG(status);
-               else
-#endif
-               {
-                       if (WIFEXITED(status))
-                               sig = 0;
+       if (!thisjob || thisjob->state == 0)
+               ;
+       else if ((!rootshell || !iflag || thisjob == job) &&
+           thisjob->foreground && thisjob->state != JOBSTOPPED) {
+               sig = 0;
+               coredump = 0;
+               for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++)
+                       if (WIFSIGNALED(sp->status)) {
+                               sig = WTERMSIG(sp->status);
+                               coredump = WCOREDUMP(sp->status);
+                       }
+               if (sig > 0 && sig != SIGINT && sig != SIGPIPE) {
+                       if (sig < sys_nsig && sys_siglist[sig])
+                               out2str(sys_siglist[sig]);
                        else
-                               sig = WTERMSIG(status);
-               }
-               if (sig != 0 && sig != SIGINT && sig != SIGPIPE) {
-                       if (!mflag ||
-                           (thisjob->foreground && !WIFSTOPPED(status))) {
-                               i = WTERMSIG(status);
-                               if ((i & 0x7F) < sys_nsig && sys_siglist[i & 0x7F])
-                                       out1str(sys_siglist[i & 0x7F]);
-                               else
-                                       out1fmt("Signal %d", i & 0x7F);
-                               if (WCOREDUMP(status))
-                                       out1str(" (core dumped)");
-                               out1c('\n');
-                       } else
-                               showjob(thisjob, pid, SHOWJOBS_DEFAULT);
+                               outfmt(out2, "Signal %d", sig);
+                       if (coredump)
+                               out2str(" (core dumped)");
+                       out2c('\n');
+                       flushout(out2);
                }
        } else {
                TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job));
-               if (thisjob)
-                       thisjob->changed = 1;
+               thisjob->changed = 1;
        }
        return pid;
 }
@@ -1019,7 +1067,7 @@ dowait(int block, struct job *job)
  * stopped processes.  If block is zero, we return a value of zero
  * rather than blocking.
  */
-STATIC pid_t
+static pid_t
 waitproc(int block, int *status)
 {
        int flags;
@@ -1050,7 +1098,7 @@ stoppedjobs(void)
                if (jp->used == 0)
                        continue;
                if (jp->state == JOBSTOPPED) {
-                       out2str("You have stopped jobs.\n");
+                       out2fmt_flush("You have stopped jobs.\n");
                        job_warning = 2;
                        return (1);
                }
@@ -1059,13 +1107,37 @@ stoppedjobs(void)
        return (0);
 }
 
+
+static void
+checkzombies(void)
+{
+       while (njobs > 0 && dowait(0, NULL) > 0)
+               ;
+}
+
+
+int
+backgndpidset(void)
+{
+       return backgndpid != -1;
+}
+
+
+pid_t
+backgndpidval(void)
+{
+       if (bgjob != NULL && !forcelocal)
+               bgjob->remembered = 1;
+       return backgndpid;
+}
+
 /*
  * Return a string identifying a command (to be printed by the
  * jobs command.
  */
 
-STATIC char *cmdnextc;
-STATIC int cmdnleft;
+static char *cmdnextc;
+static int cmdnleft;
 #define MAXCMDTEXT     200
 
 char *
@@ -1081,7 +1153,7 @@ commandtext(union node *n)
 }
 
 
-STATIC void
+static void
 cmdtxt(union node *n)
 {
        union node *np;
@@ -1214,7 +1286,7 @@ redir:
 
 
 
-STATIC void
+static void
 cmdputs(const char *s)
 {
        const char *p;
@@ -1234,13 +1306,46 @@ cmdputs(const char *s)
                        if (--cmdnleft > 0)
                                *q++ = '{';
                        subtype = *p++;
+                       if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0)
+                               *q++ = '#';
                } else if (c == '=' && subtype != 0) {
-                       *q++ = "}-+?="[(subtype & VSTYPE) - VSNORMAL];
+                       *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL];
+                       if (*q)
+                               q++;
+                       else
+                               cmdnleft++;
+                       if (((subtype & VSTYPE) == VSTRIMLEFTMAX ||
+                           (subtype & VSTYPE) == VSTRIMRIGHTMAX) &&
+                           --cmdnleft > 0)
+                               *q = q[-1], q++;
                        subtype = 0;
                } else if (c == CTLENDVAR) {
                        *q++ = '}';
-               } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE)
-                       cmdnleft++;             /* ignore it */
+               } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) {
+                       cmdnleft -= 5;
+                       if (cmdnleft > 0) {
+                               *q++ = '$';
+                               *q++ = '(';
+                               *q++ = '.';
+                               *q++ = '.';
+                               *q++ = '.';
+                               *q++ = ')';
+                       }
+               } else if (c == CTLARI) {
+                       cmdnleft -= 2;
+                       if (cmdnleft > 0) {
+                               *q++ = '$';
+                               *q++ = '(';
+                               *q++ = '(';
+                       }
+                       p++;
+               } else if (c == CTLENDARI) {
+                       if (--cmdnleft > 0) {
+                               *q++ = ')';
+                               *q++ = ')';
+                       }
+               } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND)
+                       cmdnleft++; /* ignore */
                else
                        *q++ = c;
                if (--cmdnleft <= 0) {