/* braces.c -- code for doing word expansion in curly braces. */
-/* Copyright (C) 1987,1991 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2003 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 1, or (at your option)
+ the Free Software Foundation; either version 2, or (at your option)
any later version.
Bash is distributed in the hope that it will be useful, but WITHOUT
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, 675 Mass Ave, Cambridge, MA 02139, USA. */
+ Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
-/* Stuff in curly braces gets expanded after variable and command
- substitution, but before filename globbing.
+/* Stuff in curly braces gets expanded before all other shell expansions. */
- (Actually, this should be true for the sake of efficiency, but it
- isn't because of quoting hacks. Once I rebuild quoting it will be
- true. */
+#include "config.h"
-#if defined (HAVE_STRING_H)
-# include <string.h>
-#else /* !HAVE_STRING_H */
-# include <strings.h>
-#endif /* !HAVE_STRING_H */
+#if defined (BRACE_EXPANSION)
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include "bashansi.h"
#if defined (SHELL)
-#include "shell.h"
+# include "shell.h"
#endif /* SHELL */
#include "general.h"
+#include "shmbutil.h"
+#include "chartypes.h"
+
#define brace_whitespace(c) (!(c) || (c) == ' ' || (c) == '\t' || (c) == '\n')
+#define BRACE_SEQ_SPECIFIER ".."
+
/* 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 = ',';
+#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 **array_concat __P((char **, char **));
+#else
static int brace_gobbler ();
-static char **expand_amble (), **array_concat ();
+static char **expand_amble ();
+static char **expand_seqterm ();
+static char **mkseq();
+static char **array_concat ();
+#endif
/* Return an array of strings; the brace expansion of TEXT. */
char **
char *text;
{
register int start;
+ size_t tlen;
char *preamble, *postamble, *amble;
+ size_t alen;
char **tack, **result;
- int i, c;
+ int i, j, c;
+
+ DECLARE_MBSTATE;
/* Find the text of the preamble. */
+ tlen = strlen (text);
i = 0;
- c = brace_gobbler (text, &i, '{');
+ c = brace_gobbler (text, tlen, &i, '{');
preamble = (char *)xmalloc (i + 1);
strncpy (preamble, text, i);
result = (char **)xmalloc (2 * sizeof (char *));
result[0] = preamble;
result[1] = (char *)NULL;
-
+
/* Special case. If we never found an exciting character, then
the preamble is all of the text, so just return that. */
if (c != '{')
/* Find the amble. This is the stuff inside this set of braces. */
start = ++i;
- c = brace_gobbler (text, &i, '}');
+ c = brace_gobbler (text, tlen, &i, '}');
/* What if there isn't a matching close brace? */
- if (!c)
+ if (c == 0)
{
#if defined (NOTDEF)
- register int j;
-
/* Well, if we found an unquoted BRACE_ARG_SEPARATOR between START
and I, then this should be an error. Otherwise, it isn't. */
- for (j = start; j < i; j++)
+ j = start;
+ while (j < i)
{
if (text[j] == '\\')
{
j++;
+ ADVANCE_CHAR (text, tlen, j);
continue;
}
if (text[j] == brace_arg_separator)
- {
- free_array (result);
- report_error ("Missing `}'");
+ { /* { */
+ strvec_dispose (result);
+ report_error ("no closing `%c' in %s", '}', text);
throw_to_top_level ();
}
+ ADVANCE_CHAR (text, tlen, j);
}
#endif
free (preamble); /* Same as result[0]; see initialization. */
return (result);
}
+#if defined (SHELL)
+ amble = substring (text, start, i);
+ alen = i - start;
+#else
amble = (char *)xmalloc (1 + (i - start));
strncpy (amble, &text[start], (i - start));
- amble[i - start] = '\0';
+ alen = i - start;
+ amble[alen] = '\0';
+#endif
#if defined (SHELL)
+ INITIALIZE_MBSTATE;
+
/* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, then
just return without doing any expansion. */
- {
- register int j;
-
- for (j = 0; amble[j]; j++)
- {
- if (amble[j] == '\\')
- {
- j++;
- continue;
- }
- if (amble[j] == brace_arg_separator)
- break;
- }
-
- if (!amble[j])
- {
- free (amble);
- free (preamble);
- result[0] = savestring (text);
- return (result);
- }
- }
-#endif /* SHELL */
+ j = 0;
+ while (amble[j])
+ {
+ if (amble[j] == '\\')
+ {
+ j++;
+ ADVANCE_CHAR (amble, alen, j);
+ continue;
+ }
- postamble = &text[i + 1];
+ if (amble[j] == brace_arg_separator)
+ break;
+
+ ADVANCE_CHAR (amble, alen, j);
+ }
- tack = expand_amble (amble);
+ if (amble[j] == 0)
+ {
+ tack = expand_seqterm (amble, alen);
+ if (tack)
+ goto add_tack;
+ else
+ {
+ free (amble);
+ free (preamble);
+ result[0] = savestring (text);
+ return (result);
+ }
+ }
+#endif /* SHELL */
+
+ tack = expand_amble (amble, alen, 0);
+add_tack:
result = array_concat (result, tack);
free (amble);
- free_array (tack);
+ strvec_dispose (tack);
+
+ postamble = text + i + 1;
tack = brace_expand (postamble);
result = array_concat (result, tack);
- free_array (tack);
+ strvec_dispose (tack);
return (result);
}
expand each slot which needs it, until there are no more slots which
need it. */
static char **
-expand_amble (text)
+expand_amble (text, tlen, flags)
char *text;
+ size_t tlen;
+ int flags;
{
char **result, **partial;
char *tem;
int start, i, c;
+ DECLARE_MBSTATE;
+
result = (char **)NULL;
- for (start = 0, i = 0, c = 1; c; start = ++i)
+ start = i = 0;
+ c = 1;
+ while (c)
{
- c = brace_gobbler (text, &i, brace_arg_separator);
+ c = brace_gobbler (text, tlen, &i, brace_arg_separator);
+#if defined (SHELL)
+ tem = substring (text, start, i);
+#else
tem = (char *)xmalloc (1 + (i - start));
strncpy (tem, &text[start], (i - start));
tem[i- start] = '\0';
+#endif
partial = brace_expand (tem);
result = partial;
else
{
- register int lr = array_len (result);
- register int lp = array_len (partial);
- register int j;
+ register int lr, lp, j;
+
+ lr = strvec_len (result);
+ lp = strvec_len (partial);
- result = (char **)xrealloc (result, (1 + lp + lr) * sizeof (char *));
+ result = strvec_resize (result, lp + lr + 1);
for (j = 0; j < lp; j++)
result[lr + j] = partial[j];
free (partial);
}
free (tem);
+ ADVANCE_CHAR (text, tlen, i);
+ start = i;
+ }
+ return (result);
+}
+
+#define ST_BAD 0
+#define ST_INT 1
+#define ST_CHAR 2
+
+static char **
+mkseq (start, end, type)
+ int start, end, type;
+{
+ int n, incr, i;
+ char **result, *t;
+
+ n = abs (end - start) + 1;
+ result = strvec_create (n + 1);
+
+ incr = (start < end) ? 1 : -1;
+
+ /* Make sure we go through the loop at least once, so {3..3} prints `3' */
+ i = 0;
+ n = start;
+ do
+ {
+ if (type == ST_INT)
+ result[i++] = itos (n);
+ else
+ {
+ t = (char *)xmalloc (2);
+ t[0] = n;
+ t[1] = '\0';
+ result[i++] = t;
+ }
+ if (n == end)
+ break;
+ n += incr;
}
+ while (1);
+
+ result[i] = (char *)0;
+ return (result);
+}
+
+static char **
+expand_seqterm (text, tlen)
+ char *text;
+ size_t tlen;
+{
+ char *t, *lhs, *rhs;
+ int i, lhs_t, rhs_t, lhs_v, rhs_v;
+ intmax_t tl, tr;
+ char **result;
+
+ 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);
+
+ if (lhs[0] == 0 || rhs[0] == 0)
+ {
+ free (lhs);
+ free (rhs);
+ return ((char **)NULL);
+ }
+
+ /* Now figure out whether LHS and RHS are integers or letters. Both
+ 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);
+
+ if (lhs_t != rhs_t || lhs_t == ST_BAD || rhs_t == ST_BAD)
+ {
+ free (lhs);
+ free (rhs);
+ return ((char **)NULL);
+ }
+
+ /* OK, we have something. It's either a sequence of integers, ascending
+ or descending, or a sequence or letters, ditto. Generate the sequence,
+ put it into a string vector, and return it. */
+
+ if (lhs_t == ST_CHAR)
+ {
+ lhs_v = (unsigned char)lhs[0];
+ rhs_v = (unsigned char)rhs[0];
+ }
+ else
+ {
+ lhs_v = tl; /* integer truncation */
+ rhs_v = tr;
+ }
+
+ result = mkseq (lhs_v, rhs_v, lhs_t);
+
+ free (lhs);
+ free (rhs);
+
return (result);
}
quoting. Return the character that caused us to stop searching;
this is either the same as SATISFY, or 0. */
static int
-brace_gobbler (text, indx, satisfy)
+brace_gobbler (text, tlen, indx, satisfy)
char *text;
+ size_t tlen;
int *indx;
int satisfy;
{
register int i, c, quoted, level, pass_next;
+#if defined (SHELL)
+ int si;
+ char *t;
+#endif
+ DECLARE_MBSTATE;
level = quoted = pass_next = 0;
- for (i = *indx; c = text[i]; i++)
+ i = *indx;
+ while (c = text[i])
{
if (pass_next)
{
pass_next = 0;
+ ADVANCE_CHAR (text, tlen, i);
continue;
}
/* A backslash escapes the next character. This allows backslash to
escape the quote character in a double-quoted string. */
if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`'))
- {
- pass_next = 1;
- continue;
- }
+ {
+ pass_next = 1;
+ i++;
+ continue;
+ }
+
+#if defined (SHELL)
+ /* If compiling for the shell, treat ${...} like \{...} */
+ if (c == '$' && text[i+1] == '{' && quoted != '\'') /* } */
+ {
+ pass_next = 1;
+ i++;
+ if (quoted == 0)
+ level++;
+ continue;
+ }
+#endif
if (quoted)
{
if (c == quoted)
quoted = 0;
+ ADVANCE_CHAR (text, tlen, i);
continue;
}
if (c == '"' || c == '\'' || c == '`')
{
quoted = c;
+ i++;
+ continue;
+ }
+
+#if defined (SHELL)
+ /* Pass new-style command substitutions through unchanged. */
+ if (c == '$' && text[i+1] == '(') /* ) */
+ {
+ si = i + 2;
+ t = extract_command_subst (text, &si);
+ i = si;
+ free (t);
+ i++;
continue;
}
-
- if (c == satisfy && !level && !quoted)
+#endif
+
+ if (c == satisfy && level == 0 && quoted == 0)
{
/* We ignore an open brace surrounded by whitespace, and also
- an open brace followed immediately by a close brace, that
- was preceded with whitespace. */
+ an open brace followed immediately by a close brace preceded
+ by whitespace. */
if (c == '{' &&
((!i || brace_whitespace (text[i - 1])) &&
(brace_whitespace (text[i + 1]) || text[i + 1] == '}')))
- continue;
-#if defined (SHELL)
- /* If this is being compiled as part of bash, ignore the `{'
- in a `${}' construct */
- if ((c != '{') || !i || (text[i - 1] != '$'))
-#else /* !SHELL */
- if ((c != '{') || !i)
-#endif /* !SHELL */
+ {
+ i++;
+ continue;
+ }
+
break;
}
level++;
else if (c == '}' && level)
level--;
+
+ ADVANCE_CHAR (text, tlen, i);
}
*indx = i;
register int i, j, len, len1, len2;
register char **result;
- if (!arr1)
- return (copy_array (arr2));
+ if (arr1 == 0)
+ return (strvec_copy (arr2));
- if (!arr2)
- return (copy_array (arr1));
+ if (arr2 == 0)
+ return (strvec_copy (arr1));
- len1 = array_len (arr1);
- len2 = array_len (arr2);
+ len1 = strvec_len (arr1);
+ len2 = strvec_len (arr2);
result = (char **)xmalloc ((1 + (len1 * len2)) * sizeof (char *));
for (j = 0; j < len2; j++)
{
- result[len] =
- (char *)xmalloc (1 + strlen_1 + strlen (arr2[j]));
+ result[len] = (char *)xmalloc (1 + strlen_1 + strlen (arr2[j]));
strcpy (result[len], arr1[i]);
strcpy (result[len] + strlen_1, arr2[j]);
len++;
*/
#endif /* TEST */
+#endif /* BRACE_EXPANSION */