(patch for Bash) adding Python features to Bash

William Park opengeometry at yahoo.ca
Thu Feb 13 00:47:33 EST 2003


To Python and Bash users:

I got tired of going back and forth between Python and Bash.  So, I
added few Python features to the shell.  Patch for Bash-2.05b is
included at the end.  Use at your risk, and enjoy. :-)

I hope people won't get mad at me for posting to <comp.lang.python>.
But, this may be of interest to other Python/Bash programmers, like
myself.


1.  Integer sequence generator:

	{5..10}	    -->  5 6 7 8 9 10
	{05..10}    -->  05 06 07 08 09 10
	{1..n}	    -->  1 2 3 4	(if n=4)
	{1..!m}	    -->  1 2 3		(if m='i' and i='3')

    Generates integer sequence (ascending or descending, depending on
    a<b or a>b).  This is an extension of the normal brace expansion
    that is already present, ie. 'a{1,2,3}b' for 'a1b a2b a3b'.  If 'a'
    or 'b' is normal shell variable, then use its value.  If prefix '!'
    is present, then indirect substitution will be attempted.  In all
    cases, if pure integer expression '{a..b}' cannot be found, then
    return the original string untouched.


2.  Multiple variables in for-loop:
	
	for  a,b,c  in  ... ; do
	    echo $a $b $c
	done

    Normally, only one variable can be used.  But, you can now follow
    Python's for-loop syntax,
	for (a,b,c) in [ (...), (...), ... ]:
	    print a, b, c
    as long as the variables are separated by ',' (comma) without any
    spaces.  Their functions are identical, if you serialize the list
    elements.
    

3.  Python's map() function:

	arraymap command a b c ...
    
    Mimicking Python's map() function, it runs 'command' for each
    element of arrays 'a', 'b', 'c', ... in parallel.  'command' should
    take as many positional parameters as there are arrays, but it can
    do whatever it likes.  This is modified version of 'eval' builtins,
    and is equivalent to
	command "${a[0]}" "${b[0]}" ...
	command "${a[1]}" "${b[1]}" ...
	...
	command "${a[N]}" "${b[N]}" ...

    Like map() function, array elements are referenced by index key,
    starting from 0 to N, where N is the maximum index of all the
    arrays.  If there are missing elements, then '' (null) will be given
    to 'command'.


4.  Python's filter() function:
    
	arrayfilter filter name

    Mimicking Python's filter() function, it runs 'filter' for each
    element of array 'name'.  It returns the array elements, for which
    'filter' returns success (in shell, 0 is true).


5.  Various list/dict operation in Python:

	array [-kvlrs] [-i value] [-j sep] name [arg...]

    By default, print array indexes and values, separated by '\t',
    mimicking dict.items() in Python.  Only one option is allowed, so
    the last one takes effect.
	-k          Print only indexes.  --> dict.keys()
	-v          Print only values.  --> dict.values()
	-l          Print size of each array element.  -->  [ len(v) for v in list ]
	-i value    Print all indexes that have 'value'.  --> list.index(value), repeat...
	-j sep      Print all values with 'sep' separator.  --> sep.join()
    The following operation changes the array in-place.
	-r          Reverse the array, keeping the same indexes.  --> list.reverse()
	-s          Sort the array, keeping the same indexes.  --> list.sort()
	-c          Collapse the array, so that there is no missing index.

    If one or more arguments are present, then append them sequentially to the
    end of array, mimicking list.append(arg) or list.extend([arg,...]) in
    Python.  It doesn't create new array, so create it manually.
	-j sep      Split each 'arg' string by 'sep' separator, and append each
		    segment to the end of array.  If 'sep' is null, then no
		    splitting is done.  --> list.extend(arg.split(sep)), repeat...

-- 
William Park, Open Geometry Consulting, <opengeometry at yahoo.ca>
Linux solution for data management and processing. 



