Remove the last element from an array

The answer you have is (nearly) correct for non-sparse indexed arrays¹:

unset 'arr[${#arr[@]}-1]'

Bash 4.3 or higher added this new syntax to do the same:

 unset arr[-1]

(Note the single quotes: they prevent pathname expansion).

Demo:

arr=( a b c )
echo ${#arr[@]}

3

for a in "${arr[@]}"; do echo "$a"; done
a
b
c
unset 'arr[${#arr[@]}-1]'
for a in "${arr[@]}"; do echo "$a"; done
a
b

Punchline

echo ${#arr[@]}
2

(GNU bash, version 4.2.8(1)-release (x86_64-pc-linux-gnu))


¹ @Wil provided an excellent answer that works for all kinds of arrays


You must remove the blank before -1.


If you'd like an answer which won't eat your kittens, try this:

array=([1]=1 {2..5} [10]=6);
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [5]="5" [10]="6}")'
index=("${!array[@]}");
# declare -a index='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="10")'
unset 'array[${index[@]: -1}]';
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'

And there you have it - removing the last element. Now I'll present a much easier answer which probably meets your needs but has a caveat:

array=([1]=1 {2..5} [10]=6);
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [5]="5" [10]="6}")'
array=("${array[@]::${#array[@]}-1}");
# declare -a array='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5")'

This version takes a shortcut. It re-indexes the array and drops the last element. Unfortunately you can also see that the index has not been maintained. The values and their order has been. If you don't care about the index then this is probably the answer you wanted.

Both of the above answers will also work on bash 4 Associative Arrays.

--

The chosen answer is not safe. Here's an example:

array=([1]=1 {2..5} [10]=6);
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [5]="5" [10]="6")'
unset 'arr[${#arr[@]}-1]';
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [10]="6")'

Okay so as you can see it is unsetting the element with index 5 because it incorrectly calculated the index of the last element of the array. It failed because it operated on an assumption that all arrays are zero-based, and not sparse. This answer will fail on arrays starting with anything other than zero, arrays which are sparse, and obviously must fail for an associative array with 'fubar' for the last element.


For any indexed array (sparse or not), since bash 4.3+ (and ksh93+), this is the simplest of solutions:

unset 'array[-1]'

The quotes are needed to avoid shell expansion in bash if the -1 is an arithmetic expression or a variable. This also works correctly:

a=3; unset 'arr[ a - 4 * 1 ]'

But will not work if unquoted ('') as the * will be expanded to the list of files in the present working directory ($pwd).

For older bash versions: this works since bash 3.0 for non-sparse arrays:

unset 'arr[ ${#arr[@]}-1 ]'

Example:

$ arr=( {a..i} ); declare -p arr  
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g" [7]="h")
$ unset 'arr[ ${#arr[@]}-1 ]'; declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g")

This will not work for sparse arrays (with some holes):

$ arr=( {a..g} [9]=i ); declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g" [9]="i")
$ unset 'arr[ ${#arr[@]}-1 ]'; declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g" [9]="i")

This happens because the count of elements (${#arr[@]}) is 8 and 8-1 is 7.
So, the command will unset arr[7], which doesn't exist. Nothing is done.

A solution, that also work for Associative arrays (in whatever it could mean "the last element" in an unsorted list) is to generate a new array of indexes.
Then use the last index to unset that element.

Assuming arr is already defined (for bash 3.0+):

$ index=( "${!arr[@]}" )          # makes index non-sparse.
$ unset 'arr[${index[@]}-1]'      # unset the last index.
$ declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g")

A slightly more portable (works in ksh93), that looks ugly, solution is:

$ arr=( {a..e} [9]=i )
$ index=( "${!arr[@]}" )
$ unset "arr[  ${index[${#index[@]}-1]}  ]"   # Yes, double quotes.
$ declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e")   

Or (again, double quotes for ksh):

$ unset "arr[${index[@]: -1}]"

If you want to avoid the space and the negative number, make it a variable:

$ a="-1"; unset "arr[${index[@]:a}]"