| This file is declare.def, from which is created declare.c. |
| It implements the builtins "declare" and "local" in Bash. |
| |
| 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/>. |
| |
| $PRODUCES declare.c |
| |
| $BUILTIN declare |
| $FUNCTION declare_builtin |
| $SHORT_DOC declare [-aAfFilrtux] [-p] [name[=value] ...] |
| Set variable values and attributes. |
| |
| Declare variables and give them attributes. If no NAMEs are given, |
| display the attributes and values of all variables. |
| |
| Options: |
| -f restrict action or display to function names and definitions |
| -F restrict display to function names only (plus line number and |
| source file when debugging) |
| -p display the attributes and value of each NAME |
| |
| Options which set attributes: |
| -a to make NAMEs indexed arrays (if supported) |
| -A to make NAMEs associative arrays (if supported) |
| -i to make NAMEs have the `integer' attribute |
| -l to convert NAMEs to lower case on assignment |
| -r to make NAMEs readonly |
| -t to make NAMEs have the `trace' attribute |
| -u to convert NAMEs to upper case on assignment |
| -x to make NAMEs export |
| |
| Using `+' instead of `-' turns off the given attribute. |
| |
| Variables with the integer attribute have arithmetic evaluation (see |
| the `let' command) performed when the variable is assigned a value. |
| |
| When used in a function, `declare' makes NAMEs local, as with the `local' |
| command. |
| |
| Exit Status: |
| Returns success unless an invalid option is supplied or an error occurs. |
| $END |
| |
| $BUILTIN typeset |
| $FUNCTION declare_builtin |
| $SHORT_DOC typeset [-aAfFilrtux] [-p] name[=value] ... |
| Set variable values and attributes. |
| |
| Obsolete. See `help declare'. |
| $END |
| |
| #include <config.h> |
| |
| #if defined (HAVE_UNISTD_H) |
| # ifdef _MINIX |
| # include <sys/types.h> |
| # endif |
| # include <unistd.h> |
| #endif |
| |
| #include <stdio.h> |
| |
| #include "../bashansi.h" |
| #include "../bashintl.h" |
| |
| #include "../shell.h" |
| #include "common.h" |
| #include "builtext.h" |
| #include "bashgetopt.h" |
| |
| extern int array_needs_making; |
| extern int posixly_correct; |
| |
| static int declare_internal __P((register WORD_LIST *, int)); |
| |
| /* Declare or change variable attributes. */ |
| int |
| declare_builtin (list) |
| register WORD_LIST *list; |
| { |
| return (declare_internal (list, 0)); |
| } |
| |
| $BUILTIN local |
| $FUNCTION local_builtin |
| $SHORT_DOC local [option] name[=value] ... |
| Define local variables. |
| |
| Create a local variable called NAME, and give it VALUE. OPTION can |
| be any option accepted by `declare'. |
| |
| Local variables can only be used within a function; they are visible |
| only to the function where they are defined and its children. |
| |
| Exit Status: |
| Returns success unless an invalid option is supplied, an error occurs, |
| or the shell is not executing a function. |
| $END |
| int |
| local_builtin (list) |
| register WORD_LIST *list; |
| { |
| if (variable_context) |
| return (declare_internal (list, 1)); |
| else |
| { |
| builtin_error (_("can only be used in a function")); |
| return (EXECUTION_FAILURE); |
| } |
| } |
| |
| #if defined (ARRAY_VARS) |
| # define DECLARE_OPTS "+acfilprtuxAF" |
| #else |
| # define DECLARE_OPTS "+cfilprtuxF" |
| #endif |
| |
| /* The workhorse function. */ |
| static int |
| declare_internal (list, local_var) |
| register WORD_LIST *list; |
| int local_var; |
| { |
| int flags_on, flags_off, *flags; |
| int any_failed, assign_error, pflag, nodefs, opt; |
| char *t, *subscript_start; |
| SHELL_VAR *var; |
| FUNCTION_DEF *shell_fn; |
| |
| flags_on = flags_off = any_failed = assign_error = pflag = nodefs = 0; |
| reset_internal_getopt (); |
| while ((opt = internal_getopt (list, DECLARE_OPTS)) != EOF) |
| { |
| flags = list_opttype == '+' ? &flags_off : &flags_on; |
| |
| switch (opt) |
| { |
| case 'a': |
| #if defined (ARRAY_VARS) |
| *flags |= att_array; |
| break; |
| #else |
| builtin_usage (); |
| return (EX_USAGE); |
| #endif |
| case 'A': |
| #if defined (ARRAY_VARS) |
| *flags |= att_assoc; |
| break; |
| #else |
| builtin_usage (); |
| return (EX_USAGE); |
| #endif |
| case 'p': |
| if (local_var == 0) |
| pflag++; |
| break; |
| case 'F': |
| nodefs++; |
| *flags |= att_function; |
| break; |
| case 'f': |
| *flags |= att_function; |
| break; |
| case 'i': |
| *flags |= att_integer; |
| break; |
| case 'r': |
| *flags |= att_readonly; |
| break; |
| case 't': |
| *flags |= att_trace; |
| break; |
| case 'x': |
| *flags |= att_exported; |
| array_needs_making = 1; |
| break; |
| #if defined (CASEMOD_ATTRS) |
| # if defined (CASEMOD_CAPCASE) |
| case 'c': |
| *flags |= att_capcase; |
| if (flags == &flags_on) |
| flags_off |= att_uppercase|att_lowercase; |
| break; |
| # endif |
| case 'l': |
| *flags |= att_lowercase; |
| if (flags == &flags_on) |
| flags_off |= att_capcase|att_uppercase; |
| break; |
| case 'u': |
| *flags |= att_uppercase; |
| if (flags == &flags_on) |
| flags_off |= att_capcase|att_lowercase; |
| break; |
| #endif /* CASEMOD_ATTRS */ |
| default: |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| } |
| |
| list = loptend; |
| |
| /* If there are no more arguments left, then we just want to show |
| some variables. */ |
| if (list == 0) /* declare -[aAfFirtx] */ |
| { |
| /* Show local variables defined at this context level if this is |
| the `local' builtin. */ |
| if (local_var) |
| { |
| register SHELL_VAR **vlist; |
| register int i; |
| |
| vlist = all_local_variables (); |
| |
| if (vlist) |
| { |
| for (i = 0; vlist[i]; i++) |
| print_assignment (vlist[i]); |
| |
| free (vlist); |
| } |
| } |
| else if (pflag && (flags_on == 0 || flags_on == att_function)) |
| show_all_var_attributes (flags_on == 0, nodefs); |
| else if (flags_on == 0) |
| return (set_builtin ((WORD_LIST *)NULL)); |
| else |
| set_or_show_attributes ((WORD_LIST *)NULL, flags_on, nodefs); |
| |
| return (sh_chkwrite (EXECUTION_SUCCESS)); |
| } |
| |
| if (pflag) /* declare -p [-aAfFirtx] name [name...] */ |
| { |
| for (any_failed = 0; list; list = list->next) |
| { |
| pflag = show_name_attributes (list->word->word, nodefs); |
| if (pflag) |
| { |
| sh_notfound (list->word->word); |
| any_failed++; |
| } |
| } |
| return (sh_chkwrite (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS)); |
| } |
| |
| #define NEXT_VARIABLE() free (name); list = list->next; continue |
| |
| /* There are arguments left, so we are making variables. */ |
| while (list) /* declare [-aAfFirx] name [name ...] */ |
| { |
| char *value, *name; |
| int offset, aflags; |
| #if defined (ARRAY_VARS) |
| int making_array_special, compound_array_assign, simple_array_assign; |
| #endif |
| |
| name = savestring (list->word->word); |
| offset = assignment (name, 0); |
| aflags = 0; |
| |
| if (offset) /* declare [-aAfFirx] name=value */ |
| { |
| name[offset] = '\0'; |
| value = name + offset + 1; |
| if (name[offset - 1] == '+') |
| { |
| aflags |= ASS_APPEND; |
| name[offset - 1] = '\0'; |
| } |
| } |
| else |
| value = ""; |
| |
| #if defined (ARRAY_VARS) |
| compound_array_assign = simple_array_assign = 0; |
| subscript_start = (char *)NULL; |
| if (t = strchr (name, '[')) /* ] */ |
| { |
| /* If offset != 0 we have already validated any array reference */ |
| if (offset == 0 && valid_array_reference (name) == 0) |
| { |
| sh_invalidid (name); |
| assign_error++; |
| NEXT_VARIABLE (); |
| } |
| subscript_start = t; |
| *t = '\0'; |
| making_array_special = 1; |
| } |
| else |
| making_array_special = 0; |
| #endif |
| |
| /* If we're in posix mode or not looking for a shell function (since |
| shell function names don't have to be valid identifiers when the |
| shell's not in posix mode), check whether or not the argument is a |
| valid, well-formed shell identifier. */ |
| if ((posixly_correct || (flags_on & att_function) == 0) && legal_identifier (name) == 0) |
| { |
| sh_invalidid (name); |
| assign_error++; |
| NEXT_VARIABLE (); |
| } |
| |
| /* If VARIABLE_CONTEXT has a non-zero value, then we are executing |
| inside of a function. This means we should make local variables, |
| not global ones. */ |
| |
| /* XXX - this has consequences when we're making a local copy of a |
| variable that was in the temporary environment. Watch out |
| for this. */ |
| if (variable_context && ((flags_on & att_function) == 0)) |
| { |
| #if defined (ARRAY_VARS) |
| if (flags_on & att_assoc) |
| var = make_local_assoc_variable (name); |
| else if ((flags_on & att_array) || making_array_special) |
| var = make_local_array_variable (name); |
| else |
| #endif |
| var = make_local_variable (name); |
| if (var == 0) |
| { |
| any_failed++; |
| NEXT_VARIABLE (); |
| } |
| } |
| else |
| var = (SHELL_VAR *)NULL; |
| |
| /* If we are declaring a function, then complain about it in some way. |
| We don't let people make functions by saying `typeset -f foo=bar'. */ |
| |
| /* There should be a way, however, to let people look at a particular |
| function definition by saying `typeset -f foo'. */ |
| |
| if (flags_on & att_function) |
| { |
| if (offset) /* declare -f [-rix] foo=bar */ |
| { |
| builtin_error (_("cannot use `-f' to make functions")); |
| free (name); |
| return (EXECUTION_FAILURE); |
| } |
| else /* declare -f [-rx] name [name...] */ |
| { |
| var = find_function (name); |
| |
| if (var) |
| { |
| if (readonly_p (var) && (flags_off & att_readonly)) |
| { |
| builtin_error (_("%s: readonly function"), name); |
| any_failed++; |
| NEXT_VARIABLE (); |
| } |
| |
| /* declare -[Ff] name [name...] */ |
| if (flags_on == att_function && flags_off == 0) |
| { |
| #if defined (DEBUGGER) |
| if (nodefs && debugging_mode) |
| { |
| shell_fn = find_function_def (var->name); |
| if (shell_fn) |
| printf ("%s %d %s\n", var->name, shell_fn->line, shell_fn->source_file); |
| else |
| printf ("%s\n", var->name); |
| } |
| else |
| #endif /* DEBUGGER */ |
| { |
| t = nodefs ? var->name |
| : named_function_string (name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL); |
| printf ("%s\n", t); |
| any_failed = sh_chkwrite (any_failed); |
| } |
| } |
| else /* declare -[fF] -[rx] name [name...] */ |
| { |
| VSETATTR (var, flags_on); |
| VUNSETATTR (var, flags_off); |
| } |
| } |
| else |
| any_failed++; |
| NEXT_VARIABLE (); |
| } |
| } |
| else /* declare -[aAirx] name [name...] */ |
| { |
| /* Non-null if we just created or fetched a local variable. */ |
| if (var == 0) |
| var = find_variable (name); |
| |
| if (var == 0) |
| { |
| #if defined (ARRAY_VARS) |
| if (flags_on & att_assoc) |
| var = make_new_assoc_variable (name); |
| else if ((flags_on & att_array) || making_array_special) |
| var = make_new_array_variable (name); |
| else |
| #endif |
| |
| if (offset) |
| var = bind_variable (name, "", 0); |
| else |
| { |
| var = bind_variable (name, (char *)NULL, 0); |
| VSETATTR (var, att_invisible); |
| } |
| } |
| |
| /* Cannot use declare +r to turn off readonly attribute. */ |
| if (readonly_p (var) && (flags_off & att_readonly)) |
| { |
| sh_readonly (name); |
| any_failed++; |
| NEXT_VARIABLE (); |
| } |
| |
| /* Cannot use declare to assign value to readonly or noassign |
| variable. */ |
| if ((readonly_p (var) || noassign_p (var)) && offset) |
| { |
| if (readonly_p (var)) |
| sh_readonly (name); |
| assign_error++; |
| NEXT_VARIABLE (); |
| } |
| |
| #if defined (ARRAY_VARS) |
| if ((making_array_special || (flags_on & (att_array|att_assoc)) || array_p (var) || assoc_p (var)) && offset) |
| { |
| int vlen; |
| vlen = STRLEN (value); |
| |
| if (value[0] == '(' && value[vlen-1] == ')') |
| compound_array_assign = 1; |
| else |
| simple_array_assign = 1; |
| } |
| |
| /* Cannot use declare +a name or declare +A name to remove an |
| array variable. */ |
| if (((flags_off & att_array) && array_p (var)) || ((flags_off & att_assoc) && assoc_p (var))) |
| { |
| builtin_error (_("%s: cannot destroy array variables in this way"), name); |
| any_failed++; |
| NEXT_VARIABLE (); |
| } |
| |
| if ((flags_on & att_array) && assoc_p (var)) |
| { |
| builtin_error (_("%s: cannot convert associative to indexed array"), name); |
| any_failed++; |
| NEXT_VARIABLE (); |
| } |
| if ((flags_on & att_assoc) && array_p (var)) |
| { |
| builtin_error (_("%s: cannot convert indexed to associative array"), name); |
| any_failed++; |
| NEXT_VARIABLE (); |
| } |
| |
| /* declare -A name[[n]] makes name an associative array variable. */ |
| if (flags_on & att_assoc) |
| { |
| if (assoc_p (var) == 0) |
| var = convert_var_to_assoc (var); |
| } |
| /* declare -a name[[n]] or declare name[n] makes name an indexed |
| array variable. */ |
| else if ((making_array_special || (flags_on & att_array)) && array_p (var) == 0 && assoc_p (var) == 0) |
| var = convert_var_to_array (var); |
| #endif /* ARRAY_VARS */ |
| |
| VSETATTR (var, flags_on); |
| VUNSETATTR (var, flags_off); |
| |
| #if defined (ARRAY_VARS) |
| if (offset && compound_array_assign) |
| assign_array_var_from_string (var, value, aflags); |
| else if (simple_array_assign && subscript_start) |
| { |
| /* declare [-a] name[N]=value */ |
| *subscript_start = '['; /* ] */ |
| var = assign_array_element (name, value, 0); /* XXX - not aflags */ |
| *subscript_start = '\0'; |
| } |
| else if (simple_array_assign) |
| /* let bind_array_variable take care of this. */ |
| bind_array_variable (name, 0, value, aflags); |
| else |
| #endif |
| /* bind_variable_value duplicates the essential internals of |
| bind_variable() */ |
| if (offset) |
| bind_variable_value (var, value, aflags); |
| |
| /* If we found this variable in the temporary environment, as with |
| `var=value declare -x var', make sure it is treated identically |
| to `var=value export var'. Do the same for `declare -r' and |
| `readonly'. Preserve the attributes, except for att_tempvar. */ |
| /* XXX -- should this create a variable in the global scope, or |
| modify the local variable flags? ksh93 has it modify the |
| global scope. |
| Need to handle case like in set_var_attribute where a temporary |
| variable is in the same table as the function local vars. */ |
| if ((flags_on & (att_exported|att_readonly)) && tempvar_p (var)) |
| { |
| SHELL_VAR *tv; |
| char *tvalue; |
| |
| tv = find_tempenv_variable (var->name); |
| if (tv) |
| { |
| tvalue = var_isset (var) ? savestring (value_cell (var)) : savestring (""); |
| tv = bind_variable (var->name, tvalue, 0); |
| tv->attributes |= var->attributes & ~att_tempvar; |
| if (tv->context > 0) |
| VSETATTR (tv, att_propagate); |
| free (tvalue); |
| } |
| VSETATTR (var, att_propagate); |
| } |
| } |
| |
| stupidly_hack_special_variables (name); |
| |
| NEXT_VARIABLE (); |
| } |
| |
| return (assign_error ? EX_BADASSIGN |
| : ((any_failed == 0) ? EXECUTION_SUCCESS |
| : EXECUTION_FAILURE)); |
| } |