Local arrays in zsh - arrays

Zsh has two nice features, the local keyword and the arrays concept.
local my_var # Declare variable local to this function
and
arr=(1 2 3) # Create array with 3 elements
arr+=4 # Add a fourth element
respectively.
The idea to combine them (local arr=()) is irresistible, but seems to just give an error? Is it possible?

At least the situation in 1999 was that it's not possible on a single line, but this work around exists:
local arr
arr=()
Credits should go to Peter Stephenson for answering this back then.

Of course, now you can do:
local -a arr

Related

How to pass array literal to a bash function

First I've read about passing arrays in general -- all examples I saw first created temporary variable for array and then passed it. Taken from https://stackoverflow.com/a/26443029/210342
show_value () # array index
{
local -n myarray=$1
local idx=$2
echo "${myarray[$idx]}"
}
shadock=(ga bu zo meu)
show_value shadock 2
Is there a way to pass array directly as literal, i.e. without creating temporary variable?
I tried naive approach simply substituting the name with data, but I syntax error on "(".
Update:
I use openSUSE Leap 15.3 with bash 4.4. The above code of course works, but I would like to change the call into:
show_value (ga bu zo meu) 2
i.e. pass array data directly (without using extra variable).
If you want to change the order of the arguments:
show_value () # index array_element [...]
{
local idx=$1
local -a myarray=("${#:2}")
echo "${myarray[$idx]}"
}
then
shadock=(ga bu zo meu)
show_value 2 "${shadock[#]}" # => zo
If you want to keep the index as the last argument, then
show_value () # array_element [...] index
{
local -a myarray=("${#:1:$#-1}")
local idx=${!#}
echo "${myarray[$idx]}"
}
show_value "${shadock[#]}" 2 # => zo
local -n myarray=$1
is certainly much tidier than all that, isn't it? It will also be faster and more memory efficient -- you don't have to copy all the data.
Function arguments are not structured enough to handle nested data structures like arrays. Arguments are a flat list of strings, that's it.
You can expand the array inline:
show_value "${shadock[#]}" 2
However the delimiters are lost. There's no way to know where the array starts and end since it expands to:
show_value ga bu zo meu 2
You'll have to figure out the array bounds yourself. Options include:
If the command has only a single array parameter, make it the last one. This is what many traditional UNIX tools that take multiple file names do. Examples: ls <file>..., chmod <mode> <file>....
You can put the array in the middle if there's a fixed number of arguments preceding/following it such that you can unambiguously determine where the array is. Example: cp <file>... <dir>.
If you have multiple arrays, you can ask users to separate them with --. Many of git's more complicated subcommands do this.
I'd caution against taking an array name as an argument. That's a very shell-specific technique. It would make it hard to turn the function into a full-fledged script or binary down the road.
Can you consider this syntax :
show_value () # array index
{
local -a myarray=$1
local idx=$2
echo "${myarray[$idx]}"
}
show_value "(ga bu zo meu)" 2
For security reasons, you need to be sure of the contents of the array you pass into the function.

Bash Arrays - Zip array of variable names with corresponding values

As I am teaching myself Bash programming, I came across an interesting use case, where I want to take a list of variables that exist in the environment, and put them into an array. Then, I want to output a list of the variable names and their values, and store that output in an array, one entry per variable.
I'm only about 2 weeks into Bash shell scripting in any "real" way, and I am educating myself on arrays. A common function in other programming language is the ability to "zip" two arrays, e.g. as is done in Python. Another common feature in any programming language is indirection, e.g. via pointer indirection, etc. This is largely academic, to teach myself through a somewhat challenging example, but I think this has widespread use if for no other reason than debugging, keeping track of overall system state, etc.
What I want is for the following input... :
VAR_ONE="LIGHT RED"
VAR_TWO="DARK GREEN"
VAR_THREE="BLUE"
VARIABLE_ARRAY=(VAR_ONE VAR_TWO VAR_THREE)
... to be converted into the following output (as an array, one element per line):
VAR_ONE: LIGHT RED
VAR_TWO: DARK GREEN
VAR_THREE: BLUE
Constraints:
Assume that I do not have control of all of the variables, so I cannot just sidestep the problem e.g. by using an associative array from the get-go. (i.e. please do not recommend avoiding the need for indirect reference lookups altogether by never having a discrete variable named "VAR_ONE"). But a solution that stores the result in an associative array is fine.
Assume that variable names will never contain spaces, but their values might.
The final output should not contain separate elements just because the input variables had values containing spaces.
What I've read about so far:
I've read some StackOverflow posts like this one, that deal with using indirect references to arrays themselves (e.g. if you have three arrays and want to choose which one to pull from based on an "array choice" variable): How to iterate over an array using indirect reference?
I've also found one single post that deals with "zipping" arrays in Bash in the manner I'm talking about, where you pair-up e.g. the 1st element from array1 and array2, then pair up the 2nd elements, etc.: Iterate over two arrays simultaneously in bash
...but I haven't found anything that quite discusses this unique use-case...
QUESTION:
How should I make an array containing a list of variable names and their values (colon-separated), given an array containing a list of variable names only. I'm not "failing to come up with any way to do it" but I want to find the "preferred" way to do this in Bash, considering performance, security, and being concise/understandable.
EDIT: I'll post what I've come up with thus far as an answer to this post... but not mark it as answered, since I want to also hear some unbiased recommendations...
OP starts with:
VAR_ONE="LIGHT RED"
VAR_TWO="DARK GREEN"
VAR_THREE="BLUE"
VARIABLE_ARRAY=(VAR_ONE VAR_TWO VAR_THREE)
OP has provided an answer with 4 sets of code:
# first 3 sets of code generate:
$ typeset -p outputValues
declare -a outputValues=([0]="VAR_ONE: LIGHT RED" [1]="VAR_TWO: DARK GREEN" [2]="VAR_THREE: BLUE")
# the 4th set of code generates the following where the data values are truncated at the first space:
$ typeset -p outputValues
declare -a outputValues=([0]="VAR_ONE: LIGHT" [1]="VAR_TWO: DARK" [2]="VAR_THREE: BLUE")
NOTES:
I'm assuming the output from the 4th set of code is wrong so will be ignoring this one
OP's code samples touch on a couple ideas I'm going to make use of (below) ... nameref's (declare -n <variable_name>) and indirect variable references (${!<variable_name>})
For readability (and maintainability by others) I'd probably avoid the various eval and expansion ideas and instead opt for using bash namesref's (declare -n); a quick example:
$ x=5
$ echo "${x}"
5
$ y=x
$ echo "${y}"
x
$ declare -n y="x" # nameref => y=(value of x)
$ echo "${y}"
5
Pulling this into the original issue we get:
unset outputValues
declare -a outputValues # optional; declare 'normal' array
for var_name in "${VARIABLE_ARRAY[#]}"
do
declare -n data_value="${var_name}"
outputValues+=("${var_name}: ${data_value}")
done
Which gives us:
$ typeset -p outputValues
declare -a outputValues=([0]="VAR_ONE: LIGHT RED" [1]="VAR_TWO: DARK GREEN" [2]="VAR_THREE: BLUE")
While this generates the same results (as OP's first 3 sets of code) there is (for me) the nagging question of how is this new array going to be used?
If the sole objective is to print this pre-formatted data to stdout ... ok, though why bother with a new array when the same can be done with the current array and nameref's?
If the objective is to access this array as sets of variable name/value pairs for processing purposes, then the current structure is going to be hard(er) to work with, eg, each array 'value' will need to be parsed/split based on the delimiter :<space> in order to access the actual variable names and values.
In this scenario I'd opt for using an associative array, eg:
unset outputValues
declare -A outputValues # required; declare associative array
for var_name in "${VARIABLE_ARRAY[#]}"
do
declare -n data_value="${var_name}"
outputValues[${var_name}]="${data_value}"
done
Which gives us:
$ typeset -p outputValues
declare -A outputValues=([VAR_ONE]="LIGHT RED" [VAR_THREE]="BLUE" [VAR_TWO]="DARK GREEN" )
NOTES:
again, why bother with a new array when the same can be done with the current array and nameref's?
if the variable $data_value is to be re-used in follow-on code as a 'normal' variable it will be necessary to remove the nameref attribute (unset -n data_value)
With an associative array (index=variable name / array element=variable value) it becomes easier to reference the variable name/value pairs, eg:
$ myvar=VAR_ONE
$ echo "${myvar}: ${outputValues[${myvar}]}"
VAR_ONE: LIGHT RED
$ for var_name in "${!outputValues[#]}"; do echo "${var_name}: ${outputValues[${var_name}]}"; done
VAR_ONE: LIGHT RED
VAR_THREE: BLUE
VAR_TWO: DARK GREEN
In older versions of bash (before nameref's were available), and still available in newer versions of bash, there's the option of using indirect variable references;
$ x=5
$ echo "${x}"
5
$ unset -n y # make sure 'y' has not been previously defined as a nameref
$ y=x
$ echo "${y}"
x
$ echo "${!y}"
5
Pulling this into the associative array approach:
unset -n var_name # make sure var_name not previously defined as a nameref
unset outputValues
declare -A outputValues # required; declare associative array
for var_name in "${VARIABLE_ARRAY[#]}"
do
outputValues[${var_name}]="${!var_name}"
done
Which gives us:
$ typeset -p outputValues
declare -A outputValues=([VAR_ONE]="LIGHT RED" [VAR_THREE]="BLUE" [VAR_TWO]="DARK GREEN" )
NOTE: While this requires less coding in the for loop, if you forget to unset -n the variable (var_name in this case) then you'll end up with the wrong results if var_name was previously defined as a nameref; perhaps a minor issue but it requires the coder to know of, and code for, this particular issue ... a bit too esoteric (for my taste) so I prefer to stick with namerefs ... ymmv ...
I've come up with a handful of possible solutions in the last couple days, each one with their own pro's and con's. I won't mark this as the answer for awhile though, since I'm interested in hearing unbiased recommendations.
My brainstorming solutions thus far:
OPTION #1 - FOR-LOOP:
alias PrintCommandValues='unset outputValues
for var in ${VARIABLE_ARRAY[#]}
do outputValues+=("${var}: ${!var}")
done; printf "%s\n\n" "${outputValues[#]}"'
PrintCommandValues
Pro's: Traditional, easy to understand
Cons: A little verbose. I'm not sure about Bash, but I've been doing a lot of Mathematica programming (imperative-style), where such loops are notably slower. Anybody know if that's true for Bash?
OPTION #2 - EVAL:
i=0; outputValues=("${VARIABLE_ARRAY[#]}")
eval declare "${VARIABLE_ARRAY[#]/#/outputValues[i++]+=:\\ $}"
printf "%s\n\n" "${outputValues[#]}"
Pros: Shorter than the for-loop, and still easy to understand.
Cons: I'm no expert, but I've read a lot of warnings to avoid eval whenever possible, due to security issues. Probably not something I'll concern myself a ton over when I'm mostly writing scripts for "handy utility purposes" for my personal machine only, but...
OPTION #3 - QUOTED DECLARE WITH PARENTHESIS:
i=0; declare -a outputValues="(${VARIABLE_ARRAY[#]/%/'\:\ "${!VARIABLE_ARRAY[i++]}"'})"
printf "%s\n\n" "${outputValues[#]}"
Pros: Super-concise. I just plain stumbled onto this syntax -- I haven't found it mentioned anywhere on the web. Apparently, using declare in Bash (I use version 4.4.20(1)), if (and ONLY if) you place array-style (...) brackets after the equals-sign, and quote it, you get one more "round" of expansion/dereferencing, similar to eval. I happened to be toying with this post, and found the part about the "extra expansion" by accident.
For example, compare these two tests:
varName=varOne; varOne=something
declare test1=\$$varName
declare -a test2="(\$$varName)"
declare -p test1 test2
Output:
declare -- test1="\$varOne"
declare -a test2=([0]="something")
Pretty neat, I think...
Anyways, the cons for this method are... I've never seen it documented officially or unofficially anywhere, so... portability...?
Alternative for this option:
i=0; declare -a LABELED_VARIABLE_ARRAY="(${VARIABLE_ARRAY[#]/%/'\:\ \$"${VARIABLE_ARRAY[i++]}"'})"
declare -a outputValues=("${LABELED_VARIABLE_ARRAY[#]#P}")
printf "%s\n\n" "${outputValues[#]}"
JUST FOR FUN - BRACE EXPANSION:
unset outputValues; OLDIFS=$IFS; IFS=; i=0; j=0
declare -n nameCursor=outputValues[i++]; declare -n valueCursor=outputValues[j++]
declare {nameCursor+=,valueCursor+=": "$}{VAR_ONE,VAR_TWO,VAR_THREE}
printf "%s\n\n" "${outputValues[#]}"
IFS=$OLDIFS
Pros: ??? Maybe speed?
Cons: Pretty verbose, not very easy to understand
Anyways, those are all of my methods... Are any of them reasonable, or would you do something different altogether?

Bash-Scripting: what does ${VARIABLE[number]} mean if VARIABLE is not an array?

I know that there are arrays in bash-scripting. For example:
JOBS=("JOB1", "JOB2", "JOB3")
Then one can refer to, say, JOB2 as follows:
${JOBS[1]}
In a bash script I recently encountered a normal (non-array) variable:
JOB="XYZ"
Later in the script, this variable was referred to as follows:
${JOB[0]}
and:
${JOB[1]}
Since JOB is not an array, I do not understand what ${JOB[number]} is expanded to.
Is this just a programming mistake? Or is there some construct regarding normal variables I am not aware of?
bash doesn't have array values at all; it provides array syntax to use with names that have an array attribute set:
$ a=()
$ declare -p a
declare -a a=()
The -a in the output indicates that the array attribute is set on a, having been set implicitly by the preceding array assignment.
For such variables, the name-plus-index acts as a sort of "virtual" name, and like any other name, expands to the empty string if the name doesn't actually exist, as is the case with ${JOB[1]}. A side effect of the implementation is the $foo and ${foo[0]} are generally equivalent, whether or not foo has its array attribute set.
I believe newer versions of Bash support one-dimensional arrays and the syntax can be seen in the following link as follows :
Advanced Bash-Scripting Guide
If Variable is not an array, ${Variable[Number]} will work only for Number=0 but not for any other number as ${Variable[0]} and $Variable - both are same.

How can I reference an existing bash array using a 2nd variable containing the name of the array?

My closest most helpful matches when I searched for an answer ahead of posting:
Iterate over array in shell whose name is stored in a variable
How to use an argument/parameter name as a variable in a bash script
How to iterate over an array using indirect reference?
My attempt with partial success:
#!/bin/bash
declare -a large_furry_mammals
declare -a array_reference
# I tried both declaring array_reference as an array and
# not declaring it as an array. no change in behavior.
large_furry_mammals=(horse zebra gorilla)
size=large
category=mammals
tmp="${size}_furry_${category}"
eval array_reference='$'$tmp
echo tmp=$tmp
echo array_reference[0]=${array_reference[0]}
echo array_reference[1]=${array_reference[1]}
Output
tmp=large_furry_mammals
array_reference[0]=horse
array_reference[1]=
Expectation
I would have expected to get the output zebra when I echoed array_reference[1].
...but I'm missing some subtlety...
Why can I not access elements of the index array beyond index 0?
This suggests that array_reference is not actually being treated as an array.
I'm not looking to make a copy of the array. I want to reference (what will be) a static array based on a variable pointing to that array, i.e., ${size}_furry_${category} -> large_furry_mammals.
I've been successful with the general idea here using the links I've posted but only as long as its not an array. When it's an array, it's falling down for me.
Addendum Dec 5, 2018
bash 4.3 is not available in this case. #benjamin's answer does work on under 4.3.
I'll be needing to loop over the resulting array variable's contents. This kinda dumb example I gave involving mammals was just to describe the concept. There's actually a real world case around this. I have set of static reference arrays and an input string would be parsed to select which array was relevant and then I will loop over the array that was selected. I could do a case statement but with more than 100 reference arrays that would be the direct but overly verbose way to do it.
This pseudo code is probably better example of what I'm going after.
m1_array=(x a r d)
m2_array=(q 3 fg d)
m3_array=(c e p)
Based on some logic...select which array prefix you need.
x=m1
for each element in ${x}_array
do
some-task
done
I'm doing some testing with #eduardo's solution to see if I can adapt the way he references the variables to get to my endgame.
** Addendum #2 December 14, 2018 **
Solution
I found it! Working with #eduardo's example I came up with the following:
#!/bin/bash
declare -a large_furry_mammals
#declare -a array_reference
large_furry_mammals=(horse zebra gorilla)
size=large
category=mammals
tmp="${size}_furry_${category}[#]"
for element in "${!tmp}"
do
echo $element
done
Here is what execution looks like. We successfully iterate over the elements of the array string that was built dynamically.
./example3b.sh
horse
zebra
gorilla
Thank you everyone.
If you have Bash 4.3 or newer, you can use namerefs:
large_furry_mammals=(horse zebra gorilla)
size=large
category=mammals
declare -n array_reference=${size}_furry_$category
printf '%s\n' "${array_reference[#]}"
with output
horse
zebra
gorilla
This is a reference, so changes are reflected in both large_furry_mammals and array_reference:
$ array_reference[0]='donkey'
$ large_furry_mammals[3]='llama'
$ printf '%s\n' "${array_reference[#]}"
donkey
zebra
gorilla
llama
$ printf '%s\n' "${large_furry_mammals[#]}"
donkey
zebra
gorilla
llama
declare -a large_furry_mammals
declare -a array_reference
large_furry_mammals=(horse zebra gorilla)
size=large
category=mammals
echo ${large_furry_mammals[#]}
tmp="${size}_furry_${category}"
array_reference=${tmp}"[1]"
eval ${array_reference}='bear'
echo tmp=$tmp
echo ${large_furry_mammals[#]}

Bash, variables and arrays

I crawled through lots of boards but didn't find the final solution for my problem.
I have got an array, named "array0", the name is stored to a variable called arrayname. Also I've got a logged IP address, let's say 127.0.0.1, also stored in a variable, called ip.
Now I want to assign the IP to index 3 in the array like that:
"$arrayname"[3]="$ip"
So, this didn't work. I tried lots of ways how to solve that but none worked.
Is anyone out there who can tell me the final solution for this case?
Update: The given opportunities to handle the problem are great! But I forgot to mention that the array I'm working with is just sourced from another file (also written in bash). My goal is now to edit the array in the sourced file itself. Any more ideas for that?
Try
read ${arrayname}[3] <<<"$ip"
You'll need to use the declare command and indirect parameter expansion, but it's a little tricky to use with array names. It helps if you think of the index as part of the variable name, instead of an operator applied to the array name, like in most languages.
array0=(1 2 3 4 5)
arrayname=array0
name=$arrayname[3]
declare "$name=$ip"
echo "${!name}
And yet another way to do it, this time using the versatile printf.
printf -v "$arrayname[3]" %s "$ip"
demo
#!/bin/bash
array0=(a b c d e)
echo "${array0[#]}"
arrayname='array0'
ip='127.0.0.1'
printf -v "$arrayname[3]" %s "$ip"
echo "${array0[#]}"
output
a b c d e
a b c 127.0.0.1 e
See this:
# declare -a arrayname=(element1 element2 element3)
# echo ${arrayname[0]}
element1
# arrayname[4]="Yellow"
# echo ${arrayname[4]}
Yellow
# export ip="192.168.190.23"
# arrayname[5]=$ip
# echo ${arrayname[5]}
192.168.190.23
You don't have to use quotes.
After initializing the arrays, you can access the array elements using their indices as follows.
Access as:
${arrayname[3]}

Resources