Arrays in BASH
Created:12 Feb 2017 14:48:27 , in Host development
BASH array operations and syntax
Suppose you have four strings: a b c d, and you want to place them all in array, here is how you could achieve this:
arr=(a b c d)
To take a look at what is in your array you use declare built-in:
declare -p arr
If you need to get all the values stored in array, you can do that in two ways:
${arr[@]} or ${arr[*]}
You can obtain all the keys in array:
${!arr[@]}
Frequently the number of items in the array is needed, here is the syntax for it:
${#arr[@]}
Obtaining particular value from array can be done as follows:
${arr[0]}
Using 0 as index results in first value being returned.
Like in other programming languages, arrays in BASH are zero-based.
To add another item to the end of array you use syntax:
arr+=("new item")
Removing array values is done using unset built-n, you also need an index of the value you want to remove:
unset arr[3]
You can loop over array values like this:
for item in "${arr[@]}"; do echo $item; done;
You can also loop over values starting from the last one:
for (( i=${#arr[@]};i>0; i-- )); do echo "${arr[$i - 1]}";done
It is possible to join all items in array with a separator:
str=$(IFS=,; echo "${arr[*]}")
To break up a string and place resulting parts in array you use IFS variable:
IFS=/ read -a arr <<< "a/b/c/d/e"
IFS stores separator(s) to be used for splitting, in the above case it is "/"
One can obtain a slice of the original array using parameter expansions.
arr_slice=("${arr[@]:1:2}")
Parameter expansions can also be used to update all elements of array in one go. For example, the line of code below adds + sign to the beginning of each string stored in array arr.
arr=( "${arr[@]/#/+}" )
Values in array can be updated in various other ways using parameter expansions. Check BASH manual for more information on this.
BASH array operations and syntax table
Array operation | Syntax |
---|---|
Creating array |
|
Inspecting |
|
Array values |
|
Array keys |
|
Number of items |
|
Single value |
|
Adding new value |
|
Removing a value |
|
Looping using for loop |
|
Looping from the end |
|
Joining items in array with , |
|
Breaking up string |
|
Slicing |
|
Adding prefix to each value |
|
Next version name of computer program
Imagine you have written a computer program, possibly a website, and you store its consecutive versions in a directory. Each version of the program has its own sub-directory with unique name in this directory. The version sub-directories could be named as follows: 0.1, 0.1.1, 0.5, 1.0, 1.2.1 etc. .
At some point you come to the conclusion you no longer want to deal with these version names by hand. Instead you would like a program that simply gives the next version name if provided no extra instruction (e.g next version of 1.1 would be 1.2) or finds correct version when clued (e.g. you might need version 1.3.4 to be 1.4.0 ).
Here is a program that finds next version name, it is appropriately called find_next_version_name.sh. It consists of a few global variables and 4 functions (each function has a short description above its name). The find_next_version_name.sh makes extensive use of arrays.
find_next_version_name.sh - part 1
#!/usr/bin/env bash
# Program name: find_next_version_name.sh
# Author: Sylwester Wojnowski
# WWW: wojnowski.net.pl
#directory in which consecutive version sub-directories are stored
VERSIONS_DIR=/tmp/my_program/versions/
# normalize version directory names.
#If set to 1, all version directories will be renamed to have the same length
NORMALIZE=1
########## Private ############
# set by find versions
VERSIONS=()
# set by find_latest_version
LAST_VERSION=
# find_versions() finds directories with names like 0.45, 1.1 1.11.5 or 1.1.8.9 in VERSIONS_DIR
# and places them in global VERSIONS array
find_versions(){
declare -n ITEMS=VERSIONS
local REGEX='^([0-9]+\.)+[0-9]+$'
local ASSORTED=
local VERSIONS_DIR="$1"
# make sure some directory is given
[[ -z "$VERSIONS_DIR" ]] && {
echo "Versions directory not given."
exit 1
}
# check, you can enter
# suppress output from command cd
cd "$VERSIONS_DIR" &> /dev/null
# check status of the last command
[[ "$?" != "0" ]] && {
echo "Versions directory does not exist. Exiting ..."
exit 1
}
# store all directory names in an array
ASSORTED=( * )
# filter version directories out from the rest and store them in ITEMS array
for item in "${ASSORTED[@]}"; do
[[ $item =~ $REGEX ]] && {
ITEMS+=("$item")
}
done
# exit if no version found
(( ${#ITEMS[@]} == 0 )) && {
printf "Directory $VERSIONS_DIR hosts no versions at the moment. Add first, perhaps something like 0.0.1 ..."
exit;
}
# get back to the previous working directory
cd "$OLDPWD"
return
}
Comments on the syntax used in the above piece of code
Create new empty VERSIONS array:
VERSIONS=()
Display values in array VERSIONS (see my article on declare and env for more details):
declare -p VERSIONS
Array ITEMS references global array versions. (see my article on env an bash declare for more details):
declare -n ITEMS=VERSIONS
Populate array ASSORTED with files and directories from the current working directory:
ASSORTED=( * )
Reference all items ( @ ) of ASSORTED at the same time:
${ASSORTED[@]}
If you need to reference a single item from an array you would use a numeric value in place of @:
${ASSORTED[1]}
Iterate through array ASSORTED and assign items that pass regular expressions based test to array ITEMS.
for item in "${ASSORTED[@]}"; do
[[ $item =~ $REGEX ]] && {
ITEMS+=("$item")
}
done
Find number of items currently in array ITEMS:
${#ITEMS[@]}
find_next_version_name.sh part 2
# standardize_versions() updates VERSIONS array names in such a way that each version has length of the longest version name
# e.g. if the longest version name is 1.1.1.1 and current version is 1.1, 1.1 becomes 1.1.0.0
# Original sub-directory names remain the same unless NORMALIZE global variable has value 1.
standardize_versions(){
local LONGEST=0
# find longest version name
for version in "${VERSIONS[@]}"; do
local vlen=
IFS=. read -r -a vlen <<< "$version"
(( ${#vlen[@]} > $LONGEST )) && {
LONGEST=${#vlen[@]};
}
done
# extend shorter version names with '.0'
for (( i=0 ; $i < ${#VERSIONS[@]}; i++ )); do
local orig_version=${VERSIONS[$i]}
local version=${VERSIONS[$i]}
vlen=
IFS=. read -r -a vlen <<< "$version"
while (( ${#vlen[@]} < $LONGEST )); do
version="${version}.0"
IFS=. read -r -a vlen <<< "$version"
done
VERSIONS[$i]="$version"
(( $NORMALIZE == 1 )) && [[ "$version" != "$orig_version" ]] && {
cd "$VERSIONS_DIR"
mv "$orig_version" "$version"
cd "$OLDPWD"
}
done
}
Comments on the syntax used in the above piece of code:
Split variable $version (string) using dot and place resulting values in array vlen:
IFS=. read -r -a vlen <<< "$version"
Iterate over array VERSIONS using for loop:
for (( i=0 ; $i < ${#VERSIONS[@]}; i++ )); do
.
.
.
done
Set variable orig_version to value ${VERSIONS[$i]}, where VERSIONS is an array and $i is a number ( holds particular array index )
orig_version=${VERSIONS[$i]}
find_next_version_name.sh part 3
# find_last_version() finds the highest version ( last created might not be the most recent )
# 1.1.0.1 is higher than 1.1.0.0 and 1.2.0.0 is higher 1.1.20.1
find_last_version(){
declare -n last_version=LAST_VERSION
local last_version_a=
local next_version_a=
for version in "${VERSIONS[@]}"; do
# set first version name
[[ -z "$last_version" ]] && {
last_version="$version"
IFS=. read -r -a last_version_a <<< "$version"
# compare version names here
} || {
IFS=. read -r -a next_version_a <<< "$version"
# now compare two arrays
for (( i=0; $i < ${#last_version_a[@]}; i++ )); do
(( ${last_version_a[$i]} > ${next_version_a[$i]} )) && { break; }
(( ${last_version_a[$i]} < ${next_version_a[$i]} )) && {
last_version=$version;
IFS=. read -r -a last_version_a <<< "$version"
break;
}
(( ${last_version_a[$i]} == ${next_version_a[$i]} )) && {
continue;
}
done
}
done
return
}
Compare numeric values ${last_version_a[$i]} and ${next_version_a[$i]}. Last_version_a and next_version_a are arrays and ${last_version_a[$i]} and # ${next_version_a[$i]} are values with index $i (number)
(( ${last_version_a[$i]} > ${next_version_a[$i]} ))
(( ${last_version_a[$i]} < ${next_version_a[$i]} ))
(( ${last_version_a[$i]} == ${next_version_a[$i]} ))
find_next_version_name part 4
# find_next_version_name() generates next version name.
# If invoked with no argument, it updates the rightmost part of the highest version name
# ( e.g. 1.2.1 becomes 1.2.2 ).
# if invoked with index, the function increments number at the index given as its sole argument by 1;
# e.g. if the highest version is 1.2.2 and the function is invokes like this: find_next_version_name 1
# next version will be 1.3.0, invoking it as follows: find_next_version_name 0 produces 2.0.0
find_next_version_name() {
find_versions "$VERSIONS_DIR"
standardize_versions
find_last_version
local last_version=
local index=-1
local indices=
IFS=. read -r -a last_version <<< "$LAST_VERSION"
[[ ! -z "$1" ]] && {
# check if given index exists
indices="${!last_version[@]}"
for i in $indices; do
[[ "$1" == "$i" ]] && {
index="$i"
break;
}
done
(( index == -1 )) && {
printf "*** \n ${1} is not a valid index. Possible indices are 0 numbered. Each index correspond to a placement of a number in dot separated string representing the last version, which in this particular case is ${LAST_VERSION}. Hence, possible values here are ${indices}. \n Exiting ...\n *** \n"
exit 1
}
(( last_version[$index]+=1 ))
for (( (( index++ )), j=index; $j < ${#last_version[@]}; j++ )); do
[[ last_version[$j] ]] && { last_version[$j]=0 ;}
done
} || {
(( last_version[$index]+=1 ))
}
echo $( IFS=.; echo "${last_version[*]}")
}
#run the program
find_next_version_name $1
Comments on the syntax used in the above piece of code:
Display indices of array last_version:
${!last_version[@]}
Increment value of array last_version with index $index:
(( last_version[$index]+=1 ))
Test for existence of index $i in array last_version:
[[ last_version[$j] ]]
Display all values currently in array last_version ( * works the same as @ )
${last_version[*]}
Join all items in array last_version using dot symbol (.)
echo $( IFS=.; echo "${last_version[*]}")
Other things worth knowing about BASH arrays
There is special syntax for both removing indices from an array and destroying whole arrays. In both cases built-in unset is used:
Remove member variable at index 0:
unset ${ARRAY[0]}
Destroy array ARRAY:
unset ARRAY
Running the program
If you want to run the program used as an example above, save all its four pieces to a file with the name find_next_version_name.sh, and make the file executable:
$ chmod 755 find_next_version_name.sh
Next update VERSIONS_DIR variable value at the beginning of the file to point to a directory (trailing slash required) you store you program sub-directories in. The sub-directories must have names that follow the pattern number.number. ... .number for the program to find them.
Run the program:
$ find_next_version_name.sh
This post was updated on 06 Oct 2021 21:13:02
Tags: BASH
Author, Copyright and citation
Author
Author of the this article - Sylwester Wojnowski - is a sWWW web developer. He has been writing computer code for the websites and web applications since 1998.
Copyrights
©Copyright, 2024 Sylwester Wojnowski. This article may not be reproduced or published as a whole or in parts without permission from the author. If you share it, please give author credit and do not remove embedded links.
Computer code, if present in the article, is excluded from the above and licensed under GPLv3.
Citation
Cite this article as:
Wojnowski, Sylwester. "Arrays in BASH." From sWWW - Code For The Web . https://swww.com.pl//main/index/arrays-in-bash
Add Comment