diff -ru bash-2.05b/braces.c bash/braces.c
--- bash-2.05b/braces.c	Mon May  6 13:50:40 2002
+++ bash/braces.c	Wed Feb 12 22:27:05 2003
@@ -163,10 +163,100 @@
 
   if (!amble[j])
     {
+      /* Okey, found a standalone brace expression without ','.  If it contains
+       * 'a..b' expression, where 'a' and 'b' are positive integers, then
+       * replace it with 'a,a+1,...,b' (if a < b) or 'a,a-1,...,b' (if a > b),
+       * and give it back to shell for a normal expansion.  If 'a' or 'b' has
+       * leading '0', then zero pad the numbers.  The format size is the maximum
+       * size of 'a' or 'b'.
+       *
+       * If 'a' or 'b' is a regular shell variable (not positional parameter or
+       * array element), then it will be replaced by its value '$a' or '$b'.  If
+       * 'a' or 'b' starts with '!', then indirect substitution will be tried,
+       * similiar to '${!a}' or '${!b}'.  In any case, if the final '{a..b}' is
+       * pure integer expression, then integer sequence will be generated as
+       * normal.
+       *
+       * Otherwise, return the original string back to shell as is, like before.
+       *
+       * --William Park <opengeometry at yahoo.ca>
+       */
+      char *newamble, *a, *b, *t;
+      size_t i, end, n, size;
+
+      if (strstr (amble, "..") == NULL) {
+	  free (amble);		/* original code */
+	  free (preamble);
+	  result[0] = savestring (text);
+	  return (result);
+      }
+
+      i = strstr (amble, "..") - amble;		/* index of '..' */
+
+      a = substring (amble, 0, i);
+      if (legal_identifier (a) && (t = get_string_value (a))) {
+	  free (a);
+	  a = savestring (t);
+      } else if (a[0] == '!') {
+	  if (legal_identifier (a + 1) && (t = get_string_value (a + 1))) 
+	      if (legal_identifier (t) && (t = get_string_value (t))) {
+		  free (a);
+		  a = savestring (t);
+	      }
+      }
+      b = substring (amble, i + 2, alen);
+      if (legal_identifier (b) && (t = get_string_value (b))) {
+	  free (b);
+	  b = savestring (t);
+      } else if (b[0] == '!') {
+	  if (legal_identifier (b + 1) && (t = get_string_value (b + 1))) 
+	      if (legal_identifier (t) && (t = get_string_value (t))) {
+		  free (b);
+		  b = savestring (t);
+	      }
+      }
+
+      if (strlen (a) == 0 || !all_digits (a) ||
+	  strlen (b) == 0 || !all_digits (b))		/* not pure integers */
+      {
+	  free (b);
+	  free (a);
+	  free (amble);		/* original code */
+	  free (preamble);
+	  result[0] = savestring (text);
+	  return (result);
+      }
+
+      i = atoi (a);
+      end = atoi (b);
+      if (i < end) 
+	  n = end - i + 1;
+      else
+	  n = i - end + 1;
+      size = strlen (a) > strlen (b) ? strlen (a) : strlen (b);
+      newamble = (char *)xmalloc (n * (size+1) + 1);	/* ',' for each integer */
+
+      if ((strlen (a) > 1 && a[0] == '0') || (strlen (b) > 1 && b[0] == '0'))
+	  size = size;	/* "%0*d," */
+      else
+	  size = 1;	/* "%01d," */
+
+      n = 0;
+      do {		/* print at least one: ie. {3..3} */
+	  sprintf (newamble + n, "%0*d,", size, i);
+	  n = strlen (newamble);
+	  if (i == end) {
+	      newamble[--n] = '\0';	/* remove the last ',' */
+	      break;
+	  }
+	  (i < end) ? ++i : --i;
+      } while (1);
+
+      free (b);
+      free (a);
       free (amble);
-      free (preamble);
-      result[0] = savestring (text);
-      return (result);
+      amble = newamble;		/* give the new 'amble' back to shell */
+      alen = n;
     }
 #endif /* SHELL */
 
