k-man
k-man

Reputation: 1211

Remove the last element from an array

I want to remove the last entry in my array, and I want the array to show me that it has 1 less entry when I am using the ${#array[@]}. This is the current line I am using:

unset GreppedURLs[${#GreppedURLs[@]} -1]

Please correct me and show me the right way.

Upvotes: 53

Views: 46529

Answers (6)

sehe
sehe

Reputation: 392911

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

Upvotes: 80

张馆长
张馆长

Reputation: 1839

In your function, you could add the following:

target="${@:$(($#)):1}"
set -- "${@:1:$(($#-1))}"

Upvotes: 1

Dmitry
Dmitry

Reputation: 813

The following works fine for Mac/[email protected] and Linux (ubuntu/[email protected])

unset arr[$[${#arr[@]}-1]] # non-sparse array only

in more details:

len=${#arr[@]}
idx=$[$len-1]    # <=> $(($len-1))
unset arr[$idx]

Upvotes: 1

user8017719
user8017719

Reputation:

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}]"

Upvotes: 11

Wil
Wil

Reputation: 837

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.

Upvotes: 17

Cito
Cito

Reputation: 5603

You must remove the blank before -1.

Upvotes: 16

Related Questions