static char * stputs_split(const char *data, const char *syntax, int flag, char *p, struct worddest *dst) { const char *ifs; char c; ifs = ifsset() ? ifsval() : " \t\n"; while (*data) { CHECKSTRSPACE(2, p); c = *data++; if (strchr(ifs, c) != NULL) { NEXTWORD(c, flag, p, dst); continue; } if (flag & EXP_GLOB && syntax[(int)c] == CCTL) USTPUTC(CTLESC, p); USTPUTC(c, p); } return (p); }
/* * Break the argument string into pieces based upon IFS and add the * strings to the argument list. The regions of the string to be * searched for IFS characters have been stored by recordregion. * CTLESC characters are preserved but have little effect in this pass * other than escaping CTL* characters. In particular, they do not escape * IFS characters: that should be done with the ifsregion mechanism. * CTLQUOTEMARK characters are used to preserve empty quoted strings. * This pass treats them as a regular character, making the string non-empty. * Later, they are removed along with the other CTL* characters. */ static void ifsbreakup(char *string, struct arglist *arglist) { struct ifsregion *ifsp; struct strlist *sp; char *start; char *p; char *q; const char *ifs; const char *ifsspc; int had_param_ch = 0; start = string; if (ifslastp == NULL) { /* Return entire argument, IFS doesn't apply to any of it */ sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; return; } ifs = ifsset() ? ifsval() : " \t\n"; for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) { p = string + ifsp->begoff; while (p < string + ifsp->endoff) { q = p; if (*p == CTLESC) p++; if (ifsp->inquotes) { /* Only NULs (should be from "$@") end args */ had_param_ch = 1; if (*p != 0) { p++; continue; } ifsspc = NULL; } else { if (!strchr(ifs, *p)) { had_param_ch = 1; p++; continue; } ifsspc = strchr(" \t\n", *p); /* Ignore IFS whitespace at start */ if (q == start && ifsspc != NULL) { p++; start = p; continue; } had_param_ch = 0; } /* Save this argument... */ *q = '\0'; sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; p++; if (ifsspc != NULL) { /* Ignore further trailing IFS whitespace */ for (; p < string + ifsp->endoff; p++) { q = p; if (*p == CTLESC) p++; if (strchr(ifs, *p) == NULL) { p = q; break; } if (strchr(" \t\n", *p) == NULL) { p++; break; } } } start = p; } } /* * Save anything left as an argument. * Traditionally we have treated 'IFS=':'; set -- x$IFS' as * generating 2 arguments, the second of which is empty. * Some recent clarification of the Posix spec say that it * should only generate one.... */ if (had_param_ch || *start != 0) { sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; } }
static void varvalue(const char *name, int quoted, int subtype, int flag) { int num; char *p; int i; char sep[2]; char **ap; switch (*name) { case '$': num = rootpid; break; case '?': num = oexitstatus; break; case '#': num = shellparam.nparam; break; case '!': num = backgndpidval(); break; case '-': for (i = 0 ; i < NOPTS ; i++) { if (optlist[i].val) STPUTC(optlist[i].letter, expdest); } return; case '@': if (flag & EXP_FULL && quoted) { for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { strtodest(p, flag, subtype, quoted); if (*ap) STPUTC('\0', expdest); } return; } /* FALLTHROUGH */ case '*': if (ifsset()) sep[0] = ifsval()[0]; else sep[0] = ' '; sep[1] = '\0'; for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { strtodest(p, flag, subtype, quoted); if (!*ap) break; if (sep[0]) strtodest(sep, flag, subtype, quoted); else if (flag & EXP_FULL && !quoted && **ap != '\0') STPUTC('\0', expdest); } return; default: if (is_digit(*name)) { num = atoi(name); if (num == 0) p = arg0; else if (num > 0 && num <= shellparam.nparam) p = shellparam.p[num - 1]; else return; strtodest(p, flag, subtype, quoted); } return; } expdest = cvtnum(num, expdest); }
static char * evalvar(char *p, int flag) { int subtype; int varflags; char *var; const char *val; int patloc; int c; int set; int special; int startloc; int varlen; int varlenb; int easy; int quotes = flag & (EXP_FULL | EXP_CASE); int record = 0; varflags = (unsigned char)*p++; subtype = varflags & VSTYPE; var = p; special = 0; if (! is_name(*p)) special = 1; p = strchr(p, '=') + 1; again: /* jump here after setting a variable with ${var=text} */ if (varflags & VSLINENO) { set = 1; special = 1; val = NULL; } else if (special) { set = varisset(var, varflags & VSNUL); val = NULL; } else { val = bltinlookup(var, 1); if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { val = NULL; set = 0; } else set = 1; } varlen = 0; startloc = expdest - stackblock(); if (!set && uflag && *var != '@' && *var != '*') { switch (subtype) { case VSNORMAL: case VSTRIMLEFT: case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: case VSLENGTH: error("%.*s: parameter not set", (int)(p - var - 1), var); } } if (set && subtype != VSPLUS) { /* insert the value of the variable */ if (special) { if (varflags & VSLINENO) STPUTBIN(var, p - var - 1, expdest); else varvalue(var, varflags & VSQUOTE, subtype, flag); if (subtype == VSLENGTH) { varlenb = expdest - stackblock() - startloc; varlen = varlenb; if (localeisutf8) { val = stackblock() + startloc; for (;val != expdest; val++) if ((*val & 0xC0) == 0x80) varlen--; } STADJUST(-varlenb, expdest); } } else { if (subtype == VSLENGTH) { for (;*val; val++) if (!localeisutf8 || (*val & 0xC0) != 0x80) varlen++; } else strtodest(val, flag, subtype, varflags & VSQUOTE); } } if (subtype == VSPLUS) set = ! set; easy = ((varflags & VSQUOTE) == 0 || (*var == '@' && shellparam.nparam != 1)); switch (subtype) { case VSLENGTH: expdest = cvtnum(varlen, expdest); record = 1; break; case VSNORMAL: record = easy; break; case VSPLUS: case VSMINUS: if (!set) { argstr(p, flag | (flag & EXP_FULL ? EXP_SPLIT_LIT : 0) | (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0)); break; } record = easy; break; case VSTRIMLEFT: case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: if (!set) break; /* * Terminate the string and start recording the pattern * right after it */ STPUTC('\0', expdest); patloc = expdest - stackblock(); if (subevalvar(p, NULL, patloc, subtype, startloc, varflags, quotes) == 0) { int amount = (expdest - stackblock() - patloc) + 1; STADJUST(-amount, expdest); } /* Remove any recorded regions beyond start of variable */ removerecordregions(startloc); record = 1; break; case VSASSIGN: case VSQUESTION: if (!set) { if (subevalvar(p, var, 0, subtype, startloc, varflags, quotes)) { varflags &= ~VSNUL; /* * Remove any recorded regions beyond * start of variable */ removerecordregions(startloc); goto again; } break; } record = easy; break; case VSERROR: c = p - var - 1; error("${%.*s%s}: Bad substitution", c, var, (c > 0 && *p != CTLENDVAR) ? "..." : ""); default: abort(); } if (record) recordregion(startloc, expdest - stackblock(), varflags & VSQUOTE || (ifsset() && ifsval()[0] == '\0' && (*var == '@' || *var == '*'))); if (subtype != VSNORMAL) { /* skip to end of alternative */ int nesting = 1; for (;;) { if ((c = *p++) == CTLESC) p++; else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { if (set) argbackq = argbackq->next; } else if (c == CTLVAR) { if ((*p++ & VSTYPE) != VSNORMAL) nesting++; } else if (c == CTLENDVAR) { if (--nesting == 0) break; } } } return p; }
STATIC void varvalue(shinstance *psh, char *name, int quoted, int subtype, int flag) { int num; char *p; int i; char sep; char **ap; char const *syntax; #define STRTODEST(p) \ do {\ if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH) { \ syntax = quoted? DQSYNTAX : BASESYNTAX; \ while (*p) { \ if (syntax[(int)*p] == CCTL) \ STPUTC(psh, CTLESC, psh->expdest); \ STPUTC(psh, *p++, psh->expdest); \ } \ } else \ while (*p) \ STPUTC(psh, *p++, psh->expdest); \ } while (0) switch (*name) { case '$': num = psh->rootpid; goto numvar; case '?': num = psh->exitstatus; goto numvar; case '#': num = psh->shellparam.nparam; goto numvar; case '!': num = psh->backgndpid; numvar: psh->expdest = cvtnum(psh, num, psh->expdest); break; case '-': for (i = 0; psh->optlist[i].name; i++) { if (psh->optlist[i].val) STPUTC(psh, psh->optlist[i].letter, psh->expdest); } break; case '@': if (flag & EXP_FULL && quoted) { for (ap = psh->shellparam.p ; (p = *ap++) != NULL ; ) { STRTODEST(p); if (*ap) STPUTC(psh, '\0', psh->expdest); } break; } /* fall through */ case '*': if (ifsset(psh) != 0) sep = ifsval(psh)[0]; else sep = ' '; for (ap = psh->shellparam.p ; (p = *ap++) != NULL ; ) { STRTODEST(p); if (*ap && sep) STPUTC(psh, sep, psh->expdest); } break; case '0': p = psh->arg0; STRTODEST(p); break; default: if (is_digit(*name)) { num = atoi(name); if (num > 0 && num <= psh->shellparam.nparam) { p = psh->shellparam.p[num - 1]; STRTODEST(p); } } break; } }
STATIC void argstr(shinstance *psh, char *p, int flag) { char c; int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ int firsteq = 1; const char *ifs = NULL; int ifs_split = EXP_IFS_SPLIT; if (flag & EXP_IFS_SPLIT) ifs = ifsset(psh) ? ifsval(psh) : " \t\n"; if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) p = exptilde(psh, p, flag); for (;;) { switch (c = *p++) { case '\0': case CTLENDVAR: /* end of expanding yyy in ${xxx-yyy} */ return; case CTLQUOTEMARK: /* "$@" syntax adherence hack */ if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=') break; if ((flag & EXP_FULL) != 0) STPUTC(psh, c, psh->expdest); ifs_split = 0; break; case CTLQUOTEEND: ifs_split = EXP_IFS_SPLIT; break; case CTLESC: if (quotes) STPUTC(psh, c, psh->expdest); c = *p++; STPUTC(psh, c, psh->expdest); break; case CTLVAR: p = evalvar(psh, p, (flag & ~EXP_IFS_SPLIT) | (flag & ifs_split)); break; case CTLBACKQ: case CTLBACKQ|CTLQUOTE: expbackq(psh, psh->argbackq->n, c & CTLQUOTE, flag); psh->argbackq = psh->argbackq->next; break; case CTLENDARI: expari(psh, flag); break; case ':': case '=': /* * sort of a hack - expand tildes in variable * assignments (after the first '=' and after ':'s). */ STPUTC(psh, c, psh->expdest); if (flag & EXP_VARTILDE && *p == '~') { if (c == '=') { if (firsteq) firsteq = 0; else break; } p = exptilde(psh, p, flag); } break; default: STPUTC(psh, c, psh->expdest); if (flag & EXP_IFS_SPLIT & ifs_split && strchr(ifs, c) != NULL) { /* We need to get the output split here... */ recordregion(psh, (int)(psh->expdest - stackblock(psh) - 1), (int)(psh->expdest - stackblock(psh)), 0); } break; } } }
STATIC void varvalue(char *name, int quoted, int subtype, int flag) { int num; char *p; int i; char sep; char **ap; char const *syntax; #define STRTODEST(p) \ do {\ if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH) { \ syntax = quoted? DQSYNTAX : BASESYNTAX; \ while (*p) { \ if (syntax[(int)*p] == CCTL) \ STPUTC(CTLESC, expdest); \ STPUTC(*p++, expdest); \ } \ } else \ while (*p) \ STPUTC(*p++, expdest); \ } while (0) switch (*name) { case '$': num = rootpid; goto numvar; case '?': num = exitstatus; goto numvar; case '#': num = shellparam.nparam; goto numvar; case '!': num = backgndpid; numvar: expdest = cvtnum(num, expdest); break; case '-': for (i = 0; optlist[i].name; i++) { if (optlist[i].val && optlist[i].letter) STPUTC(optlist[i].letter, expdest); } break; case '@': if (flag & EXP_FULL && quoted) { for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { STRTODEST(p); if (*ap) /* A NUL separates args inside "" */ STPUTC('\0', expdest); } break; } /* fall through */ case '*': if (ifsset() != 0) sep = ifsval()[0]; else sep = ' '; for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { STRTODEST(p); if (*ap && sep) STPUTC(sep, expdest); } break; case '0': p = arg0; STRTODEST(p); break; default: if (is_digit(*name)) { num = atoi(name); if (num > 0 && num <= shellparam.nparam) { p = shellparam.p[num - 1]; STRTODEST(p); } } break; } }
static void varvalue(const_cstring_t name, int32_t quoted, int32_t subtype, int32_t flag) { int32_t num; cstring_t p; int32_t i; char sep; cstring_t* ap; switch (*name) { case '$': num = rootpid; goto numvar; case '?': num = oexitstatus; goto numvar; case '#': num = shellparam.nparam; goto numvar; case '!': num = backgndpidval(); numvar: expdest = cvtnum(num, expdest); break; case '-': for (i = 0 ; i < NOPTS ; i++) { if (optlist[i].val) STPUTC(optlist[i].letter, expdest); } break; case '@': if (flag & EXP_FULL && quoted) { for (ap = shellparam.p ; (p = *ap++) != NULL ;) { strtodest(p, flag, subtype, quoted); if (*ap) STPUTC('\0', expdest); } break; } /* FALLTHROUGH */ case '*': if (ifsset()) sep = ifsval()[0]; else sep = ' '; for (ap = shellparam.p ; (p = *ap++) != NULL ;) { strtodest(p, flag, subtype, quoted); if (!*ap) break; if (sep || (flag & EXP_FULL && !quoted &&** ap != '\0')) STPUTC(sep, expdest); } break; default: if (is_digit(*name)) { num = atoi(name); if (num == 0) p = arg0; else if (num > 0 && num <= shellparam.nparam) p = shellparam.p[num - 1]; else break; strtodest(p, flag, subtype, quoted); } break; } }
static void varvalue(const char *name, int quoted, int subtype, int flag, struct worddest *dst) { int num; char *p; int i; int splitlater; char sep[2]; char **ap; char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1]; if (subtype == VSLENGTH) flag &= ~EXP_FULL; splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX; switch (*name) { case '$': num = rootpid; break; case '?': num = oexitstatus; break; case '#': num = shellparam.nparam; break; case '!': num = backgndpidval(); break; case '-': p = buf; for (i = 0 ; i < NSHORTOPTS ; i++) { if (optlist[i].val) *p++ = optlist[i].letter; } *p = '\0'; strtodest(buf, flag, subtype, quoted, dst); return; case '@': if (flag & EXP_SPLIT && quoted) { for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { strtodest(p, flag, subtype, quoted, dst); if (*ap) { if (splitlater) STPUTC('\0', expdest); else NEXTWORD('\0', flag, expdest, dst); } } if (shellparam.nparam > 0) dst->state = WORD_QUOTEMARK; return; } /* FALLTHROUGH */ case '*': if (ifsset()) sep[0] = ifsval()[0]; else sep[0] = ' '; sep[1] = '\0'; for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { strtodest(p, flag, subtype, quoted, dst); if (!*ap) break; if (sep[0]) strtodest(sep, flag, subtype, quoted, dst); else if (flag & EXP_SPLIT && !quoted && **ap != '\0') { if (splitlater) STPUTC('\0', expdest); else NEXTWORD('\0', flag, expdest, dst); } } return; default: if (is_digit(*name)) { num = atoi(name); if (num == 0) p = arg0; else if (num > 0 && num <= shellparam.nparam) p = shellparam.p[num - 1]; else return; strtodest(p, flag, subtype, quoted, dst); } return; } cvtnum(num, buf); strtodest(buf, flag, subtype, quoted, dst); }
/* * Perform command substitution. */ static void expbackq(union node *cmd, int quoted, int flag, struct worddest *dst) { struct backcmd in; int i; char buf[128]; char *p; char *dest = expdest; struct nodelist *saveargbackq; char lastc; char const *syntax = quoted? DQSYNTAX : BASESYNTAX; int quotes = flag & (EXP_GLOB | EXP_CASE); size_t nnl; const char *ifs; INTOFF; saveargbackq = argbackq; p = grabstackstr(dest); evalbackcmd(cmd, &in); ungrabstackstr(p, dest); argbackq = saveargbackq; p = in.buf; lastc = '\0'; nnl = 0; if (!quoted && flag & EXP_SPLIT) ifs = ifsset() ? ifsval() : " \t\n"; else ifs = ""; /* Don't copy trailing newlines */ for (;;) { if (--in.nleft < 0) { if (in.fd < 0) break; while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR); TRACE(("expbackq: read returns %d\n", i)); if (i <= 0) break; p = buf; in.nleft = i - 1; } lastc = *p++; if (lastc == '\0') continue; if (lastc == '\n') { nnl++; } else { if (nnl > 0) { if (strchr(ifs, '\n') != NULL) { NEXTWORD('\n', flag, dest, dst); nnl = 0; } else { CHECKSTRSPACE(nnl + 2, dest); while (nnl > 0) { nnl--; USTPUTC('\n', dest); } } } if (strchr(ifs, lastc) != NULL) NEXTWORD(lastc, flag, dest, dst); else { CHECKSTRSPACE(2, dest); if (quotes && syntax[(int)lastc] == CCTL) USTPUTC(CTLESC, dest); USTPUTC(lastc, dest); } } } if (in.fd >= 0) close(in.fd); if (in.buf) ckfree(in.buf); if (in.jp) exitstatus = waitforjob(in.jp, (int *)NULL); TRACE(("expbackq: size=%td: \"%.*s\"\n", ((dest - stackblock()) - startloc), (int)((dest - stackblock()) - startloc), stackblock() + startloc)); expdest = dest; INTON; }
/* * Perform parameter expansion, command substitution and arithmetic * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'. * This is used to expand word in ${var+word} etc. * If EXP_GLOB or EXP_CASE are set, keep and/or generate CTLESC * characters to allow for further processing. * * If EXP_SPLIT is set, dst receives any complete words produced. */ static char * argstr(char *p, int flag, struct worddest *dst) { char c; int quotes = flag & (EXP_GLOB | EXP_CASE); /* do CTLESC */ int firsteq = 1; int split_lit; int lit_quoted; split_lit = flag & EXP_SPLIT_LIT; lit_quoted = flag & EXP_LIT_QUOTED; flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED); if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) p = exptilde(p, flag); for (;;) { CHECKSTRSPACE(2, expdest); switch (c = *p++) { case '\0': return (p - 1); case CTLENDVAR: case CTLENDARI: return (p); case CTLQUOTEMARK: lit_quoted = 1; /* "$@" syntax adherence hack */ if (p[0] == CTLVAR && (p[1] & VSQUOTE) != 0 && p[2] == '@' && p[3] == '=') break; if ((flag & EXP_SPLIT) != 0 && expdest == stackblock()) dst->state = WORD_QUOTEMARK; break; case CTLQUOTEEND: lit_quoted = 0; break; case CTLESC: c = *p++; if (split_lit && !lit_quoted && strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { NEXTWORD(c, flag, expdest, dst); break; } if (quotes) USTPUTC(CTLESC, expdest); USTPUTC(c, expdest); break; case CTLVAR: p = evalvar(p, flag, dst); break; case CTLBACKQ: case CTLBACKQ|CTLQUOTE: expbackq(argbackq->n, c & CTLQUOTE, flag, dst); argbackq = argbackq->next; break; case CTLARI: p = expari(p, flag, dst); break; case ':': case '=': /* * sort of a hack - expand tildes in variable * assignments (after the first '=' and after ':'s). */ if (split_lit && !lit_quoted && strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { NEXTWORD(c, flag, expdest, dst); break; } USTPUTC(c, expdest); if (flag & EXP_VARTILDE && *p == '~' && (c != '=' || firsteq)) { if (c == '=') firsteq = 0; p = exptilde(p, flag); } break; default: if (split_lit && !lit_quoted && strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { NEXTWORD(c, flag, expdest, dst); break; } USTPUTC(c, expdest); } } }
/* * Break the argument string into pieces based upon IFS and add the * strings to the argument list. The regions of the string to be * searched for IFS characters have been stored by recordregion. */ STATIC void ifsbreakup(char *string, struct arglist *arglist) { struct ifsregion *ifsp; struct strlist *sp; char *start; char *p; char *q; char *ifs; int ifsspc; int nulonly; start = string; ifsspc = 0; nulonly = 0; if (ifslastp != NULL) { ifsp = &ifsfirst; do { p = string + ifsp->begoff; nulonly = ifsp->nulonly; ifs = nulonly ? nullstr : ( ifsset() ? ifsval() : " \t\n" ); ifsspc = 0; while (p < string + ifsp->endoff) { q = p; if (*p == CTLESC) p++; if (strchr(ifs, *p)) { if (!nulonly) ifsspc = (strchr(" \t\n", *p) != NULL); /* Ignore IFS whitespace at start */ if (q == start && ifsspc) { p++; start = p; continue; } *q = '\0'; sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; p++; if (!nulonly) { for (;;) { if (p >= string + ifsp->endoff) { break; } q = p; if (*p == CTLESC) p++; if (strchr(ifs, *p) == NULL ) { p = q; break; } else if (strchr(" \t\n",*p) == NULL) { if (ifsspc) { p++; ifsspc = 0; } else { p = q; break; } } else p++; } } start = p; } else p++; } } while ((ifsp = ifsp->next) != NULL); if (*start || (!ifsspc && start > string)) { sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; } } else { sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; } }