| /* braces.c -- code for doing word expansion in curly braces. */ |
| |
| /* 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 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. |
| |
| You should have received a copy of the GNU General Public License |
| along with Bash. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /* Stuff in curly braces gets expanded before all other shell expansions. */ |
| |
| #include "config.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" |
| #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 ".." |
| |
| 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), |
| postamble (stuff after the matching close brace) and amble (stuff after |
| preamble, and before postamble). Expand amble, and then tack on the |
| expansions to preamble. Expand postamble, and tack on the expansions to |
| the result so far. |
| */ |
| |
| /* The character which is used to separate arguments. */ |
| 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((intmax_t, intmax_t, int, int, int)); |
| static char **array_concat __P((char **, char **)); |
| #else |
| static int brace_gobbler (); |
| static char **expand_amble (); |
| static char **expand_seqterm (); |
| static char **mkseq(); |
| 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 *text; |
| { |
| register int start; |
| size_t tlen; |
| char *preamble, *postamble, *amble; |
| size_t alen; |
| char **tack, **result; |
| int i, j, c, c1; |
| |
| DECLARE_MBSTATE; |
| |
| /* Find the text of the preamble. */ |
| tlen = strlen (text); |
| i = 0; |
| #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); |
| preamble[i] = '\0'; |
| |
| 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 != '{') |
| return (result); |
| |
| /* Find the amble. This is the stuff inside this set of braces. */ |
| start = ++i; |
| c = brace_gobbler (text, tlen, &i, '}'); |
| |
| /* What if there isn't a matching close brace? */ |
| if (c == 0) |
| { |
| #if defined (NOTDEF) |
| /* Well, if we found an unquoted BRACE_ARG_SEPARATOR between START |
| and I, then this should be an error. Otherwise, it isn't. */ |
| j = start; |
| while (j < i) |
| { |
| if (text[j] == '\\') |
| { |
| j++; |
| ADVANCE_CHAR (text, tlen, j); |
| continue; |
| } |
| |
| if (text[j] == brace_arg_separator) |
| { /* { */ |
| 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. */ |
| result[0] = savestring (text); |
| 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)); |
| 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. */ |
| j = 0; |
| while (amble[j]) |
| { |
| if (amble[j] == '\\') |
| { |
| j++; |
| ADVANCE_CHAR (amble, alen, j); |
| continue; |
| } |
| |
| if (amble[j] == brace_arg_separator) |
| break; |
| |
| ADVANCE_CHAR (amble, alen, j); |
| } |
| |
| 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); |
| strvec_dispose (tack); |
| |
| postamble = text + i + 1; |
| |
| tack = brace_expand (postamble); |
| result = array_concat (result, tack); |
| strvec_dispose (tack); |
| |
| return (result); |
| } |
| |
| /* Expand the text found inside of braces. We simply try to split the |
| text at BRACE_ARG_SEPARATORs into separate strings. We then brace |
| expand each slot which needs it, until there are no more slots which |
| need it. */ |
| static char ** |
| 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; |
| |
| start = i = 0; |
| c = 1; |
| while (c) |
| { |
| 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); |
| |
| if (!result) |
| result = partial; |
| else |
| { |
| register int lr, lp, j; |
| |
| lr = strvec_len (result); |
| lp = strvec_len (partial); |
| |
| result = strvec_resize (result, lp + lr + 1); |
| |
| for (j = 0; j < lp; j++) |
| result[lr + j] = partial[j]; |
| |
| result[lr + j] = (char *)NULL; |
| 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 |
| #define ST_ZINT 3 |
| |
| static char ** |
| mkseq (start, end, incr, type, width) |
| intmax_t start, end; |
| int incr, type, width; |
| { |
| intmax_t n; |
| int i; |
| char **result, *t; |
| |
| i = abs (end - start) + 1; |
| result = strvec_create (i + 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[0] = n; |
| t[1] = '\0'; |
| result[i++] = t; |
| } |
| n += incr; |
| if ((incr < 0 && n < end) || (incr > 0 && n > end)) |
| break; |
| } |
| 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, incr, lhs_l, rhs_l, width; |
| intmax_t lhs_v, rhs_v; |
| intmax_t tl, tr; |
| char **result, *ep, *oep; |
| |
| t = strstr (text, BRACE_SEQ_SPECIFIER); |
| if (t == 0) |
| return ((char **)NULL); |
| |
| 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) |
| { |
| 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); |
| |
| /* 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) |
| { |
| 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]; |
| 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, incr, lhs_t, width); |
| |
| free (lhs); |
| free (rhs); |
| |
| return (result); |
| } |
| |
| /* Start at INDEX, and skip characters in TEXT. Set INDEX to the |
| 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; |
| size_t tlen; |
| int *indx; |
| int satisfy; |
| { |
| register int i, c, quoted, level, commas, pass_next; |
| #if defined (SHELL) |
| int si; |
| char *t; |
| #endif |
| 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]) |
| { |
| 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; |
| 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 and process substitutions through unchanged. */ |
| if ((c == '$' || c == '<' || c == '>') && text[i+1] == '(') /* ) */ |
| { |
| si = i + 2; |
| t = extract_command_subst (text, &si, 0); |
| i = si; |
| free (t); |
| i++; |
| continue; |
| } |
| #endif |
| |
| 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 |
| by whitespace. */ |
| if (c == '{' && |
| ((!i || brace_whitespace (text[i - 1])) && |
| (brace_whitespace (text[i + 1]) || text[i + 1] == '}'))) |
| { |
| i++; |
| continue; |
| } |
| |
| break; |
| } |
| |
| if (c == '{') |
| 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); |
| } |
| |
| *indx = i; |
| return (c); |
| } |
| |
| /* Return a new array of strings which is the result of appending each |
| string in ARR2 to each string in ARR1. The resultant array is |
| len (arr1) * len (arr2) long. For convenience, ARR1 (and its contents) |
| are free ()'ed. ARR1 can be NULL, in that case, a new version of ARR2 |
| is returned. */ |
| static char ** |
| array_concat (arr1, arr2) |
| char **arr1, **arr2; |
| { |
| register int i, j, len, len1, len2; |
| register char **result; |
| |
| if (arr1 == 0) |
| return (strvec_copy (arr2)); |
| |
| if (arr2 == 0) |
| return (strvec_copy (arr1)); |
| |
| len1 = strvec_len (arr1); |
| len2 = strvec_len (arr2); |
| |
| result = (char **)xmalloc ((1 + (len1 * len2)) * sizeof (char *)); |
| |
| len = 0; |
| for (i = 0; i < len1; i++) |
| { |
| int strlen_1 = strlen (arr1[i]); |
| |
| for (j = 0; j < len2; j++) |
| { |
| result[len] = (char *)xmalloc (1 + strlen_1 + strlen (arr2[j])); |
| strcpy (result[len], arr1[i]); |
| strcpy (result[len] + strlen_1, arr2[j]); |
| len++; |
| } |
| free (arr1[i]); |
| } |
| free (arr1); |
| |
| result[len] = (char *)NULL; |
| return (result); |
| } |
| |
| #if defined (TEST) |
| #include <stdio.h> |
| |
| fatal_error (format, arg1, arg2) |
| char *format, *arg1, *arg2; |
| { |
| report_error (format, arg1, arg2); |
| exit (1); |
| } |
| |
| report_error (format, arg1, arg2) |
| char *format, *arg1, *arg2; |
| { |
| fprintf (stderr, format, arg1, arg2); |
| fprintf (stderr, "\n"); |
| } |
| |
| main () |
| { |
| char example[256]; |
| |
| for (;;) |
| { |
| char **result; |
| int i; |
| |
| fprintf (stderr, "brace_expand> "); |
| |
| if ((!fgets (example, 256, stdin)) || |
| (strncmp (example, "quit", 4) == 0)) |
| break; |
| |
| if (strlen (example)) |
| example[strlen (example) - 1] = '\0'; |
| |
| result = brace_expand (example); |
| |
| for (i = 0; result[i]; i++) |
| printf ("%s\n", result[i]); |
| |
| free_array (result); |
| } |
| } |
| |
| /* |
| * Local variables: |
| * compile-command: "gcc -g -Bstatic -DTEST -o brace_expand braces.c general.o" |
| * end: |
| */ |
| |
| #endif /* TEST */ |
| #endif /* BRACE_EXPANSION */ |