diff -ru bash-2.05b/builtins/declare.def bash/builtins/declare.def
--- bash-2.05b/builtins/declare.def	Mon Apr  8 13:20:55 2002
+++ bash/builtins/declare.def	Wed Feb 12 22:59:05 2003
@@ -440,3 +440,343 @@
 		       : ((any_failed == 0) ? EXECUTION_SUCCESS
   					    : EXECUTION_FAILURE));
 }
+
+
+
+/* Add Python functionalities to Bash.
+ *
+ * --William Park <opengeometry at yahoo.ca>
+ */
+
+#if defined (ARRAY_VARS)
+
+$BUILTIN array
+$FUNCTION array_builtin
+$SHORT_DOC array [-kvlrs] [-i value] [-j sep] name [arg...]
+By default, print array indexes and values, separated by '\t', mimicking
+dict.items() in Python.  Only one option is allowed, so the last one takes
+effect.
+    -k          Print only indexes.  --> dict.keys()
+    -v          Print only values.  --> dict.values()
+    -l          Print size of each array element.  -->  [ len(v) for v in list ]
+    -i value    Print all indexes that have 'value'.  --> list.index(value), repeat...
+    -j sep      Print all values with 'sep' separator.  --> sep.join()
+The following operation changes the array in-place.
+    -r          Reverse the array, keeping the same indexes.  --> list.reverse()
+    -s          Sort the array, keeping the same indexes.  --> list.sort()
+    -c          Collapse the array, so that there is no missing index.
+
+If one or more arguments are present, then append them sequentially to the
+end of array, mimicking list.append(arg) or list.extend([arg,...]) in
+Python.  It doesn't create a new array, so create it manually.
+    -j sep      Split each 'arg' string by 'sep' separator, and append each
+                segment to the end of array.  If 'sep' is null, then no
+                splitting is done.  --> list.extend(arg.split(sep)), repeat...
+$END
+
+
+/* Wrapper around inttostr() in ../lib/sh/itos.c, to convert array index
+ * integers (arrayind_t == LONG) to string.  There is itos(); but, it creates
+ * string which requires an extra step of freeing it.  Copied from
+ * array_to_assign() in ../array.c.  
+ */
+char *
+element_index_to_string (ae)
+    ARRAY_ELEMENT *ae;
+{
+    /* 'static' to survive outside the function, but is not intended for
+     * long term storage. */
+    static char indstr[INT_STRLEN_BOUND(intmax_t) + 1];
+    
+    return inttostr (element_index (ae), indstr, sizeof (indstr));
+}
+
+
+/* Copied from array_walk() in ../array.c.  For each array element, print its
+ * index key, value, or both index and value, separated by '\t'.  Similiar to
+ * dict.keys(), dict.values(), and dict.items() in Python.
+ */
+static void
+print_elements (var, flag)
+    SHELL_VAR *var;
+    int flag;	/* 'k', 'v', 'l', or anything else (default) */
+{
+    ARRAY *a;
+    ARRAY_ELEMENT *ae;
+
+    a = array_cell (var);
+    if (a == 0 || array_empty (a)) return;	/* do nothing */
+
+    for (ae = element_forw (a->head); ae != a->head; ae = element_forw (ae))
+	switch (flag) {
+	case 'k':
+	    puts (element_index_to_string (ae));
+	    break;
+	case 'v':
+	    puts (element_value (ae));
+	    break;
+	case 'l':
+	    printf ("%d\n", strlen (element_value (ae)));
+	    break;
+	default:
+	    printf ("%s\t%s\n", element_index_to_string (ae), element_value (ae));
+	    break;
+	}
+}
+
+
+/* Copied from array_walk() in ../array.c.  Find all array elements with
+ * 'value', and print their index keys.  Similiar to list.index(value) in
+ * Python.
+ */
+static void
+print_all_indexes_with_value (var, value)
+    SHELL_VAR *var;
+    char *value;
+{
+    ARRAY *a;
+    ARRAY_ELEMENT *ae;
+
+    a = array_cell (var);
+    if (a == 0 || array_empty (a)) return;	/* do nothing */
+
+    for (ae = element_forw (a->head); ae != a->head; ae = element_forw (ae))
+	if (strcmp (element_value (ae), value) == 0) 
+	    puts (element_index_to_string (ae));
+}
+
+
+/* Set array index so that they are from 0 to n-1, where n is the number of
+ * elements that the array has.
+ */
+static void
+array_collapse (var)
+    SHELL_VAR *var;
+{
+    ARRAY *a;
+    ARRAY_ELEMENT *ae;
+    arrayind_t i, n;
+
+    a = array_cell (var);
+    if (a == 0 || array_empty (a)) return;	/* do nothing */
+    
+    n = array_num_elements (a);
+    ae = a->head;
+    for (i = 0; i < n; i++) {
+	ae = element_forw (ae);
+	element_index (ae) = i;
+    }
+}
+
+
+/* Reverse the array order, by swapping the element values.  The index keys are
+ * unchanged.  Similiar to list.reverse() in Python.
+ */
+static void
+array_reverse (var)
+    SHELL_VAR *var;
+{
+    ARRAY *a;
+    ARRAY_ELEMENT *ae, *be;
+    char *t;
+
+    a = array_cell (var);
+    if (a == 0 || array_empty (a)) return;	/* do nothing */
+
+    /* 'ae' goes forward, and 'be' goes backward */
+    for (ae = element_forw (a->head), be = element_back (a->head); 
+	ae != a->head && be != a->head && element_index (ae) < element_index (be);
+	ae = element_forw (ae), be = element_back(be))
+    {
+	t = element_value (ae);		/* swap the values */
+	element_value (ae) = element_value (be);
+	element_value (be) = t;
+    }
+}
+
+
+/* Sort the array.  Similiar to list.sort() in Python.
+ */
+static void
+array_sort (var)
+    SHELL_VAR *var;
+{
+    ARRAY *a;
+    ARRAY_ELEMENT *ae;
+    char **base;	/* array holding pointers to element values */
+    int n, i;
+
+    int my_strcmp (x, y) char **x, **y;
+    {
+	strcmp (*x, *y);
+    }
+
+    int my_intcmp (x, y) char **x, **y;
+    {
+	int i = atoi (*x);
+	int j = atoi (*y);
+	
+	if (i < j) return -1;
+	if (i > j) return 1;
+	return 0;
+    }
+
+    a = array_cell (var);
+    if (a == 0 || array_empty (a)) return;	/* do nothing */
+
+    n = array_num_elements (a);
+    base = (char **) xmalloc (n * sizeof (char *));
+    ae = a->head;
+    for (i = 0; i < n; i++) {
+	ae = element_forw (ae);
+	base[i] = element_value (ae);
+    }
+
+    if (integer_p (var)) 
+	qsort (base, n, sizeof (char *), my_intcmp);
+    else
+	qsort (base, n, sizeof (char *), my_strcmp);
+
+    ae = a->head;
+    for (i = 0; i < n; i++) {
+	ae = element_forw (ae);
+	element_value (ae) = base[i];
+    }
+    free (base);
+}
+
+
+/* Copied from bind_array_variable() in ../arrayfunc.c.  Find the last index and
+ * "insert" right after it (ie. append).  Similiar to list.append() in Python.
+ */
+static void
+array_append (var, arg)
+    SHELL_VAR *var;
+    char *arg;		/* raw string */
+{
+    char *value;
+    arrayind_t N;
+    
+    if (readonly_p (var)) 
+	err_readonly (var->name);
+    else if (noassign_p (var))
+	report_error ("%s: noassign variable", var->name);
+    else {
+	N = array_max_index (array_cell (var));		/* -1 if empty */
+	value = make_variable_value (var, arg);
+	if (var->assign_func)
+	    (*var->assign_func) (var, value, N+1);
+	else
+	    array_insert (array_cell (var), N+1, value);
+	FREE (value);
+    }
+}
+
+
+int
+array_builtin (list)
+    register WORD_LIST *list;
+{
+    char *name, *arg, *sep, *value;
+    SHELL_VAR *var;
+    int flag, opt;
+
+    flag = 0;
+
+    reset_internal_getopt ();
+    while ((opt = internal_getopt (list, "kvli:j:rsc")) != -1) {
+	switch (opt) {
+	case 'k':
+	case 'v':
+	case 'l':
+	    flag = opt;
+	    break;
+	case 'i':
+	    flag = opt;
+	    value = list_optarg;
+	    break;
+	case 'j':
+	    flag = opt;
+	    sep = list_optarg;
+	    break;
+	case 'r':
+	case 's':
+	case 'c':
+	    flag = opt;
+	    break;
+	default:
+	    builtin_usage ();
+	    return (EX_USAGE);
+	}
+    }
+    list = loptend;
+
+    if (list == 0) 		/* 0 argument: array */
+	return (EXECUTION_SUCCESS);
+
+    name = list->word->word;		/* first argument */
+    if (legal_identifier(name) == 0) {
+	sh_invalidid (name);
+	return (EXECUTION_FAILURE);
+    }
+    var = find_variable (name);
+    if (var == 0 || array_p (var) == 0) {
+	sh_notfound (name);
+	return (EXECUTION_FAILURE);
+    }
+
+    list = list->next;
+
+    if (list == 0) {		/* 1 argument: array [...] name */
+	switch (flag) {
+	case 'r':		/* array -r name */
+	    array_reverse (var);
+	    break;
+	case 's':		/* array -s name */
+	    array_sort (var);
+	    break;
+	case 'c':		/* array -c name */
+	    array_collapse (var);
+	    break;
+	case 'i':		/* array -i value name */
+	    print_all_indexes_with_value (var, value);
+	    break;
+	case 'j':		/* array -j sep name */
+	    arg = array_to_string (array_cell (var), sep, 0 /* no quoting */);
+	    puts (arg);
+	    break;
+	case 'k':		/* array -k name */
+	case 'v':		/* array -v name */
+	case 'l':		/* array -l name */
+	default:		/* array name */
+	    print_elements (var, flag);
+	    break;
+	}
+    }
+    
+    /* 2 or more arguments.  So, we are appending.  If 'list == 0' already, then
+     * it falls through.
+     */
+    while (list) {
+	arg = list->word->word;
+	if (flag == 'j' && strlen (sep) != 0) {		/* array -j sep name arg... */
+	    char *end, *subarg;
+
+	    for (end = strstr (arg, sep); end != NULL; end = strstr (arg, sep)) {
+		subarg = substring (arg, 0, end-arg);
+		array_append (var, subarg);
+		free (subarg);
+		arg = end + strlen (sep);
+	    }
+	}
+	array_append (var, arg);	/* append original 'arg' or leftover 'subarg' */
+	list = list->next;
+    }
+
+    stupidly_hack_special_variables (name);
+
+    fflush (stdout);
+    return (EXECUTION_SUCCESS);
+}
+
+#endif
diff -ru bash-2.05b/builtins/eval.def bash/builtins/eval.def
--- bash-2.05b/builtins/eval.def	Mon Apr  8 13:21:04 2002
+++ bash/builtins/eval.def	Wed Feb 12 22:42:25 2003
@@ -51,3 +51,165 @@
   /* Note that parse_and_execute () frees the string it is passed. */
   return (list ? parse_and_execute (string_list (list), "eval", SEVAL_NOHIST) : EXECUTION_SUCCESS);
 }
