Size: 5634
Comment: converted to 1.6 markup
|
Size: 5741
Comment: first-line and {{{ and consistent amounts of indenting space; link to FAQ 005 (arrays)
|
Deletions are marked like this. | Additions are marked like this. |
Line 3: | Line 3: |
There are two halves to this: evaluating variables, and assigning values. We'll take each half separately: | |
Line 6: | Line 7: |
{{{ # Bash realvariable=contents ref=realvariable echo "${!ref}" # prints the contents of the real variable}}} |
{{{ # Bash realvariable=contents ref=realvariable echo "${!ref}" # prints the contents of the real variable }}} |
Line 13: | Line 15: |
{{{ # ksh93 realvariable=contents nameref ref=realvariable echo "$ref" # prints the contents of the real variable}}} |
{{{ # ksh93 realvariable=contents nameref ref=realvariable echo "$ref" # prints the contents of the real variable }}} |
Line 19: | Line 22: |
ksh93's `nameref` allows us to work with references to arrays, as well as regular scalar variables. For example, {{{ # ksh93 myfunc() { nameref ref=$1 echo "array $1 has ${#ref[*]} elements" } realarray=(...) myfunc realarray}}} |
ksh93's `nameref` allows us to work with references to [[BashFAQ/005|arrays]], as well as regular scalar variables. For example, {{{ # ksh93 myfunc() { nameref ref=$1 echo "array $1 has ${#ref[*]} elements" } realarray=(...) myfunc realarray }}} |
Line 37: | Line 41: |
{{{ # ksh93 nameref ref=realvariable ref="contents" # realvariable now contains the string "contents"}}} |
{{{ # ksh93 nameref ref=realvariable ref="contents" # realvariable now contains the string "contents" }}} |
Line 44: | Line 49: |
{{{ # Bash ref=realvariable read $ref <<< "contents" # realvariable now contains the string "contents"}}} |
{{{ # Bash ref=realvariable read $ref <<< "contents" # realvariable now contains the string "contents" }}} |
Line 51: | Line 57: |
{{{ # Bash aref=realarray read -a $aref <<< "words go into array elements" echo "${realarray[1]}" # prints "go"}}} |
{{{ # Bash aref=realarray read -a $aref <<< "words go into array elements" echo "${realarray[1]}" # prints "go" }}} |
Line 58: | Line 65: |
{{{ # Bash 3.1 or higher ref=realvariable printf -v $ref "contents"}}} |
{{{ # Bash 3.1 or higher ref=realvariable printf -v $ref "contents" }}} |
Line 67: | Line 75: |
{{{ # Korn shell (all versions): typeset $ref="contents" |
{{{ # Korn shell (all versions): typeset $ref="contents" |
Line 71: | Line 79: |
# Bash: declare $ref="contents"}}} |
# Bash: declare $ref="contents" }}} |
Line 77: | Line 86: |
{{{ # Bourne ref=realvariable read $ref <<EOF contents EOF}}} |
{{{ # Bourne ref=realvariable read $ref <<EOF contents EOF }}} |
Line 99: | Line 109: |
done}}} | done }}} |
How can I use variable variables (indirect variables, pointers, references) or associative arrays?
There are two halves to this: evaluating variables, and assigning values. We'll take each half separately:
Evaluating indirect/reference variables
BASH allows you to expand a parameter indirectly -- that is, one variable may contain the name of another variable:
# Bash realvariable=contents ref=realvariable echo "${!ref}" # prints the contents of the real variable
KornShell (ksh93) has a completely different, more powerful syntax -- the nameref command (also known as typeset -n):
# ksh93 realvariable=contents nameref ref=realvariable echo "$ref" # prints the contents of the real variable
ksh93's nameref allows us to work with references to arrays, as well as regular scalar variables. For example,
# ksh93 myfunc() { nameref ref=$1 echo "array $1 has ${#ref[*]} elements" } realarray=(...) myfunc realarray
We are not aware of any trick that can duplicate that functionality in Bash, POSIX or Bourne shells (short of using eval, which is extremely difficult to do securely).
Unfortunately, for shells other than Bash and ksh93, there is no syntax for evaluating a referenced variable. You would have to use eval, which means you would have to undergo extreme measures to sanitize your data to avoid catastrophe.
Assigning indirect/reference variables
Assigning a value "through" a reference (or pointer, or indirect variable, or whatever you want to call it -- I'm going to use "ref" from now on) is more widely possible, but the means of doing so are extremely shell-specific.
In ksh93, we can just use nameref again:
# ksh93 nameref ref=realvariable ref="contents" # realvariable now contains the string "contents"
In Bash, we can use read and Bash's here string syntax:
# Bash ref=realvariable read $ref <<< "contents" # realvariable now contains the string "contents"
This works equally well with Bash array variables too:
# Bash aref=realarray read -a $aref <<< "words go into array elements" echo "${realarray[1]}" # prints "go"
Another trick is to use Bash's printf -v (only available in recent versions):
# Bash 3.1 or higher ref=realvariable printf -v $ref "contents"
The printf -v trick is handy if your contents aren't a constant string, but rather, something dynamically generated. You can use all of printf's formatting capabilities.
Yet another trick is Korn shell's typeset or Bash's declare. These are roughly equivalent to each other. Both of them cause a variable to become locally scoped to a function, if used inside a function; but if used outside a function, they can operate on global variables.
# Korn shell (all versions): typeset $ref="contents" # Bash: declare $ref="contents"
The advantage of using typeset or declare over eval is that the right hand side of the assignment is not parsed by the shell. If you used eval here, you would have to sanitize/escape the entire right hand side first.
If you aren't using Bash or Korn shell, you can still do assignments to referenced variables using here document syntax:
# Bourne ref=realvariable read $ref <<EOF contents EOF
Remember that, when using a here document, if the sentinel word (EOF in our example) is unquoted, then parameter expansions will be performed inside the body. If the sentinel is quoted, then parameter expansions are not performed. Use whichever is more convenient for your task.
Associative Arrays
Sometimes it's convenient to have associative arrays, arrays indexed by a string. Awk has associative arrays. Perl calls them "hashes", while Tcl simply calls them "arrays". ksh93 supports this kind of array:
# ksh93 typeset -A homedir # Declare ksh93 associative array homedir[jim]=/home/jim homedir[silvia]=/home/silvia homedir[alex]=/home/alex for user in ${!homedir[@]} # Enumerate all indices (user names) do echo "Home directory of user $user is ${homedir[$user]}" done
BASH (including version 3.x) does not support them, unfortunately. Consider switching to awk, perl, ksh93, tcl, etc. if you need this type of data structure to solve your problem.
Before you think of using eval to mimic this behavior in a shell (probably by creating a set of variable names like homedir_alex), try to think of a simpler approach that you could use instead. If this hack still seems to be the best thing to do, have a look at the following disadvantages:
- It's hard to read and to maintain.
The variable names must match the RegularExpression ^[a-zA-Z_][a-zA-Z_0-9]* -- i.e., a variable name cannot contain arbitrary characters but only letters, digits, and underscores. We cannot have a variable's name contain Unix usernames, for instance -- consider a user named hong-hu. A dash '-' cannot be part of a variable name, so the entire attempt to make a variable named homedir_hong-hu is doomed from the start.
Quoting is hard to get right. If content strings (not variable name) can contain whitespace characters and quotes, it's hard to quote it right to preserve it through both shell parsings. And that's just for constants, known at the time you write the program.
If the program handles unsanitized user input, it can be VERY dangerous!