Can't empty a bash array (bash --version 3.2.25) - arrays

My bash array never empty itself.
I am using bash 3.2.25.
I tried using the folowing methods:
declare -a array
# fill array...
# 1
array=()
# 2
empty_array=()
array=( "${empty_array[#]}" )
# 3
unset array
My array never get emptied, am I doing something wrong?
Full code as requested :
declare -a array
function get_array() {
#active_tills=()
#unset active_tills
#active_tills=( "${active_tills[#]}" )
# fill array
while read -r line || [[ -n "$line" ]]; do
line=$(echo "$line" | cut -d' ' -f1)
if [ -n "$line" ] ; then
to_add+="$line "
fi
done < "$request_tmp"
array=($(echo $to_add))
return 0
}
Then
get_array
for host in "${array[#]}"; do
echo "=> $host"
done
# 1
# 2
# 3
get_array
for host in "${array[#]}"; do
echo "=> $host"
done
# 1
# 2
# 3
# 1
# 2
# 3

to_add is also a global variable, and you don't reset its value before appending to it. However, you don't need it: you can append directly to the array.
declare -a array
function get_array() {
local line rest
array=()
while read -r line rest || [[ -n "$line" ]]; do
if [ -n "$line" ] ; then
array+=("$line")
fi
done < "$request_tmp"
return 0
}
As an aside, if you can guarantee that the input file ends with a newline (as is required of a proper text file in POSIX), you don't need the || [[ -n $line ]] hack in your while loop.

Related

issues looping over array in bash

I have written a very simple port scanning script in bash. For my ports argument, i accept a single port (eg: 443), a comma-separated list (eg: 80,443), or a range (eg: 1-1000).
When I run my script with a single port or comma-separated list of ports, everything runs as it should:
~/projects/bash-port-scan# ./bash-port-scan.sh -i xx.xx.xxx.xxx -p 1,80,443 -v
Beginning scan of xx.xx.xxx.xxx
Port 1 closed
Port 80 open
Port 443 open
Scan complete.
~/projects/bash-port-scan# ./bash-port-scan.sh -i xx.xx.xxx.xxx -p 80 -v
Beginning scan of xx.xx.xxx.xxx
Port 80 open
Scan complete.
However, when I run with a range, I get:
~/projects/bash-port-scan# ./bash-port-scan.sh -i xx.xx.xxx.xxx -p 1-10 -v
Beginning scan of xx.xx.xxx.xxx
Port 1
2
3
4
5
6
7
8
9
10 closed
Scan complete.
Relevant code where I assign the array:
portarray=()
if [[ "$ports" == *","* ]]; then
IFS=','
read -r -a portarray <<< $ports
IFS=' '
elif [[ "$ports" == *"-"* ]]; then
IFS='-'
read -r -a range <<< $ports
IFS=' '
first="${range[0]}"
last="${range[1]}"
portarray=($(seq $first 1 $last))
else
portarray=($ports)
fi
and the loop itself:
empty=""
for p in "${portarray[#]}"; do
result=$(nc -zvw5 $ip $p 2>&1 | grep open)
if [ "$result" = "$empty" ]; then
if [ $verbose -eq 1 ]; then
str="Port "
closed=" closed"
echo "$str$p$closed"
fi
else
str="Port "
closed=" open"
echo "$str$p$closed"
fi
done
I'm not sure if this is because of how I'm assigning my port array, or if it is because of something I have wrong in my loop. I'm relatively new to bash scripting, and I'm having a terrible time figuring out what I have wrong.
I've read here on SO about some commands run in loops eating the output of other portions of the script, but I don't believe that to be the case here, as the script does actually print to screen, just not as expected.
EDIT:
Here is the full script:
#!/bin/bash
verbose=0
while [ "$1" != "" ]; do
case "$1" in
-h | --help )
echo "bash-port-scan.sh v0.1\r\nUsage: ./bash-port-scan.sh -i 127.0.0.1 -p 80,443\r\n./bash-port-scan.sh -i 127.0.0.1 -p 1-1000"; shift;;
-v | --verbose )
verbose=1; shift;;
-i | --ip )
ip="$2"; shift;;
-p | --ports )
ports="$2"; shift;;
esac
shift
done
if [[ $ip = "" ]]; then
echo "Please enter an IP address with -i"
exit
fi
if [[ $ports = "" ]]; then
echo "Please enter the port(s) with -p"
exit
fi
portarray=()
if [[ "$ports" == *","* ]]; then
IFS=','
read -r -a portarray <<< $ports
IFS=' '
elif [[ "$ports" == *"-"* ]]; then
IFS='-'
read -r -a range <<< $ports
IFS=' '
first="${range[0]}"
last="${range[1]}"
portarray=($(seq $first $last))
else
portarray=($ports)
fi
if [ $verbose -eq 1 ]; then
echo "Beginning scan of $ip"
fi
shuf -e "${portarray[#]}"
empty=""
for p in "${portarray[#]}"; do
result=$(nc -zvw5 $ip $p 2>&1 | grep open)
if [ "$result" = "$empty" ]; then
if [ $verbose -eq 1 ]; then
str="Port "
closed=" closed"
echo "$str$p$closed"
fi
else
str="Port "
closed=" open"
echo "$str$p$closed"
fi
done
echo "Scan complete."
Addressing just the portarray=(...) assignment (when ports=1-10)
Consider:
$ first=1
$ last=10
$ portarray=($(seq $first 1 $last))
$ typeset -p portarray
declare -a portarray=([0]=$'1\n2\n3\n4\n5\n6\n7\n8\n9\n10')
NOTE: the output from the $(seq ...) call is processed as a single string with embedded linefeeds.
A couple ideas:
### define \n as field separator; apply custom IFS in same line to limit IFS change to just the follow-on array assignment:
$ IFS=$'\n' portarray=($(seq $first 1 $last))
$ typeset -p portarray
declare -a portarray=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10")
### use mapfile to read each *line* into a separate array entry:
$ mapfile -t portarray < <(seq $first 1 $last)
$ typeset -p portarray
declare -a portarray=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10")