+
+
+
+/* Add Python's 
+ *	map (function, arg, ...)
+ *	filter (function, arg)
+ * to Bash.
+ *
+ * --William Park <opengeometry at yahoo.ca>
+ */
+
+$BUILTIN arraymap
+$FUNCTION arraymap_builtin
+$SHORT_DOC arraymap command name [name ...]
+Mimicking Python's map() function, it runs 'command' for each element of
+arrays 'name', ... in parallel.  'command' should take as many positional
+parameters as there are arrays.  This is modified version of 'eval'
+builtins, and is equivalent to
+    command "${name[0]}" "${name[0]}" ...
+    command "${name[1]}" "${name[1]}" ...
+    ...
+    command "${name[N]}" "${name[N]}" ...
+where 'N' is the maximum of all indexes.  Array elements are referenced by
+index key, starting from 0 to N, not the order of storage.  So, there can
+be empty parameters.
+$END
+
+
+int
+arraymap_builtin (list)
+    WORD_LIST *list;
+{
+    char *name, *command, *eval_string;
+    int i, n, size, eval_len;
+    SHELL_VAR *var;
+    WORD_LIST *t;
+
+    if (no_options (list))
+	return (EX_USAGE);
+    list = loptend;	/* skip over possible `--' */
+
+    if (list == 0) 		/* 0 argument: arraymap */
+	return (EXECUTION_SUCCESS);
+
+    command = list->word->word;
+    list = list->next;
+
+    if (list == 0) 		/* 1 argument: arraymap command */
+	return (EXECUTION_SUCCESS);
+
+    /* 2 or more arguments: arraymap command a ... */
+
+    n = 0;
+    size = strlen (command);
+    for (t = list; t != 0; t = t->next) {
+	name = t->word->word;
+	if (legal_identifier(name) == 0) {
+	    sh_invalidid (name);
+	    return (EXECUTION_FAILURE);
+	}
+
+	var = find_variable (name);
+	if (var == 0 || array_p (var) == 0) {
+	    sh_notfound (name);
+	    return (EXECUTION_FAILURE);
+	}
+
+	i = array_max_index (array_cell (var));
+	n = (n > i) ? n : i;		/* max of all index */
+
+	/* ' "${name[index]}"'  -->  name + index + 8 */
+	size += strlen (name) + INT_STRLEN_BOUND(intmax_t) + 8;
+    }
+
+    /* command "${name[0]}" "${name[0]}" ...
+     * ...
+     * command "${name[n]}" "${name[n]}" ...
+     */
+    for (i = 0; i <= n; i++) {
+	eval_string = (char *)xmalloc (size + 1);
+
+	strcpy (eval_string, command);
+	eval_len = strlen (eval_string);
+
+	for (t = list; t != 0; t = t->next) {
+	    name = t->word->word;
+	    sprintf (eval_string + eval_len, " \"${%s[%d]}\"", name, i);
+	    eval_len = strlen (eval_string);
+	}
+
+	/* Note that parse_and_execute () frees the string it is passed. */
+	if (parse_and_execute (eval_string, "arraymap", SEVAL_NOHIST) != EXECUTION_SUCCESS) 
+	    return (EXECUTION_FAILURE);
+    }
+
+    return (EXECUTION_SUCCESS);
+}
+
+
+$BUILTIN arrayfilter
+$FUNCTION arrayfilter_builtin
+$SHORT_DOC arrayfilter filter name
+Mimicking Python's filter() function, it runs 'filter' for each element of
+array 'name'.  It returns the array elements, for which 'filter' returns
+success (0).
+$END
+
+
+int
+arrayfilter_builtin (list)
+    WORD_LIST *list;
+{
+    char *name, *filter, *eval_string;
+    int size;
+    SHELL_VAR *var;
+    ARRAY *a;
+    ARRAY_ELEMENT *ae;
+
+    if (no_options (list))
+	return (EX_USAGE);
+    list = loptend;	/* skip over possible `--' */
+
+    if (list == 0) 		/* 0 argument: arrayfilter */
+	return (EXECUTION_SUCCESS);
+
+    filter = list->word->word;
+    list = list->next;
+
+    if (list == 0) 		/* 1 argument: arrayfilter filter */
+	return (EXECUTION_SUCCESS);
+
+    name = list->word->word;	/* 2 arguments: arrayfilter filter name */
+    if (legal_identifier(name) == 0) {
+	sh_invalidid (name);
+	return (EXECUTION_FAILURE);
+    }
+    var = find_variable (name);
+    if (var == 0 || array_p (var) == 0) {
+	sh_notfound (name);
+	return (EXECUTION_FAILURE);
+    }
+
+    /* filter "value"
+     * ...
+     * filter "value"
+     */
+    a = array_cell (var);
+    if (a == 0 || array_empty (a)) return;	/* do nothing */
+
+    for (ae = element_forw (a->head); ae != a->head; ae = element_forw (ae)) {
+	size = strlen (filter) + strlen(element_value (ae)) + 3;
+
+	eval_string = (char *)xmalloc (size + 1);
+	sprintf (eval_string, "%s \"%s\"", filter, element_value (ae));
+
+	/* Note that parse_and_execute () frees the string it is passed. */
+	if (parse_and_execute (eval_string, "arrayfilter", SEVAL_NOHIST) == EXECUTION_SUCCESS)
+	    puts (element_value (ae));
+    }
+
+    return (EXECUTION_SUCCESS);
+}
diff -ru bash-2.05b/execute_cmd.c bash/execute_cmd.c
--- bash-2.05b/execute_cmd.c	Mon Mar 18 13:24:22 2002
+++ bash/execute_cmd.c	Wed Feb 12 22:26:54 2003
@@ -1527,15 +1527,54 @@
   SHELL_VAR *old_value = (SHELL_VAR *)NULL; /* Remember the old value of x. */
 #endif
 
