sh: Expand assignment-like words specially for export/readonly/local.

Examples:
  export x=~
now expands the tilde
  local y=$1
is now safe, even if $1 contains IFS characters or metacharacters.

For a word to "look like an assignment", it must start with a name followed
by an equals sign, none of which may be quoted.

The special treatment applies when the first word (potentially after
"command") is "export", "readonly" or "local". There may be quoting
characters but no expansions. If "local" is overridden with a function there
is no special treatment ("export" and "readonly" cannot be overridden with a
function).

If things like
  local arr=(1 2 3)
are ever allowed in the future, they cannot call a "local" function. This
would either be a run-time error or it would call the builtin.

This matches Austin Group bug #351, planned for the next issue of POSIX.1.

PR:		bin/166771
This commit is contained in:
Jilles Tjoelker 2012-07-15 10:19:43 +00:00
parent 17fda71d9f
commit 84fbdd8ca0
9 changed files with 202 additions and 4 deletions

View File

@ -672,6 +672,52 @@ evalbackcmd(union node *n, struct backcmd *result)
result->fd, result->buf, result->nleft, result->jp));
}
static int
mustexpandto(const char *argtext, const char *mask)
{
for (;;) {
if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) {
argtext++;
continue;
}
if (*argtext == CTLESC)
argtext++;
else if (BASESYNTAX[(int)*argtext] == CCTL)
return (0);
if (*argtext != *mask)
return (0);
if (*argtext == '\0')
return (1);
argtext++;
mask++;
}
}
static int
isdeclarationcmd(struct narg *arg)
{
int have_command = 0;
if (arg == NULL)
return (0);
while (mustexpandto(arg->text, "command")) {
have_command = 1;
arg = &arg->next->narg;
if (arg == NULL)
return (0);
/*
* To also allow "command -p" and "command --" as part of
* a declaration command, add code here.
* We do not do this, as ksh does not do it either and it
* is not required by POSIX.
*/
}
return (mustexpandto(arg->text, "export") ||
mustexpandto(arg->text, "readonly") ||
(mustexpandto(arg->text, "local") &&
(have_command || !isfunc("local"))));
}
/*
* Check if a builtin can safely be executed in the same process,
* even though it should be in a subshell (command substitution).
@ -743,11 +789,12 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
exitstatus = 0;
for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
if (varflag && isassignment(argp->narg.text)) {
expandarg(argp, &varlist, EXP_VARTILDE);
expandarg(argp, varflag == 1 ? &varlist : &arglist,
EXP_VARTILDE);
continue;
}
} else if (varflag == 1)
varflag = isdeclarationcmd(&argp->narg) ? 2 : 0;
expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
varflag = 0;
}
*arglist.lastp = NULL;
*varlist.lastp = NULL;

View File

@ -648,6 +648,19 @@ unsetfunc(const char *name)
return (0);
}
/*
* Check if a function by a certain name exists.
*/
int
isfunc(const char *name)
{
struct tblentry *cmdp;
cmdp = cmdlookup(name, 0);
return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION);
}
/*
* Shared code for the following builtin commands:
* type, command -v, command -V

View File

@ -72,5 +72,6 @@ void hashcd(void);
void changepath(const char *);
void defun(const char *, union node *);
int unsetfunc(const char *);
int isfunc(const char *);
int typecmd_impl(int, char **, int, const char *);
void clearcmdentry(void);

View File

@ -32,7 +32,7 @@
.\" from: @(#)sh.1 8.6 (Berkeley) 5/4/95
.\" $FreeBSD$
.\"
.Dd November 5, 2011
.Dd July 15, 2012
.Dt SH 1
.Os
.Sh NAME
@ -1164,6 +1164,20 @@ Assignments are expanded differently from other words:
tilde expansion is also performed after the equals sign and after any colon
and usernames are also terminated by colons,
and field splitting and pathname expansion are not performed.
.Pp
This special expansion applies not only to assignments that form a simple
command by themselves or precede a command word,
but also to words passed to the
.Ic export ,
.Ic local
or
.Ic readonly
built-in commands that have this form.
For this, the builtin's name must be literal
(not the result of an expansion)
and may optionally be preceded by one or more literal instances of
.Ic command
without options.
.Ss Positional Parameters
A positional parameter is a parameter denoted by a number greater than zero.
The shell sets these initially to the values of its command line

View File

@ -0,0 +1,24 @@
# $FreeBSD$
w='@ @'
check() {
[ "$v" = "$w" ] || echo "Expected $w got $v"
}
export v=$w
check
HOME=/known/value
check() {
[ "$v" = ~ ] || echo "Expected $HOME got $v"
}
export v=~
check
check() {
[ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
}
export v=x:~
check

View File

@ -0,0 +1,30 @@
# $FreeBSD$
w='@ @'
check() {
[ "$v" = "$w" ] || echo "Expected $w got $v"
}
command export v=$w
check
command command export v=$w
check
HOME=/known/value
check() {
[ "$v" = ~ ] || echo "Expected $HOME got $v"
}
command export v=~
check
command command export v=~
check
check() {
[ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
}
command export v=x:~
check
command command export v=x:~
check

View File

@ -0,0 +1,28 @@
# $FreeBSD$
run_test() {
w='@ @'
check() {
[ "$v" = "$w" ] || echo "Expected $w got $v"
}
local v=$w
check
HOME=/known/value
check() {
[ "$v" = ~ ] || echo "Expected $HOME got $v"
}
local v=~
check
check() {
[ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
}
local v=x:~
check
}
run_test

View File

@ -0,0 +1,34 @@
# $FreeBSD$
run_test() {
w='@ @'
check() {
[ "$v" = "$w" ] || echo "Expected $w got $v"
}
command local v=$w
check
command command local v=$w
check
HOME=/known/value
check() {
[ "$v" = ~ ] || echo "Expected $HOME got $v"
}
command local v=~
check
command command local v=~
check
check() {
[ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
}
command local v=x:~
check
command command local v=x:~
check
}
run_test

View File

@ -0,0 +1,7 @@
# $FreeBSD$
w='@ @'
v=0 HOME=/known/value
readonly v=~:~/:$w
[ "$v" = "$HOME:$HOME/:$w" ] || echo "Expected $HOME/:$w got $v"