Returning array from a Bash function - arrays

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

Related

bash array substitution broken on for loop

I have this piece of bash code in a script:
[..]
ARRAY=("foo-2" "foo-3" "foo-4")
IGNORE=2
while sleep 2
do
for ii in "${ARRAY[#]/foo-${IGNORE}/}"
do
echo $ii
done
[..]
done
This piece of code on its own works. But in my full script a weird thing happens:
First loop of sleep runs well. 2nd iteration however, uses the ${ARRAY[#]/foo-${IGNORE}/} as a literal string. Making a single echo command in the for loop.
Debugging the for loop:
Works:
for module in ${ARRAY[#]}
Also works:
for module in $(echo ${ARRAY[#]} | xargs -n 1 | grep -v foo-${IGNORE})
What do you think might be the problem? Is it some sort of bash limitation? Is there any other way you can suggest I do this?
Thanks!
"${ARRAY[#]/foo-${IGNORE}/}" should expand to multiple elements but one of them would just be an empty string and not completely ignored.
See this:
# A=(1 2 3)
# printf '|%s|\n' "${A[#]//1/}"
||
|2|
|3|
It also sounds like your array doesn't expand at all. Probably cause of it is that your shell is not being run by Bash.
Anyway better just compare your element instead:
for ii in "${ARRAY[#]}"
do
[[ $ii == "foo-$IGNORE" ]] && continue
echo $ii
done
You can also use globbing:
for ii in "${ARRAY[#]}"
do
[[ $ii == *"foo-$IGNORE"* ]] && continue
echo $ii
done

shell adding string to an array

I am having some trouble adding a string to an array within a loop. For some reason it always adds the same line. Here is my code:
declare -a properties
counter=0
while read line
do
if [[ ${line} == *=* ]]
then
properties[${counter}]=${line}
(( counter=counter + 1 ))
fi
done < ${FILE}
for x in ${!properties[#]}
do
echo "the value is $properties[x]"
done
For some reason each element in the array is the first line in the file. I must be doing something wrong, just not sure what.
Any help would be greatly appreciated
Try this script:
declare -a properties
while read line
do
[[ "${line}" == *=* ]] && properties+=("$line")
done < "${FILE}"
for x in "${properties[#]}"
do
echo "the value is "$x"
done
As #twalberg mentions in this comment, the problem is not in the top loop, but in the bottom one:
for x in ${!properties[#]}
do
echo "the value is $properties[x]"
done
Array references always need braces { ... } to expand properly.
For some reason each element in the array is the first line in the
file.
Not so. The array is correctly populated, but you need to change the reference to the array from:
echo "the value is $properties[x]"
to:
echo "the value is ${properties[x]}"
Just a simple oversight.
A much simpler way to add an element to an array is to simply use the syntax:
VARNAME+=("content")
Also, as written, your bug may be here:
(( counter=counter + 1 ))
It probably should be one of these three:
(( counter=$counter + 1 ))
counter+=1
counter=$[$counter+1]
counter=$(($counter + 1))
This KornShell (ksh) script worked fine for me. Let me know if anything.
readFileArrayExample.ksh
#! /usr/bin/ksh
file=input.txt
typeset -i counter=0
while read line
do
if [[ ${line} == *=* ]]; then
properties[${counter}]="${line}"
((counter = counter + 1))
echo "counter:${counter}"
fi
done < "${file}"
#echo ${properties[*]}
for x in "${properties[#]}"
do
echo "${x}"
done
readFileArrayExample.ksh Output:
#:/tmp #ksh readFileArrayExample.ksh
counter:1
counter:2
counter:3
a=b
a=1
b=1
#:/tmp #
input.txt
a-b
a+b
a=b
a=1
b=1
1-a

Splitting line of text into array at bash

I have a bunch of files named jtn216_<n>.o<m> where n and m are integer. The first one is assigned by me and the second one by the system. I need to check the last line at each file. I ran this to split that line into array
for i in {361..380}; do
v=$(tail -n 1 jtn216_$i.o*)
IFS=' ' read -ra line <<< "$v"
echo $line $v
done
3499200 3499200 87650.5574975270 13.6931802555886
1014400 1014400 87947.4382620423 13.9208064005841
3475800 3475800 87779.1695691355 13.8939964916376
3479200 3479200 87459.7284508034 13.7824644675699
3827800 3827800 87868.7538056652 13.8792123626210
2551600 2551600 87615.6417285010 13.8700006744178
3818400 3818400 87872.1788028955 13.8942371285402
3476800 3476800 87842.0543708163 13.9170342642747
3481800 3481800 87670.5841054385 13.8808556469308
2559200 2559200 87800.6530231416 13.8874423695824
3841600 3841600 87804.3972028423 13.8657419719638
916400 916400 87776.1342228681 13.8622746230494
3839000 3839000 87662.8185016707 13.8576498806465
3835200 3835200 87933.6917697832 14.0007327053153
3482000 3482000 88323.3509854563 13.9453990979062
3485400 3485400 87657.5078357100 13.8478805156354
3484800 3484800 87757.3379321554 13.8215034461609
3475400 3475400 87970.4729449120 13.9605031841208
3481800 3481800 87612.4211302676 13.8327950845915
2319400 2319400 87521.5669854330 13.8383953325475
I expected line to be an array not the first value from v. What am I doing wrong?
I think you're just printing line wrongly. Try echo "${line[#]}" instead.
You should address the contents of the array variable $line well e.g.
echo "${line[0]}"
echo "${line[*]}" # converts to a single string
echo "${line[#]}" # converts to multiple elements i.e. multiple arguments for echo
When an array variable is addressed without an index it would be just equivalent to getting the first element.
What bash calls "arrays" are really just a list of words (a list that can't even be nested). So if you simply output the array using echo, you're just asking it to output a list of arguments to echo. Consider:
echo foo bar # 'foo bar\n'
echo -n foo bar # 'foo bar'
x=( -n foo bar )
echo "${x[#]}" # 'foo bar'
If you output an array's name without any index or # or * component, you will get the first word* in that array:
echo "$x" # foo
So when you do echo $line $v what you're really saying is
echo ${line[0]} $v
Here's what you probably want:
for i in {361..380}; do
tail -n 1 jtn216_$i.o* | { # Pipe to a compound command to preserve assignments from read
IFS=' ' read -ra line
printf '[ '
printf '%s ' "${line[#]}"
printf ']\n'
}
done
Then you should see
[ 3499200 87650.5574975270 13.6931802555886 ]
[ 1014400 87947.4382620423 13.9208064005841 ]
[ 3475800 87779.1695691355 13.8939964916376 ]
…
*Wordsplit again since you didn't use quotes.
Try the following, if you want to get last line as array:
v=(`tail -n 1 jtn216_$i.o*`)
UPDATED
To accumulate line as array use:
v=(`tail -n 1 jtn216_$i.o*`)
line+=(${v[#]})
To output whole array:
${line[#]} or ${line[*]}
To count number of array elements:
${#line[#]} or ${#line[*]}

How to find the last element in an array in unix?

How can i find the last element in an array in unix? I need find the last element in an array to do an if-statement:
if [ #last_array ];
then
#Do something
fi
How can i do it? Can i put only one parameter in the if? I want only the last array to do something
I think that something like that could make it. It is not very nice, I know, but cannot think in other good ways:
#!/bin/bash
a=("hello" "bye" "another" "word")
i=0
num_words=${#a[#]}
echo "there are $num_words words"
for word in "${a[#]}"
do
let i=i+1
echo $i $word
if [ $i -eq $num_words ]; then
echo "last word!"
fi
done
Test
$ ./test
there are 4 words
1 hello
2 bye
3 another
4 word
last word!
$ set -A arr 1 2 3 4 5
$ let LAST_ELEMENT=${#arr[*]}-1
$ echo ${arr[$LAST_ELEMENT]}

BASH: Best way to set variable from array

Bash 4 on Linux ~
I have an array of possible values. I must restrict user input to these values.
Arr=(hello kitty goodbye quick fox)
User supplies value as argument to script:
bash myscript.sh -b var
Currently, I'm trying the following:
function func_exists () {
_var="$1"
for i in ${Arr[#]}
do
if [ "$i" == "$_var" ]
then
echo hooray for "$_var"
return 1
fi
done
return 0
}
func_exists $var
if [ $? -ne 1 ];then
echo "Not a permitted value."
func_help
exit $E_OPTERROR
fi
Seems to work fine, are there better methods for testing user input against an array of allowed values?
UPDATE: I like John K's answer ...can someone clarify the use of $#? I understand that this represents all positional parameters -- so we shift the first param off the stack and $# now represents all remaining params, those being the passed array ...is that correct? I hate blindly using code without understanding ...even if it works!
Your solution is what I'd do. Maybe using a few more shell-isms, such as returning 0 for success and non-0 for failure like UNIX commands do in general.
# Tests if $1 is in the array ($2 $3 $4 ...).
is_in() {
value=$1
shift
for i in "$#"; do
[[ $i == $value ]] && return 0
done
return 1
}
if ! is_in "$var" "${Arr[#]}"; then
echo "Not a permitted value." >&2
func_help
exit $E_OPTERROR
fi
Careful use of double quotes makes sure this will work even if the individual array entries contain spaces, which is allowed. This is a two element array: list=('hello world' 'foo bar').
Another solution. is_in is just a variable:
Arr=(hello kitty goodbye quick fox)
var='quick'
string=" ${Arr[*]} " # array to string, framed with blanks
is_in=1 # false
# try to delete the variable inside the string; true if length differ
[ "$string" != "${string/ ${var} /}" ] && is_in=0
echo -e "$is_in"
function func_exists () {
case "$1"
in
hello)
kitty)
goodbye)
quick)
fox)
return 1;;
*)
return 0;;
esac
}

Resources