How to push pop shift and unshift arrays in bash

Oh Bash…

Let me start by saying, I hate bash. I should stop using it for anything remotely more complicated than running a bunch of commands in series. I don’t know if this already exists in bash, but I couldn’t find it and I needed a data structure in a hurry, so here it is.

Bash’s Pseudo Functions

Since you can’t really pass pointers or references to arrays and you can’t return anything other than integers, here’s how to implement push, pop, shift, and unshift in a bash shell script.

Push

arr_push() {
  arr=("${arr[@]}" "$1")
}

Pop

arr_pop() {
  i=$(expr ${#arr[@]} - 1)
  placeholder=${arr[$i]}
  unset arr[$i]
  arr=("${arr[@]}")
}

Shift

arr_shift() {
  arr=("$1" "${arr[@]}")
}

Unshift

arr_unshift() {
  placeholder=${arr[0]}
  unset arr[0]
  arr=("${arr[@]}")
}

This won’t be so bad if you’re just using one array since you’re coding the array directly into the functions, but it’s obviously not going to scale!

Test Time!

Now for some wicked simple examples…

Start with a three element array called “arr”. I’ll use some string numbers to make it obvious which index of the array they belong in.

arr=("one" "two" "three")
echo ${arr[@]}

one two three

Put a “zero” at the beginning and a “four” on the end.

arr_shift "zero"
arr_push "four"
echo ${arr[@]}

zero one two three four

Drop the first element, “zero”, by unshifting.

arr_unshift
echo ${arr[@]}

one two three four

Pop off the last element, “four”, by popping.

arr_pop
echo ${arr[@]}

one two three

If you want to hold onto the values of unshift and pop, you can look at the placeholder variable. It keeps the last element that was removed.

Now that i’ve done this the hard way, it’s time to throw it all away and rewrite the script in python. It was supposed to be a simple take this command line option and do something, but it turned into a much longer program.

Posted by admica   @   18 November 2010
Tags : , , , , , , ,

Related Posts

7 Comments

Comments
Feb 7, 2011
2:25 pm
#1 Jo Liss :

(Came here looking for a sane way to do array handling in Bash. Apparently it doesn’t exist.)

Out of curiosity, why are you doing arr=(“${arr[@]}”)? Isn’t that a no-op?

Some of these functions can be simplified, by the way. For example,
unset arr[${#arr[@]}-1]
is how I do arr_pop in a single expression. If you squint, it’s almost readable.

May 23, 2011
1:10 pm
#2 matheus :

eu fiz em um método muito diferente e mais fácil …

array+=(hmm)

May 24, 2011
8:45 am
#3 admica :

they can be, but I didn’t really care what they looked like or how they performed. they got the job done and were throwaway functions anyway. It wasn’t worth the time to pretty them up. I wrote the first thing that came to mind when I approached the situation and that was what popped out.

Jul 5, 2011
3:18 am
#4 Oded :

Pop/push/shift/unshift in Bash are much less horrendous then you describe – see after the break.

BTW – you have shift and unshift confused in your code: shift discards the first element by shifting the all elements to the left so that element 1 is now element 0, while unshift is “push from the other side” – it sticks an element at the beginning of the array shifting all the other elements to the right.

True – the syntax may not be pretty (for some operations), especially if you are used to object oriented languages like ECMAScript (ne, Javascript), but its usable and understandable:

Push:
arr+=(newvalue)
[Kudo matheus]

Pop:
unset arr[${#arr[*]}-1]
[Kudo Jo Liss]
This just truncates the array by removing the last element. If you want to actually get it first, then just assign ${arr[${#arr[*]}-1]} to something before hand.

Shift:
arr=(“${arr[@]:1}”)
This uses the “slice syntax” which you can use to get any view of the array. Again, you don’t get to keep the shifted argument so if you want it, assign it elsewhere first.

Unshift:
arr=(newvalue “${arr[@]}”)

Jul 25, 2011
1:47 am
#5 Eduardo Bustamante :

This is extremely inefficient:

$ TIMEFORMAT=%R; time arr=({1..1000000}); time arr=(“${arr[@]}” 1); time arr+=(1)
1.914
3.765
0.000

Also, let me point out to Jo Liss that arr=(“${arr[@]}”) isn’t no-op; bash arrays can be sparse, so reassigning removes the wholes.

Just use the recommended syntax to append ( foo+=(“bar”) which is extremely cheap ); if you require shifting or popping, then the thing gets pretty difficult… the fact that bash arrays are sparse makes that more difficult (there’s no guarantee that ${#arr[@]}-1 is the last element and you can’t mix the ${!arr[@]} expansion with the ${arr[@]: -1} selector…)

Jul 25, 2011
1:48 am
#6 Eduardo Bustamante :

Ups, s/wholes/holes/

Oct 10, 2011
10:30 pm
#7 dethrophes :

This is a more generic version

function push_element {
[ $# -gt 1 ] || return 0
eval $1=\(\”\${$1[@]}\” \”\${@:2:99}\”\)
}
function pop_element {
local ArgCnt
local ArrayVal
eval ArrayVal=\(\”\${$1[@]}\”\)
ArgCnt=${#ArrayVal[@]}
(( ArgCnt — ))
echo “${ArrayVal[$ArgCnt]}”
eval $1=\(“\${$1[@]:0:$ArgCnt}”\)
}

function shift_element {
local ArrayVal
eval ArrayVal=\(\”\${$1[@]}\”\)
echo “${ArrayVal[0]}”
eval $1=\(“\${$1[@]:1:99}”\)
}
function unshift_element {
[ $# -gt 1 ] || return 0
eval $1=\(\”\${@:2:99}\” \”\${$1[@]}\” \)
}

Test Code

TestArray=(testq testg sdgsdg)
echo TestArray=”${TestArray[@]}”
push_element TestArray Push

echo TestArray=”${TestArray[@]}”
unshift_element TestArray Shift
echo TestArray=”${TestArray[@]}”

pop_element TestArray
echo TestArray=”${TestArray[@]}”

shift_element TestArray
echo TestArray=”${TestArray[@]}”

Leave a Comment

Name

Email

Website

*

Previous Post
«
Next Post
»
Powered by Wordpress   |   Lunated designed by ZenVerse

Valid XHTML 1.0 Transitional