-  if (check_identifier (for_command->name, 1) == 0)
-    {
-      if (posixly_correct && interactive_shell == 0)
+  /* Enable multiple loop variables in for-loop, with syntax
+   *	for a,b,c,... in list; do
+   *	    command
+   *	done
+   * where no spaces are allowed around ',' because Shell expects only one word
+   * for the loop variable.  Items from 'list' will be sequentially assigned to
+   * the loop variables 'a', 'b', 'c', etc.  The loop ends when there is no
+   * items left to assign.  If there is shortage of items, then the last
+   * iteration will run with '' (null) values assigned to the leftover
+   * variables.
+   *
+   * --William Park <opengeometry at yahoo.ca>
+   */
+  int multi_variables;
+  WORD_LIST *list_of_for_variables;
+
+  multi_variables = 0;
+  identifier = for_command->name->word;
+  if (strchr (identifier, ',') != NULL) {	/* if ',' exists */
+      multi_variables = 1;
+      /* Split 'a,b,c,...' into list of 'a', 'b', 'c', ... */
+      list_of_for_variables = list_string (identifier, ",", 0 /* quoted */);
+  }
+
+  if (multi_variables) {
+      /* Check if 'a', 'b', 'c', ... are legal shell variable names. */
+      WORD_LIST *forlist;
+
+      for (forlist = list_of_for_variables; forlist; forlist = forlist->next) {
+	  if (check_identifier (forlist->word, 1) == 0) {
+	      if (posixly_correct && interactive_shell == 0) {
+		  last_command_exit_value = EX_USAGE;
+		  jump_to_top_level (EXITPROG);
+	      }
+	      return (EXECUTION_FAILURE);
+	  }
+      }
+  } else {
+      if (check_identifier (for_command->name, 1) == 0)		/* original code */
 	{
-	  last_command_exit_value = EX_USAGE;
-	  jump_to_top_level (EXITPROG);
+	  if (posixly_correct && interactive_shell == 0)
+	    {
+	      last_command_exit_value = EX_USAGE;
+	      jump_to_top_level (EXITPROG);
+	    }
+	  return (EXECUTION_FAILURE);
 	}
-      return (EXECUTION_FAILURE);
-    }
+  }
 
   loop_level++;
   identifier = for_command->name->word;
