/* braces.c -- code for doing word expansion in curly braces. */
-/* Copyright (C) 1987-2003 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2009 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
- Bash is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- Bash is distributed in the hope that it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
- License for more details.
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with Bash; see the file COPYING. If not, write to the Free
- Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
/* Stuff in curly braces gets expanded before all other shell expansions. */
#define BRACE_SEQ_SPECIFIER ".."
+extern int asprintf __P((char **, const char *, ...)) __attribute__((__format__ (printf, 2, 3)));
+
/* Basic idea:
Segregate the text into 3 sections: preamble (stuff before an open brace),
*/
/* The character which is used to separate arguments. */
-int brace_arg_separator = ',';
+static const int brace_arg_separator = ',';
#if defined (__P)
static int brace_gobbler __P((char *, size_t, int *, int));
static char **expand_amble __P((char *, size_t, int));
static char **expand_seqterm __P((char *, size_t));
-static char **mkseq __P((int, int, int));
+static char **mkseq __P((intmax_t, intmax_t, int, int, int));
static char **array_concat __P((char **, char **));
#else
static int brace_gobbler ();
static char **array_concat ();
#endif
+#if 0
+static void
+dump_result (a)
+ char **a;
+{
+ int i;
+
+ for (i = 0; a[i]; i++)
+ printf ("dump_result: a[%d] = -%s-\n", i, a[i]);
+}
+#endif
+
/* Return an array of strings; the brace expansion of TEXT. */
char **
brace_expand (text)
char *preamble, *postamble, *amble;
size_t alen;
char **tack, **result;
- int i, j, c;
+ int i, j, c, c1;
DECLARE_MBSTATE;
/* Find the text of the preamble. */
tlen = strlen (text);
i = 0;
- c = brace_gobbler (text, tlen, &i, '{');
+#if defined (CSH_BRACE_COMPAT)
+ c = brace_gobbler (text, tlen, &i, '{'); /* } */
+#else
+ /* Make sure that when we exit this loop, c == 0 or text[i] begins a
+ valid brace expansion sequence. */
+ do
+ {
+ c = brace_gobbler (text, tlen, &i, '{'); /* } */
+ c1 = c;
+ /* Verify that c begins a valid brace expansion word. If it doesn't, we
+ go on. Loop stops when there are no more open braces in the word. */
+ if (c)
+ {
+ start = j = i + 1; /* { */
+ c = brace_gobbler (text, tlen, &j, '}');
+ if (c == 0) /* it's not */
+ {
+ i++;
+ c = c1;
+ continue;
+ }
+ else /* it is */
+ {
+ c = c1;
+ break;
+ }
+ }
+ else
+ break;
+ }
+ while (c);
+#endif /* !CSH_BRACE_COMPAT */
preamble = (char *)xmalloc (i + 1);
strncpy (preamble, text, i);
#define ST_BAD 0
#define ST_INT 1
#define ST_CHAR 2
+#define ST_ZINT 3
static char **
-mkseq (start, end, type)
- int start, end, type;
+mkseq (start, end, incr, type, width)
+ intmax_t start, end;
+ int incr, type, width;
{
- int n, incr, i;
+ intmax_t n;
+ int i;
char **result, *t;
- n = abs (end - start) + 1;
- result = strvec_create (n + 1);
+ i = abs (end - start) + 1;
+ result = strvec_create (i + 1);
- incr = (start < end) ? 1 : -1;
+ if (incr == 0)
+ incr = 1;
+
+ if (start > end && incr > 0)
+ incr = -incr;
+ else if (start < end && incr < 0)
+ incr = -incr;
/* Make sure we go through the loop at least once, so {3..3} prints `3' */
i = 0;
n = start;
do
{
+#if defined (SHELL)
+ QUIT; /* XXX - memory leak here */
+#endif
if (type == ST_INT)
result[i++] = itos (n);
+ else if (type == ST_ZINT)
+ {
+ int len, arg;
+ arg = n;
+ len = asprintf (&t, "%0*d", width, arg);
+ result[i++] = t;
+ }
else
{
t = (char *)xmalloc (2);
t[1] = '\0';
result[i++] = t;
}
- if (n == end)
- break;
n += incr;
+ if ((incr < 0 && n < end) || (incr > 0 && n > end))
+ break;
}
while (1);
size_t tlen;
{
char *t, *lhs, *rhs;
- int i, lhs_t, rhs_t, lhs_v, rhs_v;
+ int i, lhs_t, rhs_t, incr, lhs_l, rhs_l, width;
+ intmax_t lhs_v, rhs_v;
intmax_t tl, tr;
- char **result;
+ char **result, *ep, *oep;
t = strstr (text, BRACE_SEQ_SPECIFIER);
if (t == 0)
return ((char **)NULL);
- i = t - text; /* index of start of BRACE_SEQ_SPECIFIER */
- lhs = substring (text, 0, i);
- rhs = substring (text, i + sizeof(BRACE_SEQ_SPECIFIER) - 1, tlen);
+ lhs_l = t - text; /* index of start of BRACE_SEQ_SPECIFIER */
+ lhs = substring (text, 0, lhs_l);
+ rhs = substring (text, lhs_l + sizeof(BRACE_SEQ_SPECIFIER) - 1, tlen);
if (lhs[0] == 0 || rhs[0] == 0)
{
sides have to match. */
lhs_t = (legal_number (lhs, &tl)) ? ST_INT :
((ISALPHA (lhs[0]) && lhs[1] == 0) ? ST_CHAR : ST_BAD);
- rhs_t = (legal_number (rhs, &tr)) ? ST_INT :
- ((ISALPHA (rhs[0]) && rhs[1] == 0) ? ST_CHAR : ST_BAD);
+
+ /* Decide on rhs and whether or not it looks like the user specified
+ an increment */
+ ep = 0;
+ if (ISDIGIT (rhs[0]) || ((rhs[0] == '+' || rhs[0] == '-') && ISDIGIT (rhs[1])))
+ {
+ rhs_t = ST_INT;
+ tr = strtoimax (rhs, &ep, 10);
+ if (ep && *ep != 0 && *ep != '.')
+ rhs_t = ST_BAD; /* invalid */
+ }
+ else if (ISALPHA (rhs[0]) && (rhs[1] == 0 || rhs[1] == '.'))
+ {
+ rhs_t = ST_CHAR;
+ ep = rhs + 1;
+ }
+ else
+ {
+ rhs_t = ST_BAD;
+ ep = 0;
+ }
+
+ incr = 1;
+ if (rhs_t != ST_BAD)
+ {
+ oep = ep;
+ if (ep && *ep == '.' && ep[1] == '.' && ep[2])
+ incr = strtoimax (ep + 2, &ep, 10);
+ if (*ep != 0)
+ rhs_t = ST_BAD; /* invalid incr */
+ tlen -= ep - oep;
+ }
if (lhs_t != rhs_t || lhs_t == ST_BAD || rhs_t == ST_BAD)
{
if (lhs_t == ST_CHAR)
{
- lhs_v = lhs[0];
- rhs_v = rhs[0];
+ lhs_v = (unsigned char)lhs[0];
+ rhs_v = (unsigned char)rhs[0];
+ width = 1;
}
else
{
lhs_v = tl; /* integer truncation */
rhs_v = tr;
+
+ /* Decide whether or not the terms need zero-padding */
+ rhs_l = tlen - lhs_l - sizeof (BRACE_SEQ_SPECIFIER) + 1;
+ width = 0;
+ if (lhs_l > 1 && lhs[0] == '0')
+ width = lhs_l, lhs_t = ST_ZINT;
+ if (lhs_l > 2 && lhs[0] == '-' && lhs[1] == '0')
+ width = lhs_l, lhs_t = ST_ZINT;
+ if (rhs_l > 1 && rhs[0] == '0' && width < rhs_l)
+ width = rhs_l, lhs_t = ST_ZINT;
+ if (rhs_l > 2 && rhs[0] == '-' && rhs[1] == '0' && width < rhs_l)
+ width = rhs_l, lhs_t = ST_ZINT;
+
+ if (width < lhs_l && lhs_t == ST_ZINT)
+ width = lhs_l;
+ if (width < rhs_l && lhs_t == ST_ZINT)
+ width = rhs_l;
}
- result = mkseq (lhs_v, rhs_v, lhs_t);
+ result = mkseq (lhs_v, rhs_v, incr, lhs_t, width);
free (lhs);
free (rhs);
index of the character matching SATISFY. This understands about
quoting. Return the character that caused us to stop searching;
this is either the same as SATISFY, or 0. */
+/* If SATISFY is `}', we are looking for a brace expression, so we
+ should enforce the rules that govern valid brace expansions:
+ 1) to count as an arg separator, a comma or `..' has to be outside
+ an inner set of braces.
+*/
static int
brace_gobbler (text, tlen, indx, satisfy)
char *text;
int *indx;
int satisfy;
{
- register int i, c, quoted, level, pass_next;
+ register int i, c, quoted, level, commas, pass_next;
#if defined (SHELL)
int si;
char *t;
DECLARE_MBSTATE;
level = quoted = pass_next = 0;
+#if defined (CSH_BRACE_COMPAT)
+ commas = 1;
+#else
+ commas = (satisfy == '}') ? 0 : 1;
+#endif
i = *indx;
while (c = text[i])
{
pass_next = 1;
i++;
+ if (quoted == 0)
+ level++;
continue;
}
#endif
}
#if defined (SHELL)
- /* Pass new-style command substitutions through unchanged. */
- if (c == '$' && text[i+1] == '(') /* ) */
+ /* Pass new-style command and process substitutions through unchanged. */
+ if ((c == '$' || c == '<' || c == '>') && text[i+1] == '(') /* ) */
{
si = i + 2;
- t = extract_command_subst (text, &si);
+ t = extract_command_subst (text, &si, 0);
i = si;
free (t);
i++;
}
#endif
- if (c == satisfy && level == 0 && quoted == 0)
+ if (c == satisfy && level == 0 && quoted == 0 && commas > 0)
{
/* We ignore an open brace surrounded by whitespace, and also
an open brace followed immediately by a close brace preceded
level++;
else if (c == '}' && level)
level--;
+#if !defined (CSH_BRACE_COMPAT)
+ else if (satisfy == '}' && c == brace_arg_separator && level == 0)
+ commas++;
+ else if (satisfy == '}' && STREQN (text+i, BRACE_SEQ_SPECIFIER, 2) &&
+ text[i+2] != satisfy && level == 0)
+ commas++;
+#endif
ADVANCE_CHAR (text, tlen, i);
}