How to create mutiple arrays from a text file and loop through the values of each array

I have a text file with the following:
Paige
Buckley
Govan
Mayer
King
Harrison
Atkins
Reinhardt
Wilson
Vaughan
Sergovia
Tarrega
My goal is to create an array for each set of names. Then Iterate through the first array of values then move on to the second array of values and lastly the third array. Each set is separated by a new line in the text file. Help with code or logic is much appreciated!
so far I have the following. i am unsure of the logic moving forward when i reach a line break. My research here also suggests that i can use readarray -d.
#!/bin/bash
my_array=()
while IFS= read -r line || [[ "$line" ]]; do
if [[ $line -eq "" ]];
.
.
.
arr+=("$line") # i know this adds the value to the array
done < "$1"
printf '%s\n' "${my_array[#]}"
desired output:
array1 = (Paige Buckley6 Govan Mayer King)
array2 = (Harrison Atkins Reinhardt Wilson)
array3 = (Vaughan Sergovia Terrega)
#then loop through the each array one after the other.
Bash has no array-of-arrays. So you have to represent it in an other way.
You could leave the newlines and have an array of newline separated elements:
array=()
elem=""
while IFS= read -r line; do
if [[ "$line" != "" ]]; then
elem+="${elem:+$'\n'}$line" # accumulate lines in elem
else
array+=("$elem") # flush elem as array element
elem=""
fi
done
if [[ -n "$elem" ]]; then
array+=("$elem") # flush the last elem
fi
# iterate over array
for ((i=0;i<${#array[#]};++i)); do
# each array element is newline separated items
readarray -t elem <<<"${array[i]}"
printf 'array%d = (%s)\n' "$i" "${elem[*]}"
done
You could simplify the loop with some unique character and a sed for example like:
readarray -d '#' -t array < <(sed -z 's/\n\n/#/g' file)
But overall, this awk generates same output:
awk -v RS= -v FS='\n' '{
printf "array%d = (", NR;
for (i=1;i<=NF;++i) printf "%s%s", $i, i==NF?"":" ";
printf ")\n"
}'
Using nameref :
#!/usr/bin/env bash
declare -a array1 array2 array3
declare -n array=array$((n=1))
while IFS= read -r line; do
test "$line" = "" && declare -n array=array$((n=n+1)) || array+=("$line")
done < "$1"
declare -p array1 array2 array3
Called with :
bash test.sh data
# result
declare -a array1=([0]="Paige" [1]="Buckley" [2]="Govan" [3]="Mayer" [4]="King")
declare -a array2=([0]="Harrison" [1]="Atkins" [2]="Reinhardt" [3]="Wilson")
declare -a array3=([0]="Vaughan" [1]="Sergovia" [2]="Tarrega")
Assumptions:
blank links are truly blank (ie, no need to worry about any white space on said lines)
could have consecutive blank lines
names could have embedded white space
the number of groups could vary and won't always be 3 (as with the sample data provided in the question)
OP is open to using a (simulated) 2-dimensional array as opposed to a (variable) number of 1-dimensional arrays
My data file:
$ cat names.dat
<<< leading blank lines
Paige
Buckley
Govan
Mayer
King Kong
<<< consecutive blank lines
Harrison
Atkins
Reinhardt
Wilson
Larry
Moe
Curly
Shemp
Vaughan
Sergovia
Tarrega
<<< trailing blank lines
One idea that uses a couple arrays:
array #1: associative array - the previously mentioned (simulated) 2-dimensional array with the index - [x,y] - where x is a unique identifier for a group of names and y is a unique identifier for a name within a group
array #2: 1-dimensional array to keep track of max(y) for each group x
Loading the arrays:
unset names max_y # make sure array names are not already in use
declare -A names # declare associative array
x=1 # init group counter
y=0 # init name counter
max_y=() # initialize the max(y) array
inc= # clear increment flag
while read -r name
do
if [[ "${name}" = '' ]] # if we found a blank line ...
then
[[ "${y}" -eq 0 ]] && # if this is a leading blank line then ...
continue # ignore and skip to the next line
inc=y # set flag to increment 'x'
else
[[ "${inc}" = 'y' ]] && # if increment flag is set ...
max_y[${x}]="${y}" && # make note of max(y) for this 'x'
((x++)) && # increment 'x' (group counter)
y=0 && # reset 'y'
inc= # clear increment flag
((y++)) # increment 'y' (name counter)
names[${x},${y}]="${name}" # save the name
fi
done < names.dat
max_y[${x}]="${y}" # make note of the last max(y) value
Contents of the array:
$ typeset -p names
declare -A names=([1,5]="King Kong" [1,4]="Mayer" [1,1]="Paige" [1,3]="Govan" [1,2]="Buckley" [3,4]="Shemp" [3,3]="Curly" [3,2]="Moe" [3,1]="Larry" [2,4]="Wilson" [2,2]="Atkins" [2,3]="Reinhardt" [2,1]="Harrison" [4,1]="Vaughan" [4,2]="Sergovia" [4,3]="Tarrega" )
$ for (( i=1; i<=${x}; i++ ))
do
for (( j=1; j<=${max_y[${i}]}; j++ ))
do
echo "names[${i},${j}] : ${names[${i},${j}]}"
done
echo ""
done
names[1,1] : Paige
names[1,2] : Buckley
names[1,3] : Govan
names[1,4] : Mayer
names[1,5] : King Kong
names[2,1] : Harrison
names[2,2] : Atkins
names[2,3] : Reinhardt
names[2,4] : Wilson
names[3,1] : Larry
names[3,2] : Moe
names[3,3] : Curly
names[3,4] : Shemp
names[4,1] : Vaughan
names[4,2] : Sergovia
names[4,3] : Tarrega

Fetching data into an array

I have a file like this below:
-bash-4.2$ cat a1.txt
0 10.95.187.87 5444 up 0.333333 primary 0 false 0
1 10.95.187.88 5444 up 0.333333 standby 1 true 0
2 10.95.187.89 5444 up 0.333333 standby 0 false 0
I want to fetch the data from the above file into a 2D array.
Can you please help me with a suitable way to put into an array.
Also post putting we need put a condition to check whether the value in the 4th column is UP or DOWN. If it's UP then OK, if its down then below command needs to be executed.
-bash-4.2$ pcp_attach_node -w -U pcpuser -h localhost -p 9898 0
(The value at the end is getting fetched from the 1st column.
You could try something like that:
while read -r line; do
declare -a array=( $line ) # use IFS
echo "${array[0]}"
echo "${array[1]}" # and so on
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
Or:
while read -r -a array; do
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
This works only if field are space separated (any kind of space).
You could probably mix that with regexp if you need more precise control of the format.
Firstly, I don't think you can have 2D arrays in bash. But you can however store lines into a 1-D array.
Here is a script ,parse1a.sh, to demonstrate emulation of 2D arrays for the type of data you included:
#!/bin/bash
function get_element () {
line=${ARRAY[$1]}
echo $line | awk "{print \$$(($2+1))}" #+1 since awk is one-based
}
function set_element () {
line=${ARRAY[$1]}
declare -a SUBARRAY=($line)
SUBARRAY[$(($2))]=$3
ARRAY[$1]="${SUBARRAY[#]}"
}
ARRAY=()
while IFS='' read -r line || [[ -n "$line" ]]; do
#echo $line
ARRAY+=("$line")
done < "$1"
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
for line in "${ARRAY[#]}"; do
#echo $line
if [ "$(echo $line | awk '{print $4}')" == "down" ]; then
echo "Replace this with what to do for down"
else
echo "...and any action for up - if required"
fi
done
echo "Element access of [2,3]:"
echo "get_element 2 3 : "
get_element 2 3
echo "set_element 2 3 left: "
set_element 2 3 left
echo "get_element 2 3 : "
get_element 2 3
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
It can be executed by:
./parsea1 a1.txt
Hope this is close to what you are looking for. Note that this code will loose all indenting spaces during manipulation, but a formatted update of the lines could solve that.

bash sort array trouble

My first sort statement works great but for some reason my second sort statement doesn't work and doesn't throw any errors. I can't spot the trouble. Please suggest ways I can make this script better. I know its really messy.
#!/bin/bash
testarr0=(37 32 11 31 41 10)
#target ticket
testarr1=[];
#sort target ticket array
IFS=$'\n' testarr00=($(sort <<<"${testarr0[*]}"))
unset IFS
cou0=0
input="test_tickets0"
while IFS= read -r ticket0
do
IFS=',' read -ra digit0 <<< "$ticket0"
for i in "${digit0[#]}"; do
remv0="${i//]}"
remv1="${remv0//[}"
if [ $cou0 -eq 6 ];then
cou0=0
#sort array
IFS=$'\n' testarr11=($(sort <<<"${testarr1[*]}"))
unset IFS
#compare arrays
echo "${testarr00[#]}"
echo "${testarr11[#]}"
if [[ "[]${testarr00[#]}" == "${testarr11[#]}" ]];then
echo "match"
fi
testarr1=[]
fi
cou0=$((cou0+1))
if [ $cou0 -lt 7 ];then
#push into array
testarr1+=$remv1
fi
done
done < "$input"

Sorting an array passed to a function as parameter

I wanted to have some helper function for array sorting, that I would be able
to use whenever I need it. So I did something like this. It works.
#!/usr/bin/bash
# sorts an array given as param
function array_sort {
declare -a source_array=("${!1}")
sorted_array=($(for elmnt in "${source_array[#]}"; do echo $elmnt; done | sort))
echo "${sorted_array[#]}" # wont echo to stdout, because of assignment to a variable
}
# TEST CASE
# orginal array
arr=(c b a 3 2 1)
#assign function call to a variable
sorted=$(array_sort arr[#])
# echo-out the results
echo ${sorted[#]}
My question is, is there some better(cleaner) way of doing this, in terms of returning array elements from function (not implementing better sorting algorithm)?
If you want a robust sorting function (i.e., one that will deal with spaces and newlines flawlessly), you may consider implementing a sorting algorithm in Bash: here's a quicksort.
quicksort() {
# sorts the positional elements wrt alphanumerical sort
# return is in array quicksort_ret
if (($#==0)); then
quicksort_ret=()
return
fi
local pivot=$1 greater=() lower=() i
shift
for i; do
if [[ "$i" < "$pivot" ]]; then
lower+=( "$i" )
else
greater+=( "$i" )
fi
done
quicksort "${greater[#]}"
greater=( "${quicksort_ret[#]}" )
quicksort "${lower[#]}"
quicksort_ret+=( "$pivot" "${greater[#]}" )
}
$ quicksort c b a 3 2 1
$ printf '%s\n' "${quicksort_ret[#]}"
1
2
3
a
b
c
You can change the ordering test in the line
if [[ "$i" < "$pivot" ]]; then
by whatever you like. E.g., for numerical only sort, you'd use
if ((i<pivot)); then
You can even use a variable (e.g., quicksort_order) that will expand to an ordering function. In this case, replace the former line by
if $quicksort_order "$i" "$pivot"; then
and use with, e.g., if you want alphanumerical sort:
order_alnum() { [[ $1 < $2 ]]; }
quicksort_order=order_alnum
The quicksort function uses the positional parameters for input and the variable quicksort_ret for output. It's now trivial to make a wrapper around this function to handle an array name as input.
For a method that, like yours, uses sort but fixes the issues with wildcards and spaces (but doesn't fix issues with newlines). Uses the builtin mapfile, so this is Bash≥4 only. For Bash<4, there are other workarounds (but you shouldn't be using Bash<4 anymore anyways).
#!/usr/bin/bash
# sorts an array given as param
# return is in array sorted_array
array_sort() {
mapfile -t sorted_array < <( printf '%s\n' "${!1}" | sort )
}
# TEST CASE 1
echo "Test 1"
# original array
arr=(c b a 3 2 1)
# sort array
array_sort "arr[#]"
# display array
declare -p "sorted_array"
# TEST CASE 2
echo "Test 2"
# original array
arr=( '*' 'a space in this field' )
# sort array
array_sort "arr[#]"
# display array
declare -p "sorted_array"
# TEST CASE 3 (fails)
echo "Test 3"
# original array
arr=( $'there is\na newline\nin this array' )
# sort array
array_sort "arr[#]"
# display array
declare -p "sorted_array"
will output:
Test 1
declare -a sorted_array='([0]="1" [1]="2" [2]="3" [3]="a" [4]="b" [5]="c")'
Test 2
declare -a sorted_array='([0]="*" [1]="a space in this field")'
Test 3
declare -a sorted_array='([0]="a newline" [1]="in this array" [2]="there is")'
Answering your questions in comment:
So that way I would have to know the name of that sorted_array variable, to use it in my scripts. Can that be avoided?
If you want to give the name of the sorted array, modify array_sort as:
array_sort() {
# $1 is the name of array to sort (with the trailing [#])
# $2 is the name of the returned array (without [#])
# Note: name of output array can be name of input array
mapfile -t "$2" < <( printf '%s\n' "${!1}" | sort )
}
and use as:
$ a=( a g e z j r )
$ array_sort "a[#]" a_sorted
$ declare -p a_sorted
declare -a a_sorted='([0]="a" [1]="e" [2]="g" [3]="j" [4]="r" [5]="z")'
If you want to use the quicksort function from my first answer, you'd use a wrapper function (sorry about the name)(*):
quicksort_gniourf() {
# $1 is the name of array to sort (with the trailing [#])
# $2 is the name of the returned array (without [#])
# Note: name of output array can be name of input array
# This is a wrapper function around the quicksort function
quicksort "${!1}"
local k=0 v
declare -g "$2=()"
for v in "${quicksort_ret[#]}"; do
printf -v "$2[$k]" '%s' "$v"
((++k))
done
}
and use as (here I'm using the same array name for input and output):
$ a=( a g e z j r )
$ quicksort_gniourf "a[#]" a
$ declare -p a
declare -a a='([0]="a" [1]="e" [2]="g" [3]="j" [4]="r" [5]="z")'
Also, how would you echo out that resulting array, preventing expansion of *, with declare -p it is ok, however with printf or echo it expands on filenames?
To print an array array using echo without expanding wildcards (observe the quotes):
echo "${array[#]}"
and using printf, one field per line (observe the quotes):
printf '%s\n' "${array[#]}"
(*) As #konsolebox mentions in his comment, declare -g appeared in bash 4.2. You can replace this line with eval "$2=()" if you like (it's fairly safe at this point since $2 is supposed to be a variable name anyways).
Forwarded from PlayShell's array/sort.sh, here's a pure Bash solution that uses Quicksort algorithm.
# ---- array.sh ----
# array_copy (avar <src>, avar <dest>) :: boolean
#
# Copies a whole array including index (key) structure.
#
# For a faster method that does not copy key structure, see
# array_get_all().
#
# This function will return true status code even if the source array
# is empty. It may only return false if other problem occurs like for
# example if source or destination array is not an indexed array
# variable or if the two array variables are not compatible.
# On the other hand, array_get_all() returns false if source array is
# empty.
#
function array_copy {
local -i __I
eval "$2=() && for __I in \${!$1[#]}; do $2[__I]=\${$1[__I]}; done"
# I hope AVAR=() does not reset variable's attributes. I've been
# wondering if I should use 'unset AVAR\[*\]' instead. The latter
# version probably is a bit slower though since it's a builtin call.
}
# array_reset (avar <array>, [mixed <element_value>, ...])
#
# Clears an array or resets it to optional elements.
#
function array_reset {
eval "$1=(\"\${#:2}\")"
}
# ---- array/sort.sh ----
# ----------------------------------------------------------------------
# array/sort.sh
#
# A portable utility that provides a function that sorts an array of a
# specific type. The sorted output can be in the form of values or
# indices.
#
# This methods were based from QuickSort (the one described in
# "A Book on C 4th Ed.").
#
# Credits have got to be given to the authors of the book
# "A Book on C 4th Ed." for this great algorithm. The algorithm was
# originally described by someone and was completely explained in the
# book with an implementation that's written in C.
#
# I knew C from many sources but most of what I learned came from this
# book and I therefore recommend it to starters for a good start and
# also to experienced programmers for a good reference and new methods
# that they may discover from it.
#
# I hope you enjoy using these functions and/or algorithms.
#
# Author: konsolebox
# Copyright free, 2008-2013
# ----------------------------------------------------------------------
# array_sort
# (
# ["from=<array>"],
# ["type=<string|integer>"],
# ["to=<array>"],
# ["as=<values|indices>"],
# ["--" [ SOURCEVALUES[#] ]]
# )
#
function array_sort {
[[ $# -eq 0 ]] && return
local __FROM __TYPE __TO __AS
local -a __ARRAY
local -a -i __INDICES
while [[ $# -gt 0 ]]; do
case "$1" in
from=*)
__FROM=${1#from=}
;;
type=*)
__TYPE=${1#type=}
;;
to=*)
__TO=${1#to=}
;;
as=*)
__AS=${1#as=}
;;
--)
shift
break
;;
#beginsyntaxcheckblock
*)
array_sort_error "unknown parameter: $1"
;;
#endsyntaxcheckblock
esac
shift
done
#beginsyntaxcheckblock
[[ -n $__FROM && $__FROM != [[:alpha:]_]*([[:alpha:][:digit:]_]) ]] && \
array_sort_error "variable name not valid for the source array: $__FROM"
[[ -n $__TYPE && $__TYPE != #(string|integer) ]] && \
array_sort_error "argument is not valid for type: $__TYPE"
[[ -n $__TO && $__TO != [[:alpha:]_]*([[:alpha:][:digit:]_]) ]] && \
array_sort_error "variable name not valid for the target array: $__TO"
[[ -n $__AS && $__AS != #(values|indices) ]] && \
array_sort_error "argument is not valid for as: $__AS"
[[ -z $__FROM && $# -eq 0 ]] && \
array_sort_error "a source should be specified either by 'from=<array>' or '-- CONTENTS[#]'"
#endsyntaxcheckblock
if [[ $# -gt 0 ]]; then
__ARRAY=("$#")
elif [[ -n $__FROM ]]; then
array_copy "$__FROM" __ARRAY || \
array_sort_error "failed to make a temporary working copy of $__FROM."
fi
[[ -z $__TYPE ]] && __TYPE=string
[[ -z $__TO ]] && __TO=__
[[ -z $__AS ]] && __AS=values
__INDICES=("${!__ARRAY[#]}")
if [[ ${#__INDICES[#]} -gt 1 ]]; then
case "$__TYPE" in
string)
array_sort_strings 0 "$(( ${#__INDICES[#]} - 1 ))"
;;
integer)
array_sort_integers 0 "$(( ${#__INDICES[#]} - 1 ))"
;;
esac
fi
case "$__AS" in
values)
local -i I J=0
array_reset "$__TO"
eval "for I in \"\${__INDICES[#]}\"; do ${__TO}[J++]=\${__ARRAY[I]}; done"
;;
indices)
eval "$__TO=(\"\${__INDICES[#]}\")"
;;
esac
}
# array_sort_strings (uint LEFT, uint RIGHT)
#
function array_sort_strings {
[[ $1 -lt $2 ]] || return
local -i LEFT=$1 RIGHT=$2 PIVOT PARTITION
if array_sort_strings_findpivot; then
array_sort_strings_partition
array_sort_strings "$LEFT" "$(( PARTITION - 1 ))"
array_sort_strings "$PARTITION" "$RIGHT"
fi
}
# array_sort_strings_findpivot () :: boolean
#
function array_sort_strings_findpivot {
local -i A B C P MIDDLE
(( MIDDLE = LEFT + (RIGHT - LEFT) / 2 ))
(( A = __INDICES[LEFT] ))
(( B = __INDICES[MIDDLE] ))
(( C = __INDICES[RIGHT] ))
[[ ${__ARRAY[A]} > "${__ARRAY[B]}" ]] && (( A = $B, B = $A ))
[[ ${__ARRAY[A]} > "${__ARRAY[C]}" ]] && (( A = $C, C = $A ))
[[ ${__ARRAY[B]} > "${__ARRAY[C]}" ]] && (( B = $C, C = $B ))
if [[ ${__ARRAY[A]} < "${__ARRAY[B]}" ]]; then
PIVOT=$B
return 0
fi
if [[ ${__ARRAY[B]} < "${__ARRAY[C]}" ]]; then
PIVOT=$C
return 0
fi
for (( P = LEFT + 1; P < MIDDLE; ++P )); do
if [[ ${__ARRAY[P]} > "${__ARRAY[A]}" ]]; then
PIVOT=$P
return 0
fi
if [[ ${__ARRAY[P]} < "${__ARRAY[A]}" ]]; then
PIVOT=$A
return 0
fi
done
for (( P = MIDDLE + 1; P < RIGHT; ++P )); do
if [[ ${__ARRAY[P]} > "${__ARRAY[A]}" ]]; then
PIVOT=$P
return 0
fi
if [[ ${__ARRAY[P]} < "${__ARRAY[A]}" ]]; then
PIVOT=$A
return 0
fi
done
return 1
}
# array_sort_strings_partition ()
#
function array_sort_strings_partition {
local -i L R T
local P=${__ARRAY[PIVOT]}
for (( L = LEFT, R = RIGHT; L <= R; )); do
while [[ ${__ARRAY[__INDICES[L]]} < "$P" ]]; do
(( ++L ))
done
until [[ ${__ARRAY[__INDICES[R]]} < "$P" ]]; do
(( --R ))
done
[[ L -lt R ]] && (( T = __INDICES[L], __INDICES[L] = __INDICES[R], __INDICES[R] = T, ++L, --R ))
done
(( PARTITION = L ))
}
# array_sort_integers (uint LEFT, uint RIGHT)
#
function array_sort_integers {
[[ $1 -lt $2 ]] || return
local -i LEFT=$1 RIGHT=$2 PIVOT PARTITION
if array_sort_integers_findpivot; then
array_sort_integers_partition
array_sort_integers "$LEFT" "$(( PARTITION - 1 ))"
array_sort_integers "$PARTITION" "$RIGHT"
fi
}
# array_sort_integers_findpivot () :: boolean
#
function array_sort_integers_findpivot {
local -i A B C P MIDDLE
(( MIDDLE = LEFT + (RIGHT - LEFT) / 2 ))
(( A = __INDICES[LEFT] ))
(( B = __INDICES[MIDDLE] ))
(( C = __INDICES[RIGHT] ))
[[ __ARRAY[A] -gt __ARRAY[B] ]] && (( A = $B, B = $A ))
[[ __ARRAY[A] -gt __ARRAY[C] ]] && (( A = $C, C = $A ))
[[ __ARRAY[B] -gt __ARRAY[C] ]] && (( B = $C, C = $B ))
if [[ __ARRAY[A] -lt __ARRAY[B] ]]; then
PIVOT=$B
return 0
fi
if [[ __ARRAY[B] -lt __ARRAY[C] ]]; then
PIVOT=$C
return 0
fi
for (( P = LEFT + 1; P < MIDDLE; ++P )); do
if [[ __ARRAY[P] -gt __ARRAY[A] ]]; then
PIVOT=$P
return 0
fi
if [[ __ARRAY[P] -lt __ARRAY[A] ]]; then
PIVOT=$A
return 0
fi
done
for (( P = MIDDLE + 1; P < RIGHT; ++P )); do
if [[ __ARRAY[P] -gt __ARRAY[A] ]]; then
PIVOT=$P
return 0
fi
if [[ __ARRAY[P] -lt __ARRAY[A] ]]; then
PIVOT=$A
return 0
fi
done
return 1
}
# array_sort_integers_partition ()
#
function array_sort_integers_partition {
local -i L R T P
for (( L = LEFT, R = RIGHT, P = __ARRAY[PIVOT]; L <= R; )); do
for (( ; __ARRAY[__INDICES[L]] < P; ++L )); do
continue
done
for (( ; __ARRAY[__INDICES[R]] >= P; --R )); do
continue
done
[[ L -lt R ]] && (( T = __INDICES[L], __INDICES[L] = __INDICES[R], __INDICES[R] = T, ++L, --R ))
done
(( PARTITION = L ))
}
# array_sort_error (string <message>)
#
function array_sort_error {
echo "array_sort: $1"
exit 1
}
# ----------------------------------------------------------------------
# Footnotes:
#
# * In some versions of bash, conditional statements does not properly
# parse the second string operand so sometimes this form doesn't work:
#
# [[ $STRINGVAR1 < $STRINGVAR2 ]]
#
# So to make it work, we have no choice but put it around quotes:
#
# [[ $STRINGVAR1 < "$STRINGVAR2" ]]
#
# * In some versions of bash, a segmentation fault occurs when
# assignment statements where sources are arrays are compounded.
#
# (( A = __A0[INDEX1], B = __A0[INDEX2] ))
# ----------------------------------------------------------------------
You can sort an array like this:
arr=(c b a 3 2 1)
sarr=( $(sort < <(printf "%s\n" "${arr[#]}")) )
printf "%s\n" "${sarr[#]}"
1
2
3
a
b
c
EDIT: To make it into a function:
array_sort() {
declare -a source_array=("${!1}");
sarr=( $(sort < <(printf "%s\n" "${arr[#]}")) );
echo "${sarr[#]}";
}

Resources