@@ -1561,21 +1600,52 @@
     {
       QUIT;
       this_command_name = (char *)NULL;
-      v = bind_variable (identifier, list->word->word);
-      if (readonly_p (v) || noassign_p (v))
-	{
-	  if (readonly_p (v) && interactive_shell == 0 && posixly_correct)
-	    {
-	      last_command_exit_value = EXECUTION_FAILURE;
-	      jump_to_top_level (FORCE_EOF);
-	    }
-	  else
+
+      if (multi_variables) {
+	  /* Assign items from 'list' into 'a', 'b', 'c', ... */
+	  WORD_LIST *forlist;
+
+	  for (forlist = list_of_for_variables; forlist; forlist = forlist->next) {
+	      identifier = forlist->word->word;
+	      if (list) {		/* if there is item to assign */
+		  v = bind_variable (identifier, list->word->word);
+		  /* Goto the next item, only if there are more variables to
+		   * assign.  If finished assigning, then leave the incrementing
+		   * for the next iteration. */
+		  if (forlist->next)
+		      list = list->next;
+	      } else {			/* no more items */
+		  v = bind_variable (identifier, "");
+	      }
+	      if (readonly_p (v) || noassign_p (v)) {
+		  if (readonly_p (v) && interactive_shell == 0 && posixly_correct) {
+		      last_command_exit_value = EXECUTION_FAILURE;
+		      jump_to_top_level (FORCE_EOF);
+		  } else {
+		      run_unwind_frame ("for");
+		      loop_level--;
+		      return (EXECUTION_FAILURE);
+		  }
+	      }
+	  }
+      } else {
+	  v = bind_variable (identifier, list->word->word);		/* original code */
+	  if (readonly_p (v) || noassign_p (v))
 	    {
-	      run_unwind_frame ("for");
-	      loop_level--;
-	      return (EXECUTION_FAILURE);
+	      if (readonly_p (v) && interactive_shell == 0 && posixly_correct)
+		{
+		  last_command_exit_value = EXECUTION_FAILURE;
+		  jump_to_top_level (FORCE_EOF);
+		}
+	      else
+		{
+		  run_unwind_frame ("for");
+		  loop_level--;
+		  return (EXECUTION_FAILURE);
+		}
 	    }
-	}
+      }
+
       retval = execute_command (for_command->action);
       REAP ();
       QUIT;
@@ -1592,6 +1662,8 @@
 	  if (continuing)
 	    break;
 	}
+
+      if (list == 0) break;	/* list is already empty */
     }
 
   loop_level--;
@@ -1613,6 +1685,8 @@
 #endif
 
   dispose_words (releaser);
+  if (multi_variables)		/* clean up multiple loop variables */
+      dispose_words (list_of_for_variables);
   discard_unwind_frame ("for");
   return (retval);
 }
diff -ru bash-2.05b/subst.c bash/subst.c
--- bash-2.05b/subst.c	Mon Jun 24 07:59:45 2002
+++ bash/subst.c	Wed Feb 12 22:26:40 2003
@@ -4359,8 +4359,13 @@
       break;
 #if defined (ARRAY_VARS)
     case VT_ARRAYVAR:
+      /* array[] are indexed from 0 to len-1, if there is no missing index.  In
+       * '${name:i:j}', order of storage is used, not the index.
+       *
+       * --William Park <opengeometry at yahoo.ca>
+       */
       a = (ARRAY *)value;
-      len = array_num_elements (a) + 1;
+      len = array_num_elements (a);
       break;
 #endif
     }




More information about the Python-list mailing list