Bash arrays: compound assignments fail - arrays

I have no idea why the compound array initialization does not work for me.
minimal example:
#!/bin/bash
#
MINRADIUS=( 'foo' 'bar' 'foobar' )
for i in {0..2..1}; do echo ${MINRADIUS[$i]}; done
output is
$ sh test.sh
(foo bar foobar)
with 2 additional blank lines.
Fieldwise initialization works:
#!/bin/bash
#
MINRADIUS[0]="foo"
MINRADIUS[1]="bar"
MINRADIUS[2]="foobar"
for i in {0..2..1}; do echo ${MINRADIUS[$i]}; done
$ sh test.sh
foo
bar
foobar
I have tried every possible combination of braces, quotes and "declare -a".
Could it be related to my bash version? I'm running version 4.1.2(1).

The problem is, you are not using bash. Shebang doesn't matter if you run your script throught sh. Try bash instead.

I tried the below code and its working fine for me. Using bash 3.2.39(1)-release
#!/bin/bash
#
MINRADIUS=( 'foo' 'bar' 'foobar' )
for i in {0,1,2}; do echo ${MINRADIUS[$i]}; done
Output for this was
foo
bar
foobar
For me with your code it was giving an error
line 4: {0..1..2}: syntax error: operand expected (error token is "{0..1..2}")

I would suspect there is some quoting going on in your first example using compound assignments. Using this modified test script:
#!/bin/bash
echo "SHELL=${SHELL}"
echo 'Single-quoted v:'
v='(a b c)'; for i in {0..2}; do echo "v[$i]=${v[i]}"; done
echo 'Double-quoted v:'
v="(a b c)"; for i in {0..2}; do echo "v[$i]=${v[i]}"; done
echo 'Unquoted v:'
v=(a b c); for i in {0..2}; do echo "v[$i]=${v[i]}"; done
I get the following output:
$ sh test.sh
SHELL=/bin/bash
Single-quoted v:
v[0]=(a b c)
v[1]=
v[2]=
Double-quoted v:
v[0]=(a b c)
v[1]=
v[2]=
Unquoted v:
v[0]=a
v[1]=b
v[2]=c
If you quote the assignment, it becomes a simple variable assignment; a simple mistake to make that is easily overlooked.

Related

syntax error when using eval for dynamic array initiation

var=(a b c)
eval "${var[0]}=(1 2)"
then I got an error message says
bash: syntax error near unexpected token `1'
So what does the syntax error mean and how to solve it? Thanks a lot!
The simplest way to understand how is eval affecting the line you wrote is to replace eval with echo. Issuing the command with echo, it shows this:
$ var=(a b c)
$ echo "${var[0]}=(1 2)"
a=(1 2)
As you can see, the command that will be executed by the eval line will set the variable $a not the variable $var. In fact, if for any reason, the value of ${var[0]} becomes 1 (as you are trying to do) the eval line will become:
$ var=(1 2)
$ echo "${var[0]}=(1 2)"
1=(1 2)
Line that if it gets evaled will trigger the error you see.
The solution depends on exactly what you are willing to get done.
If it is to change the value of variable $a, which is stored inside ${var[0]}, then, you may either use:
declare -a "${var[0]}=(1 2)"
Which, if ${var[0]} is 1, will emit this error:
bash: declare: `1=(1 2)': not a valid identifier
Which is a more meaningful message IMO.
Or, if you really want to avoid the use of eval:
#! /bin/bash
var=(a b c)
values=(1 2)
read -ra "${var[0]}" <<< "${values[#]}"
declare -p "${var[0]}"
Will print:
declare -a a=([0]="1" [1]="2")
If the $values may contain spaces or newlines (or a value that a modified IFS could contain) you will need a more complex script:
#! /bin/bash
var=(a b c)
values=("1 2" "3 4")
i=0
while IFS='' read -rd $'\0';do
declare "${var[0]}[$i]"="$REPLY" ;
((i++));
done < <( printf '%s\0' "${values[#]}")
declare -p "${var[0]}"
Or, for more recent bash versions (since bash-4.4), you could use readarray's -t option (to remove the delimiter):
#! /bin/bash
var=(a b c)
values=("1 2" "3 4")
IFS='' readarray -td $'\0' "${var[0]}" < <(printf '%s\0' "${values[#]}")
declare -p "${var[0]}"

