sh: Apply locale vars on builtins, recognize LC_MESSAGES as a locale var.

This allows doing things like LC_ALL=C some_builtin to run a builtin under a
different locale, just like is possible with external programs. The
immediate reason is that this allows making printf(1) a builtin without
breaking things like LC_NUMERIC=C printf '%f\n' 1.2

This change also affects special builtins, as even though the assignment is
persistent, the export is only to the builtin (unless the variable was
already exported).

Note: for this to work for builtins that also exist as external programs
such as /bin/test, the setlocale() call must be under #ifndef SHELL. The
shell will do the setlocale() calls which may not agree with the environment
variables.
This commit is contained in:
Jilles Tjoelker 2010-05-05 21:48:40 +00:00
parent d18129ea1d
commit e4b50334ec
4 changed files with 207 additions and 7 deletions

View File

@ -937,6 +937,8 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
cmdentry.special = 1;
if (cmdentry.special)
listsetvar(cmdenviron);
if (argc > 0)
bltinsetlocale();
commandname = argv[0];
argptr = argv + 1;
nextopt_optptr = NULL; /* initialize nextopt */
@ -944,6 +946,8 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv);
flushall();
cmddone:
if (argc > 0)
bltinunsetlocale();
cmdenviron = NULL;
out1 = &output;
out2 = &errout;

View File

@ -122,6 +122,14 @@ STATIC const struct varinit varinit[] = {
STATIC struct var *vartab[VTABSIZE];
STATIC const char *const locale_names[7] = {
"LC_COLLATE", "LC_CTYPE", "LC_MONETARY",
"LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL
};
STATIC const int locale_categories[7] = {
LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0
};
STATIC struct var **hashvar(const char *);
STATIC int varequal(const char *, const char *);
STATIC int localevar(const char *);
@ -258,11 +266,7 @@ setvar(const char *name, const char *val, int flags)
STATIC int
localevar(const char *s)
{
static const char *lnames[7] = {
"ALL", "COLLATE", "CTYPE", "MONETARY",
"NUMERIC", "TIME", NULL
};
const char **ss;
const char *const *ss;
if (*s != 'L')
return 0;
@ -270,8 +274,10 @@ localevar(const char *s)
return 1;
if (strncmp(s + 1, "C_", 2) != 0)
return 0;
for (ss = lnames; *ss ; ss++)
if (varequal(s + 3, *ss))
if (varequal(s + 3, "ALL"))
return 1;
for (ss = locale_names; *ss ; ss++)
if (varequal(s + 3, *ss + 3))
return 1;
return 0;
}
@ -437,6 +443,61 @@ bltinlookup(const char *name, int doall)
}
/*
* Set up locale for a builtin (LANG/LC_* assignments).
*/
void
bltinsetlocale(void)
{
struct strlist *lp;
int act = 0;
char *loc, *locdef;
int i;
for (lp = cmdenviron ; lp ; lp = lp->next) {
if (localevar(lp->text)) {
act = 1;
break;
}
}
if (!act)
return;
loc = bltinlookup("LC_ALL", 0);
INTOFF;
if (loc != NULL) {
setlocale(LC_ALL, loc);
INTON;
return;
}
locdef = bltinlookup("LANG", 0);
for (i = 0; locale_names[i] != NULL; i++) {
loc = bltinlookup(locale_names[i], 0);
if (loc == NULL)
loc = locdef;
if (loc != NULL)
setlocale(locale_categories[i], loc);
}
INTON;
}
/*
* Undo the effect of bltinlocaleset().
*/
void
bltinunsetlocale(void)
{
struct strlist *lp;
INTOFF;
for (lp = cmdenviron ; lp ; lp = lp->next) {
if (localevar(lp->text)) {
setlocale(LC_ALL, "");
return;
}
}
INTON;
}
/*
* Generate a list of exported variables. This routine is used to construct

View File

@ -107,6 +107,8 @@ struct strlist;
void listsetvar(struct strlist *);
char *lookupvar(const char *);
char *bltinlookup(const char *, int);
void bltinsetlocale(void);
void bltinunsetlocale(void);
char **environment(void);
int showvarscmd(int, char **);
int exportcmd(int, char **);

View File

@ -0,0 +1,133 @@
# $FreeBSD$
# Note: this test depends on strerror() using locale.
failures=0
check() {
if ! eval "[ $1 ]"; then
echo "Failed: $1 at $2"
: $((failures += 1))
fi
}
unset LANG LC_ALL LC_COLLATE LC_CTYPE LC_MONETARY LC_NUMERIC LC_TIME LC_MESSAGES
msgeng="No such file or directory"
msgdut="Bestand of map niet gevonden"
# Verify C locale error message.
case $(command . /var/empty/foo 2>&1) in
*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
# Various locale variables that should not affect the message.
case $(LC_ALL=C command . /var/empty/foo 2>&1) in
*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LC_ALL=C LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LC_ALL=C LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LC_CTYPE=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
# Verify Dutch message.
case $(export LANG=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in
*"$msgdut"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(export LC_MESSAGES=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in
*"$msgdut"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(export LC_ALL=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in
*"$msgdut"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
*"$msgdut"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
*"$msgdut"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LC_ALL=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
*"$msgdut"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
# Verify that command assignments do not set the locale persistently.
case $(command . /var/empty/foo 2>&1) in
*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in
*"$msgdut"*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in
*"$msgdut"*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(LC_ALL=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in
*"$msgdut"*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
# Check special builtin; add colon invocation to avoid depending on certain fix.
case $(LC_ALL=nl_NL.ISO8859-1 . /var/empty/foo 2>&1; :) in
*"$msgdut"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
# Assignments on special builtins are exported to that builtin; the export
# is not persistent.
case $(LC_ALL=nl_NL.ISO8859-1 . /dev/null; . /var/empty/foo 2>&1) in
*"$msgeng"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
case $(export LC_ALL; LC_ALL=nl_NL.ISO8859-1 . /dev/null; . /var/empty/foo 2>&1) in
*"$msgdut"*) ok=1 ;;
*) ok=0 ;;
esac
check '$ok -eq 1' $LINENO
exit $((failures > 0))