* Copyright (c) 1991 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)expand.c 5.1 (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 */
* Set if the last argument processed had /u/logname expanded. This
* variable is read by the cd command.
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
*);
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
void recordregion();
STATIC
void ifsbreakup();
STATIC
void expandmeta();
STATIC
struct strlist
*expsort();
STATIC
struct strlist
*msort();
STATIC
char *expudir(char *);
* 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 full is true,
* perform splitting and file name expansion. When arglist is NULL, perform
* here document expansion.
expandarg(arg
, arglist
, full
)
argbackq
= arg
->narg
.backquote
;
argstr(arg
->narg
.text
, full
);
return; /* here document expanded */
p
= grabstackstr(expdest
);
exparg
.lastp
= &exparg
.list
;
exparg
.lastp
= &exparg
.list
;
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 full is set, output CTLESC
* characters to allow for further processing. If full is not set, treat
* $@ like $* since no splitting will be performed.
expbackq(argbackq
->n
, c
& CTLQUOTE
, full
);
argbackq
= argbackq
->next
;
* Expand stuff in backwards quotes.
expbackq(cmd
, quoted
, full
)
struct ifsregion saveifs
, *savelastp
;
struct nodelist
*saveargbackq
;
int startloc
= dest
- stackblock();
char const *syntax
= quoted
? DQSYNTAX
: BASESYNTAX
;
while ((i
= read(in
.fd
, buf
, sizeof buf
)) < 0 && errno
== EINTR
);
TRACE(("expbackq: read returns %d\n", i
));
if (full
&& syntax
[lastc
] == CCTL
)
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
subtype
= flags
& VSTYPE
;
again
: /* jump here after setting a variable with ${var=text} */
if (val
== NULL
|| (flags
& VSNUL
) && val
[0] == '\0') {
startloc
= expdest
- stackblock();
if (set
&& subtype
!= VSPLUS
) {
/* insert the value of the variable */
varvalue(*var
, flags
& VSQUOTE
, full
);
char const *syntax
= (flags
& VSQUOTE
)? DQSYNTAX
: BASESYNTAX
;
if (full
&& syntax
[*val
] == CCTL
)
if (((flags
& VSQUOTE
) == 0 || (*var
== '@' && shellparam
.nparam
!= 1))
&& (set
|| subtype
== VSNORMAL
))
recordregion(startloc
, expdest
- stackblock(), flags
& VSQUOTE
);
if (! set
&& subtype
!= VSNORMAL
) {
if (subtype
== VSPLUS
|| subtype
== VSMINUS
) {
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
, (flags
& 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
)
} while ((num
/= 10) != 0);
for (i
= 0 ; optchar
[i
] ; i
++) {
STPUTC(optchar
[i
], expdest
);
syntax
= quoted
? DQSYNTAX
: BASESYNTAX
;
for (ap
= shellparam
.p
; (p
= *ap
++) != NULL
; ) {
/* should insert CTLESC characters */
syntax
= quoted
? DQSYNTAX
: BASESYNTAX
;
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
;
if (p
[0] == '/' && p
[1] == 'u' && p
[2] == '/')
str
->text
= p
= expudir(p
);
for (;;) { /* fast check for meta chars */
if (c
== '*' || c
== '?' || c
== '[' || c
== '!')
savelastp
= exparg
.lastp
;
expdir
= ckmalloc(1024); /* I hope this is big enough */
expmeta(expdir
, str
->text
);
if (exparg
.lastp
== savelastp
) {
exparg
.lastp
= &str
->next
;
*savelastp
= sp
= expsort(*savelastp
);
exparg
.lastp
= &sp
->next
;
* Expand /u/username into the home directory for the specified user.
* We could use the getpw stuff here, but then we would have to load
* in stdio and who knows what else.
register char *p
, *q
, *r
;
r
= path
; /* result on failure */
p
= r
+ 3; /* the 3 skips "/u/" */
while (*p
&& *p
!= '/') {
if (q
>= name
+ MAXLOGNAME
- 1)
return r
; /* fail, name too long */
setinputfile("/etc/passwd", 1);
while (pfgets(line
, MAXPWLINE
) != NULL
) {
if (line
[0] == name
[0] && prefix(name
, line
) && *q
== ':') {
/* skip to start of home directory */
while (*++q
&& *q
!= ':');
break; /* fail, corrupted /etc/passwd */
for (r
= q
; *r
&& *r
!= '\n' && *r
!= ':' ; r
++);
*r
= '\0'; /* nul terminate home directory */
i
= r
- q
; /* i = strlen(q) */
r
= stalloc(i
+ strlen(p
) + 1);
TRACE(("expudir converts %s to %s\n", path
, r
));
* 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
, 0);
p
= grabstackstr(expdest
);
result
= patmatch(p
, val
);