Create array from many variables

Using bash I am trying to create an echo command with a list of variables. The number of variables can differ on what I am trying to do but I know how many I have as I count them in $ctr1. An example of what it should look like is:
echo "$var1,$var2,$var3"
etc. with the last variable the same number as the counter.
Can someone give me an idea as to what I should be doing, an example would be great. I know it could be done with if statements with a line for the possible number in the counter but that is not practical as there can be from 1 to 50+ variables in a line. I do not know if this should be an array or such like nor how to put this together. Any assistance on this would be a help.
Yes, this should be an array instead.
Instead of doing e.g.
var1=foo
var2=bar
var3=quux
ctr1=3
echo "${var1},${var2},${var3}"
you could do
var=("foo" "bar" "quux")
( IFS=,; echo "${var[*]}" )
Example:
$ cat test.sh
#!/bin/bash
# the following is equivalent to doing
# ( IFS=,; echo "$*" )
# but that wouldn't be a good example, would it?
for argument in "$#"; do
var+=( "${argument}" )
done
( IFS=,; echo "${var[*]}" )
.
$ ./test.sh foo
foo
$ ./test.sh foo bar
foo,bar
$ ./test.sh foo bar "qu ux"
foo,bar,qu ux

bash string quoted multi-word args to array

The Question:
In bash scripting, what is the best way to convert a string, containing literal quotes surrounding multiple words, into an array with the same result of parsed arguments?
The Controversy:
Many questions exist all applying evasive tactics to avoid the problem instead of finding a solution, this question raises the following arguments and would like to encourage the reader to focus on arguments and if you are up for it, partake in the challenge to find the optimum solution.
Arguments raised:
Although there are many scenarios where this pattern should be avoided, because there exists alternative solutions better suited, the author is of the opinion that valid use cases still remain. This question will attempt to produce one such use case, but make no claim to the viability thereof only that it is a conceivable scenario which may present itself in a real world situation.
You must find the optimum solution to satisfy the requirement. The use case was chosen specifically for its real world applications. You may not agree with the decisions that were made but are not tasked to give an opinion only to deliver the solution.
Satisfy the requirement without modifying the input or choice of transport. Both specifically chosen with a real world scenario to defend the narrative that those parts are out of your control.
No answers exist to the particular problem and this question aims to address that. If you are inclined to avoid this pattern then simply avoid the question but if you think you are up for the challenge lets see how you would approach the problem.
The Valid use case:
Converting an existing script currently in use to receive parameters via named pipe or similar stream. In order to minimize the impact on the myriad of scripts outside of the developers control a decision was made to not change the interface. Existing scripts must be able to pass the same arguments via the new stream implementation as they did before.
Existing implementation:
$ ./string2array arg1 arg2 arg3
args=(
[0]="arg1"
[1]="arg2"
[2]="arg3"
)
Required change:
$ echo "arg1 arg2 arg3" | ./string2array
args=(
[0]="arg1"
[1]="arg2"
[2]="arg3"
)
The problem:
As pointed out by Bash and Double-Quotes passing to argv literal quotes are not parsed as would be expected.
This workbench script can be used to test various solutions, it handles the transport and formulates a measurable response. It is suggested that you focus on the solution script which gets sourced with the string as argument and you should populate the $args variable as an array.
The string2array workbench script:
#!/usr/bin/env bash
#string2arry
args=()
function inspect() {
local inspct=$(declare -p args)
inspct=${inspct//\[/\\n\\t[}; inspct=${inspct//\'/}; inspct="${inspct:0:-1}\n)"
echo -e ${inspct#*-a }
}
while read -r; do
# source the solution to turn $REPLY in $args array
source $1 "${REPLY}"
inspect
done
Standard solution - FAILS
The solution for turning a string into a space delimited array of words worked for our first example above:
#solution1
args=($#)
Undesired result
Unfortunately the standard solution produces an undesired result for quoted multi word arguments:
$ echo 'arg1 "multi arg 2" arg3' | ./string2array solution1
args=(
[0]="arg1"
[1]="\"multi"
[2]="arg"
[3]="2\""
[4]="arg3"
)
The Challenge:
Using the workbench script provide a solution snippet that will produce the following result for the arguments received.
Desired result:
$ echo 'arg1 "multi arg 2" arg3' | ./string2array solution-xyz
args=(
[0]="arg1"
[1]="multi arg 2"
[2]="arg3"
)
The solution should be compatible with standard argument parsing in every way. The following unit test should pass for for the provided solution. If you can think of anything currently missing from the unit test please leave a comment and we can update it.
Unit test for the requirements
Update: Test simplified and includes the Johnathan Leffer test
#!/usr/bin/env bash
#test_string2array
solution=$1
function test() {
cmd="echo \"${1}\" | ./string2array $solution"
echo "$ ${cmd}"
echo ${1} | ./string2array $solution > /tmp/t
cat /tmp/t
echo -n "Result : "
[[ $(cat /tmp/t|wc -l) -eq 7 ]] && echo "PASSED!" || echo "FAILED!"
}
echo 1. Testing single args
test 'arg1 arg2 arg3 arg4 arg5'
echo
echo 2. Testing multi args \" quoted
test 'arg1 "multi arg 2" arg3 "a r g 4" arg5'
echo
echo 3 Testing multi args \' quoted
test "arg1 'multi arg 2' arg3 'a r g 4' arg5"
echo
echo 4 Johnathan Leffer test
test "He said, \"Don't do that!\" but \"they didn't listen.\""
The declare built-in seems to do what you want; in my test, it's your inspect function that doesn't seem work to properly test all inputs:
# solution3
declare -a "args=($1)"
Then
$ echo "arg1 'arg2a arg2b' arg3" | while read -r; do
> source solution3 "${REPLY}"
> for arg in "${args[#]}"; do
> echo "Arg $((++i)): $arg"
> done
> done
Arg 1: arg1
Arg 2: arg2a arg2b
Arg 3: arg3
You may do it with declare instead of eval, for example:
Instead of:
string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
echo "Initial string: $string"
eval 'for word in '$string'; do echo $word; done'
Do:
declare -a "array=($string)"
for item in "${array[#]}"; do echo "[$item]"; done
But please note, it is not much safer if input comes from user!
So, if you try it with say string like:
string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" `hostname`'
You get hostname evaluated (there off course may be something like rm -rf /)!
Very-very simple attempt to guard it just replace chars like backtrick ` and $:
string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" `hostname`'
declare -a "array=( $(echo $string | tr '`$<>' '????') )"
for item in "${array[#]}"; do echo "[$item]"; done
Now you got output like:
[aString that may haveSpaces IN IT]
[bar]
[foo]
[bamboo]
[bam boo]
[?hostname?]
More details about methods and pros about using different methods you may found in that good answer: Why should eval be avoided in Bash, and what should I use instead?
See also https://superuser.com/questions/1066455/how-to-split-a-string-with-quotes-like-command-arguments-in-bash/1186997#1186997
But there still leaved vector for attack.
I very would have in bash method of string quote like in double quotes (") but without interpreting content.
First attempt
Populate a variable with the combined words once the open quote was detected and only append to the array once the close quote arrives.
Solution
#solution2
j=''
for a in ${1}; do
if [ -n "$j" ]; then
[[ $a =~ ^(.*)[\"\']$ ]] && {
args+=("$j ${BASH_REMATCH[1]}")
j=''
} || j+=" $a"
elif [[ $a =~ ^[\"\'](.*)$ ]]; then
j=${BASH_REMATCH[1]}
else
args+=($a)
fi
done
Unit test results:
$ ./test_string2array solution2
1. Testing single args
$ echo "arg1 arg2 arg3 arg4 arg5" | ./string2array solution2
args=(
[0]="arg1"
[1]="arg2"
[2]="arg3"
[3]="arg4"
[4]="arg5"
)
Result : PASSED!
2. Testing multi args " quoted
$ echo 'arg1 "multi arg 2" arg3 "a r g 4" arg5' | ./string2array solution2
args=(
[0]="arg1"
[1]="multi arg 2"
[2]="arg3"
[3]="a r g 4"
[4]="arg5"
)
Result : PASSED!
3 Testing multi args ' quoted
$ echo "arg1 'multi arg 2' arg3 'a r g 4' arg5" | ./string2array solution2
args=(
[0]="arg1"
[1]="multi arg 2"
[2]="arg3"
[3]="a r g 4"
[4]="arg5"
)
Result : PASSED!
So I think xargs actually works for all your test cases, eg:
echo 'arg1 "multi arg 2" arg3' | xargs -0 ./string2array
Second attempt
Append the element in place without the need for an additional variable.
#solution3
for i in $1; do
[[ $i =~ ^[\"\'] ]] && args+=(' ')
lst=$(( ${#args[#]}-1 ))
[[ "${args[*]}" =~ [[:space:]]$ ]] && args[$lst]+="${i/[\"\']/} " || args+=($i)
[[ $i =~ [\"\']$ ]] && args[$lst]=${args[$lst]:1:-1}
done
Modify in place
Let bash convert the string to array and then loop through to fix it.
args=($#) cnt=${#args[#]} idx=-1 chr=
for (( i=0; i<cnt; i++ )); do
[[ $idx -lt 0 ]] && {
[[ ${args[$i]:0:1} =~ [\'\"] ]] && \
idx=$i chr=${args[$idx]:0:1} args[$idx]="${args[$idx]:1}"
continue
}
args[$idx]+=" ${args[$i]}"
unset args[$i]
[[ ${args[$idx]: -1:1} == $chr ]] && args[$idx]=${args[$idx]:0:-1} idx=-1
done
Modify the delimiter
In this solution we turn the spaces into commas, remove the quotes and reset the spaces for the multi word arguments, to allow for the correct argument parsing.
#solution4
s=${*//[[:space:]]/\l}
while [[ $s =~ [\"\']([^\"\']*)[\"\'] ]]; do
s=${s/$BASH_REMATCH/${BASH_REMATCH[1]//\l/ }}
done
IFS=\l
args=(${s})
NEEDS WORK!!

How to pass an array argument to the Bash script

It is surprising me that I do not find the answer after 1 hour search for this.
I would like to pass an array to my script like this:
test.sh argument1 array argument2
I DO NOT want to put this in another bash script like following:
array=(a b c)
for i in "${array[#]}"
do
test.sh argument1 $i argument2
done
Bash arrays are not "first class values" -- you can't pass them around like one "thing".
Assuming test.sh is a bash script, I would do
#!/bin/bash
arg1=$1; shift
array=( "$#" )
last_idx=$(( ${#array[#]} - 1 ))
arg2=${array[$last_idx]}
unset array[$last_idx]
echo "arg1=$arg1"
echo "arg2=$arg2"
echo "array contains:"
printf "%s\n" "${array[#]}"
And invoke it like
test.sh argument1 "${array[#]}" argument2
Have your script arrArg.sh like this:
#!/bin/bash
arg1="$1"
arg2=("${!2}")
arg3="$3"
arg4=("${!4}")
echo "arg1=$arg1"
echo "arg2 array=${arg2[#]}"
echo "arg2 #elem=${#arg2[#]}"
echo "arg3=$arg3"
echo "arg4 array=${arg4[#]}"
echo "arg4 #elem=${#arg4[#]}"
Now setup your arrays like this in a shell:
arr=(ab 'x y' 123)
arr2=(a1 'a a' bb cc 'it is one')
And pass arguments like this:
. ./arrArg.sh "foo" "arr[#]" "bar" "arr2[#]"
Above script will print:
arg1=foo
arg2 array=ab x y 123
arg2 #elem=3
arg3=bar
arg4 array=a1 a a bb cc it is one
arg4 #elem=5
Note: It might appear weird that I am executing script using . ./script syntax. Note that this is for executing commands of the script in the current shell environment.
Q. Why current shell environment and why not a sub shell?
A. Because bash doesn't export array variables to child processes as documented here by bash author himself
If values have spaces (and as a general rule) I vote for glenn jackman's answer, but I'd simplify it by passing the array as the last argument. After all, it seems that you can't have multiple array arguments, unless you do some complicated logic to detect their boundaries.
So I'd do:
ARRAY=("the first" "the second" "the third")
test.sh argument1 argument2 "${ARRAY[#]}"
which is the same as:
test.sh argument1 argument2 "the first" "the second" "the third"
And in test.sh do:
ARG1="$1"; shift
ARG2="$1"; shift
ARRAY=("$#")
If values have no spaces (i.e. they are urls, identifiers, numbers, etc) here's a simpler alternative. This way you can actually have multiple array arguments and it's easy to mix them with normal arguments:
ARRAY1=(one two three)
ARRAY2=(four five)
test.sh argument1 "${ARRAY1[*]}" argument3 "${ARRAY2[*]}"
which is the same as:
test.sh argument1 "one two three" argument3 "four five"
And in test.sh you do:
ARG1="$1"
ARRAY1=($2) # Note we don't use quotes now
ARG3="$3"
ARRAY2=($4)
I hope this helps. I wrote this to help (you and me) understand how arrays work, and how * an # work with arrays.
Passing array as argument to script by using alternate separator
And passing array as argument to remote script by using alternate separator
Practical sample:
Choosing bell character: ascii 0x07 used by C sequence: \a.
Little preparation: Having a function to prepare variables:
mergeArray() {
local IFS=$'\a'
local -n src=$1 tgt=$2
tgt=${src[*]}
}
Then the script could look like:
#!/bin/bash
arg1="$1"
IFS=$'\a' read -ra arg2 <<<"$2"
arg3="$3"
declare -p arg{1,2,3}
In practice:
var1=Hello var2=(foo bar baz) var3=world.
mergeArray var2 mvar2
bash script "$var1" "$mvar2" "$var3"
must output:
declare -- arg1="Hello"
declare -a arg2=([0]="foo" [1]="bar" [2]="baz")
declare -- arg3="world."
Explanations
by using local IFS= I ensure IFS varialbe won't be modified in environment
local -n is a nameref for binding variables.
${src[*]} will merge all elements of array to one string by using 1st character of $IFS
Of course, I use \a for this, this could replaced (on both side) by any other single byte non null character (ascii) from \1 to \377, while choosed character is not used in your array.
Installation and remote execution
The function mergeArray could be installed into your .bashrc file or into a separated file to be sourced before you run your script.
Once done, the script himself could even be located remotely. then run by same command:
ssh user#remote /path/to/script "$var1" "$mvar2" "$var3"
But for more robust remote execution I prefer:
myArray=("foo bar baz" 'alice bob charlie' 'strawberry raspberry')
mergeArray myArray mergedArray
var1="Hello world!"
ssh user#remote /bin/bash -s <<eoRemote
/path/to/script "$var1" "$mergedArray" "This seem nice, isnt't it?"
eoRemote
From my remote host, I will read:
declare -- arg1="Hello world!"
declare -a arg2=([0]="foo bar baz" [1]="alice bob charlie" [2]="strawberry raspberry")
declare -- arg3="This seem nice, isnt't it?"
( Notice the mixing of single quotes and double quotes!! ;-)
Passing Associative arrays as argument to remote bash via ssh
Things become a little stronger!
mergeArray () {
local -n _srce=${1?Source var missing.} _trgt=${2?Target var missing.}
local _tvar
IFS=\ read _ _tvar _ <<< "${_srce#A}"
case ${_tvar#-} in
*a*) local IFS=$'\a'; _trgt=${_srce[*]} ;;
*A*) _trgt=''
for _tvar in "${!_srce[#]}" ;do
printf -v _tvar '%s\a%s\a' "$_tvar" "${_srce[$_tvar]}"
_trgt+="$_tvar"
done
_trgt=${_trgt%$'\a'} ;;
* ) printf >&2 '%s ERROR: Variable "%s" is not an array.\n' \
$FUNCNAME "$1"
return 1 ;;
esac
}
Then parsing args in script will become:
#!/bin/bash
arg1=${1}
IFS=$'\a' read -ra arg2 <<<"$2"
IFS=$'\a' read -ra _tmpvar <<<"$3"
printf -v _tmpvar '[%s]="%s" ' "${_tmpvar[#]//\"/\\\"}"
declare -A arg3="($_tmpvar)"
arg4=$4
declare -p arg{1,2,3,4}
In action:
var1="Hello world!"
myArray=("foo bar baz" 'alice bob charlie' 'strawberry raspberry')
declare -A myAArray='([Full name]="John Doo" [Birth date]="1970/01/02 12:34:56"
[Status]="Maried" [Title]="Chief")'
mergeArray myArray mergedArray
mergeArray myAArray mergedAArray
ssh user#remote /bin/bash -s <<eoRemote
/path/to/script "$var1" "$mergedArray" "$mergedAArray" "Still seem nice, isn't it?"
eoRemote
declare -- arg1="Hello world!"
declare -a arg2=([0]="foo bar baz" [1]="alice bob charlie" [2]="strawberry raspberry")
declare -A arg3=(["Birth date"]="1970/01/02 12:34:56" [Title]="Chief" [Status]="Maried" ["Full name"]="John Doo" )
declare -- arg4="Sill seem nice, isn't it?"
Passing complex variables as arguments over ssh
And for holding double-quotes in addition to already supported single-guotes, spaces and others,
remplace $varNAme by ${varName//\"/\\\"}:
var1="Hello world!"
myArray=("foo bar baz" 'alice bob charlie' 'strawberry raspberry')
declare -A myAArray='([Full name]="John Doo" [Birth date]="1970/01/02 12:34:56"
[Status]="Maried")'
myAArray[Title]="Chief's pain sufferer"
myAArray[datas]='{ "height": "5.5 feet", "weight":"142 pounds",'
myAArray[datas]+=$' "phrase": "Let\'s go!" }'
mergeArray myArray mergedArray
mergeArray myAArray mergedAArray
ssh user#remote /bin/bash -s <<eoRemote
/path/to/script "${var1//\"/\\\"}" "${mergedArray//\"/\\\"}" \
"${mergedAArray//\"/\\\"}" "This still seem nice, isn't it?"
eoRemote
declare -- arg1="Hello world!"
declare -a arg2=([0]="foo bar baz" [1]="alice bob charlie" [2]="strawberry raspberry")
declare -A arg3=([Title]="Chief's pain sufferer" [Status]="Maried" ["Birth date"]="1970/01/02 12:34:56" ["Full name"]="John Doo" [datas]="{ \"height\": \"5.5 feet\", \"weight\":\"142 pounds\", \"phrase\": \"Let's go!\" }" )
declare -- arg4="This still seem nice, isn't it?"
Or after some foldering:
declare -- arg1="Hello world!"
declare -a arg2=([0]="foo bar baz" [1]="alice bob charlie"
[2]="strawberry raspberry" )
declare -A arg3=([Title]="Chief's pain sufferer" [Status]="Maried"
["Birth date"]="1970/01/02 12:34:56" ["Full name"]="John Doo"
[datas]="{ \"height\": \"5.5 feet\", \"weight\":\"142 pounds\",
\"phrase\": \"Let's go!\" }" )
declare -- arg4="This still seem nice, isn't it?"
( By adding: jq <<<${arg3[datas]} at end of my script, I see:
{
"height": "5.5 feet",
"weight": "142 pounds",
"phrase": "Let's go!"
}
:-)
if the length of your array is not too long, you can convert the array to a string which joining with ','
file1.sh
s=${1}
arr=(`echo ${s} | tr ',' ' '`) # then we can convert str to arr
file2.sh
a="1,2,3,4"
sh file1.sh ${a}
You can write your array to a file, then source the file in your script.
e.g.:
array.sh
array=(a b c)
test.sh
source $2
...
Run the test.sh script:
./test.sh argument1 array.sh argument3
The best solution that I'm found here
f() {
declare -a argAry1=("${!1}")
echo "${argAry1[#]}"
# ...
}
arr1=(
"a"
"b"
)
f arr1[#] arr2[#] arg2
If this is your command:
test.sh argument1 ${array[*]} argument2
You can read the array into test.sh like this:
arg1=$1
arg2=${2[*]}
arg3=$3
It will complain at you ("bad substitution"), but will work.

Returning array from a Bash function

I am making a bash script and I have encountered a problem. So let's say I got this
function create_some_array(){
for i in 0 1 2 3 .. 10
do
a[i]=$i
done
}
create_some_array
echo ${a[*]}
Is there any way I can make this work? I have searched quite a lot and nothing I found worked.
I think making the a[] a global variable should work but I can't find something that actually works in my code. Is there any way to return the array from the function to main program?
Thanks in advance
This won't work as expected when there are whitespaces in the arrays:
function create_some_array() {
local -a a=()
for i in $(seq $1 $2); do
a[i]="$i $[$i*$i]"
done
echo ${a[#]}
}
and worse: if you try to get array indices from the outside "a", it turns out to be a scalar:
echo ${!a[#]}
even assignment as an array wont help, as possible quoting is naturally removed by the echo line and evaluation order cannot be manipulated to escape quoting: try
function create_some_array() {
...
echo "${a[#]}"
}
a=($(create_some_array 0 10))
echo ${!a[#]}
Still, printf seems not to help either:
function create_some_array() {
...
printf " \"%s\"" "${a[#]}"
}
seems to produce correct output on one hand:
$ create_some_array 0 3; echo
"0 0" "1 1" "2 4" "3 9"
but assignment doesn't work on the other:
$ b=($(create_some_array 0 3))
$ echo ${!b[#]}
0 1 2 3 4 5 6 7
So my last trick was to do assignment as follows:
$ eval b=("$(create_some_array 0 3)")
$ echo -e "${!b[#]}\n${b[3]}"
0 1 2 3
3 9
Tataaa!
P.S.: printf "%q " "${a[#]}" also works fine...
This works fine as described. The most likely reason it doesn't work in your actual code is because you happen to run it in a subshell:
cat textfile | create_some_array
echo ${a[*]}
would not work, because each element in a pipeline runs in a subshell, and
myvalue=$(create_some_array)
echo ${a[*]}
would not work, since command expansion happens in a subshell.
You can make an array local to a function, and then return it:
function create_some_array(){
local -a a=()
for i in $(seq $1 $2); do
a[i]=$i
done
echo ${a[#]}
}
declare -a a=()
a=$(create_some_array 0 10)
for i in ${a[#]}; do
echo "i = " $i
done
Hi here is my solution:
show(){
local array=()
array+=("hi")
array+=("everything")
array+=("well?")
echo "${array[#]}"
}
for e in $(show);do
echo $e
done
Try this code on: https://www.tutorialspoint.com/execute_bash_online.php
Both these work for me with sh and bash:
arr1=("192.168.3.4" "192.168.3.4" "192.168.3.3")
strArr=$(removeDupes arr1) # strArr is a string
for item in $strArr; do arr2+=("$item"); done # convert it to an array
len2=${#arr2[#]} # get array length
echo "${len2}" # echo length
eval arr3=("$(removeDupes arr1)") # shellcheck does not like this line and won't suppress it but it works
len3=${#arr3[#]} # get array length
echo "${len3}" # echo length
As an aside, the removeDupes function looks like this:
removeDupes() {
arg="$1[#]"
arr=("${!arg}")
len=${#arr[#]}
resultArr=()
# some array manipulation here
echo "${resultArr[#]}"
}
This answer is based on but better explains and simplifies the answers from #Hans and #didierc

Resources