* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
* This code is derived from software contributed to Berkeley by
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)expand.c 8.2 (Berkeley) %G%";
* Routines to expand arguments to commands. We have to deal with
* backquotes, shell variables, and file metacharacters.
* Structure specifying which parts of the string should be searched
struct ifsregion
*next
; /* next region in list */
int begoff
; /* offset of start of region */
int endoff
; /* offset of end of region */
int nulonly
; /* search for nul bytes only */
char *expdest
; /* output of current string */
struct nodelist
*argbackq
; /* list of back quote expressions */
struct ifsregion ifsfirst
; /* first struct in list of ifs regions */
struct ifsregion
*ifslastp
; /* last struct in list */
struct arglist exparg
; /* holds expanded arg list */
STATIC
void argstr(char *, int);
STATIC
void expbackq(union node
*, int, int);
STATIC
char *evalvar(char *, int);
STATIC
int varisset(int);
STATIC
void varvalue(int, int, int);
STATIC
void recordregion(int, int, int);
STATIC
void ifsbreakup(char *, struct arglist
*);
STATIC
void expandmeta(struct strlist
*, int);
STATIC
void expmeta(char *, char *);
STATIC
void addfname(char *);
STATIC
struct strlist
*expsort(struct strlist
*);
STATIC
struct strlist
*msort(struct strlist
*, int);
STATIC
int pmatch(char *, char *);
STATIC
char *exptilde(char *, int);
STATIC
void recordregion();
STATIC
void ifsbreakup();
STATIC
void expandmeta();
STATIC
struct strlist
*expsort();
STATIC
struct strlist
*msort();
* Expand shell variables and backquotes inside a here document.
union node
*arg
; /* the document */
int fd
; /* where to write the expanded version */
expandarg(arg
, (struct arglist
*)NULL
, 0);
xwrite(fd
, stackblock(), expdest
- stackblock());
* Perform variable substitution and command substitution on an argument,
* placing the resulting list of arguments in arglist. If EXP_FULL is true,
* perform splitting and file name expansion. When arglist is NULL, perform
* here document expansion.
expandarg(arg
, arglist
, flag
)
argbackq
= arg
->narg
.backquote
;
argstr(arg
->narg
.text
, flag
);
return; /* here document expanded */
p
= grabstackstr(expdest
);
exparg
.lastp
= &exparg
.list
;
exparg
.lastp
= &exparg
.list
;
expandmeta(exparg
.list
, flag
);
if (flag
& EXP_REDIR
) /*XXX - for now, just remove escapes */
sp
= (struct strlist
*)stalloc(sizeof (struct strlist
));
exparg
.lastp
= &sp
->next
;
while (ifsfirst
.next
!= NULL
) {
ifsp
= ifsfirst
.next
->next
;
*arglist
->lastp
= exparg
.list
;
arglist
->lastp
= exparg
.lastp
;
* Perform variable and command substitution. If EXP_FULL is set, output CTLESC
* characters to allow for further processing. Otherwise treat
* $@ like $* since no splitting will be performed.
int quotes
= flag
& (EXP_FULL
| EXP_CASE
); /* do CTLESC */
if (*p
== '~' && (flag
& (EXP_TILDE
| EXP_VARTILDE
)))
case CTLENDVAR
: /* ??? */
expbackq(argbackq
->n
, c
& CTLQUOTE
, flag
);
argbackq
= argbackq
->next
;
* sort of a hack - expand tildes in variable
* assignments (after the first '=' and after ':'s).
if (flag
& EXP_VARTILDE
&& *p
== '~') {
int quotes
= flag
& (EXP_FULL
| EXP_CASE
);
if (*(startp
+1) == '\0') {
if ((home
= lookupvar("HOME")) == NULL
)
if ((pw
= getpwnam(startp
+1)) == NULL
)
if (quotes
&& SQSYNTAX
[c
] == CCTL
)
* Expand arithmetic expression. Backup to start of expression,
* evaluate, place result in (backed up) result, adjust string position.
int quotes
= flag
& (EXP_FULL
| EXP_CASE
);
* This routine is slightly over-compilcated for
* efficiency. First we make sure there is
* enough space for the result, which may be bigger
* than the expression if we add exponentation. Next we
* scan backwards looking for the start of arithmetic. If the
* next previous character is a CTLESC character, then we
* have to rescan starting from the beginning since CTLESC
* characters have to be processed left to right.
CHECKSTRSPACE(8, expdest
);
while (*p
!= CTLARI
&& p
>= start
)
error("missing CTLARI (shouldn't happen)");
if (p
> start
&& *(p
-1) == CTLESC
)
for (p
= start
; *p
!= CTLARI
; p
++)
fmtstr(p
, 10, "%d", result
);
result
= expdest
- p
+ 1;
STADJUST(-result
, expdest
);
* Expand stuff in backwards quotes.
expbackq(cmd
, quoted
, flag
)
struct ifsregion saveifs
, *savelastp
;
struct nodelist
*saveargbackq
;
int startloc
= dest
- stackblock();
char const *syntax
= quoted
? DQSYNTAX
: BASESYNTAX
;
int quotes
= flag
& (EXP_FULL
| EXP_CASE
);
while ((i
= read(in
.fd
, buf
, sizeof buf
)) < 0 && errno
== EINTR
);
TRACE(("expbackq: read returns %d\n", i
));
if (quotes
&& syntax
[lastc
] == CCTL
)
exitstatus
= waitforjob(in
.jp
);
recordregion(startloc
, dest
- stackblock(), 0);
TRACE(("evalbackq: size=%d: \"%.*s\"\n",
(dest
- stackblock()) - startloc
,
(dest
- stackblock()) - startloc
,
stackblock() + startloc
));
* Expand a variable, and return a pointer to the next character in the
int quotes
= flag
& (EXP_FULL
| EXP_CASE
);
subtype
= varflags
& VSTYPE
;
again
: /* jump here after setting a variable with ${var=text} */
if (val
== NULL
|| (varflags
& VSNUL
) && val
[0] == '\0') {
startloc
= expdest
- stackblock();
if (set
&& subtype
!= VSPLUS
) {
/* insert the value of the variable */
varvalue(*var
, varflags
& VSQUOTE
, flag
& EXP_FULL
);
char const *syntax
= (varflags
& VSQUOTE
)? DQSYNTAX
: BASESYNTAX
;
if (quotes
&& syntax
[*val
] == CCTL
)
if (((varflags
& VSQUOTE
) == 0 || (*var
== '@' && shellparam
.nparam
!= 1))
&& (set
|| subtype
== VSNORMAL
))
recordregion(startloc
, expdest
- stackblock(), varflags
& VSQUOTE
);
if (! set
&& subtype
!= VSNORMAL
) {
if (subtype
== VSPLUS
|| subtype
== VSMINUS
) {
struct nodelist
*saveargbackq
= argbackq
;
startp
= stackblock() + startloc
;
if (subtype
== VSASSIGN
) {
STADJUST(startp
- expdest
, expdest
);
/* subtype == VSQUESTION */
outfmt(&errout
, "%s\n", startp
);
error("%.*s: parameter %snot set", p
- var
- 1,
var
, (varflags
& VSNUL
)? "null or " : nullstr
);
if (subtype
!= VSNORMAL
) { /* skip to end of alternative */
if ((c
= *p
++) == CTLESC
)
else if (c
== CTLBACKQ
|| c
== (CTLBACKQ
|CTLQUOTE
)) {
argbackq
= argbackq
->next
;
} else if (c
== CTLVAR
) {
if ((*p
++ & VSTYPE
) != VSNORMAL
)
} else if (c
== CTLENDVAR
) {
* Test whether a specialized variable is set.
} else if (name
== '@' || name
== '*') {
if (*shellparam
.p
== NULL
)
} else if ((unsigned)(name
-= '1') <= '9' - '1') {
* Add the value of a specialized variable to the stack string.
varvalue(name
, quoted
, allow_split
)
syntax = quoted? DQSYNTAX : BASESYNTAX; \
if (syntax[*p] == CCTL) \
STPUTC(CTLESC, expdest); \
} while ((num
/= 10) != 0);
for (i
= 0 ; i
< NOPTS
; i
++) {
STPUTC(optlist
[i
].letter
, expdest
);
for (ap
= shellparam
.p
; (p
= *ap
++) != NULL
; ) {
if ((unsigned)(name
-= '1') <= '9' - '1') {
* Record the the fact that we have to scan this region of the
* string for IFS characters.
recordregion(start
, end
, nulonly
) {
register struct ifsregion
*ifsp
;
ifsp
= (struct ifsregion
*)ckmalloc(sizeof (struct ifsregion
));
ifslastp
->begoff
= start
;
ifslastp
->nulonly
= nulonly
;
* 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.
ifsbreakup(string
, arglist
)
p
= string
+ ifsp
->begoff
;
ifs
= ifsp
->nulonly
? nullstr
: ifsval();
while (p
< string
+ ifsp
->endoff
) {
if (q
> start
|| *ifs
!= ' ') {
sp
= (struct strlist
*)stalloc(sizeof *sp
);
arglist
->lastp
= &sp
->next
;
if (p
>= string
+ ifsp
->endoff
)
if (strchr(ifs
, *p
++) == NULL
) {
} while ((ifsp
= ifsp
->next
) != NULL
);
if (*start
|| (*ifs
!= ' ' && start
> string
)) {
sp
= (struct strlist
*)stalloc(sizeof *sp
);
arglist
->lastp
= &sp
->next
;
sp
= (struct strlist
*)stalloc(sizeof *sp
);
arglist
->lastp
= &sp
->next
;
* Expand shell metacharacters. At this point, the only control characters
* should be escapes. The results are stored in the list exparg.
struct strlist
**savelastp
;
for (;;) { /* fast check for meta chars */
if (c
== '*' || c
== '?' || c
== '[' || c
== '!')
savelastp
= exparg
.lastp
;
int i
= strlen(str
->text
);
expdir
= ckmalloc(i
< 2048 ? 2048 : i
); /* XXX */
expmeta(expdir
, str
->text
);
if (exparg
.lastp
== savelastp
) {
exparg
.lastp
= &str
->next
;
*savelastp
= sp
= expsort(*savelastp
);
exparg
.lastp
= &sp
->next
;
* Do metacharacter (i.e. *, ?, [...]) expansion.
if (*p
== '*' || *p
== '?')
if (*q
== '/' || *q
== '\0')
} else if (*p
== '!' && p
[1] == '!' && (p
== name
|| p
[-1] == '/')) {
if (metaflag
== 0) { /* we've reached the end of the file name */
if (metaflag
== 0 || stat(expdir
, &statb
) >= 0)
} else if (enddir
== expdir
+ 1 && *expdir
== '/') {
if ((dirp
= opendir(p
)) == NULL
)
if (start
[0] == '.' || start
[0] == CTLESC
&& start
[1] == '.')
while (! int_pending() && (dp
= readdir(dirp
)) != NULL
) {
if (dp
->d_name
[0] == '.' && ! matchdot
)
if (patmatch(start
, dp
->d_name
)) {
scopy(dp
->d_name
, enddir
);
for (p
= enddir
, q
= dp
->d_name
; *p
++ = *q
++ ;);
* Add a file name to the list.
p
= stalloc(strlen(name
) + 1);
sp
= (struct strlist
*)stalloc(sizeof *sp
);
exparg
.lastp
= &sp
->next
;
* Sort the results of file name expansion. It calculates the number of
* strings to sort and then calls msort (short for merge sort) to do the
for (sp
= str
; sp
; sp
= sp
->next
)
for (n
= half
; --n
>= 0 ; ) {
q
->next
= NULL
; /* terminate first half of list */
q
= msort(list
, half
); /* sort first half of list */
p
= msort(p
, len
- half
); /* sort second half */
if (strcmp(p
->text
, q
->text
) < 0) {
if ((p
= *lpp
) == NULL
) {
if ((q
= *lpp
) == NULL
) {
* Returns true if the pattern matches the string.
patmatch(pattern
, string
)
if (pattern
[0] == '!' && pattern
[1] == '!')
return 1 - pmatch(pattern
+ 2, string
);
return pmatch(pattern
, string
);
if (c
!= CTLESC
&& c
!= '?' && c
!= '*' && c
!= '[') {
goto dft
; /* no matching ] */
if (*p
== '-' && p
[1] != ']') {
if (chr
>= c
&& chr
<= *p
)
} while ((c
= *p
++) != ']');
* Remove any CTLESC characters from a string.
* See if a pattern matches in a case statement.
argbackq
= pattern
->narg
.backquote
;
argstr(pattern
->narg
.text
, EXP_TILDE
| EXP_CASE
);
p
= grabstackstr(expdest
);
result
= patmatch(p
, val
);