Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make bash 'pop' and 'shift' functions that actually return the item popped or shifted?

Running

$ echo $BASH_VERSION
4.3.42(1)-release

given these two functions:

ashift ()
{
    declare -n arr;arr="$1"
    ((${#arr[@]} == 0)) && return
    echo "${arr[0]"}
    arr=("${arr[@]:1}")
}
apop ()
{
    declare -n arr="$1";shift
    ((${#arr[@]} == 0)) && return
    echo "${arr[-1]}"
    arr=("${arr[@]:0:$((${#arr[@]}-1))}")
}

the 'natural' way to use them would be

declare -a thearray
thearray=(a b c d e f g)
p=$(apop thearray)
s=$(ashift thearray)
echo "p=$p, thearray=${thearray[@]}, s=$s"

However, the output is not what you would expect:

p=g, thearray=a b c d e f g, s=a

That is because (I think) we are running the ashift and apop in a subshell to capture the output. If I do not capture the output:

declare -a thearray
thearray=(a b c d e f g)
apop thearray
ashift thearray
echo "thearray=${thearray[@]}"

the output (intermixed with the commands) is

g
a
thearray=b c d e f

So, does anyone know how I can run the apop and ashift commands in the current process AND capture the output?


Note: For completeness, these work because there is no capturing, so you don't ever run them in a subshell:

aunshift ()
{
    declare -n arr;arr="$1";shift
   arr=("$@" "${arr[@]}")
}
apush ()
{
    declare -n arr;arr="$1";shift
    arr+=("$@")
}
like image 894
mpersico Avatar asked Sep 21 '25 12:09

mpersico


2 Answers

Assuming that you are OK with the following 'API' to the 4 operations:

apush array value1 value2 value3 ...
ashift array value1 value2 value3 ...
apop array var1 var2 var3        # Extracted values stored in variables
aunshift array var1 var2 var3    # Extract values stored in variables

Possible to use bash reference variables (declare -n).

#! /bin/bash

function apush {
    declare -n _array=$1
    shift
    _array+=("${@}")
}

function apop {
    declare -n _array=$1
    shift
    declare _n=0
    for _v ; do
        declare -n _var=$_v
        let ++_n
        _var=${_array[-_n]}
    done
    array=("${_array[@]:0:$((${#_array[@]}-_n))}")
}

Use the following test case

A=()
apush A B1 B2 B3
apush A C1 C2 C3
echo "S1=${A[*]}"
apop A X1 X2 X3
echo "X1=$X1, X2=$X2, X3=$X3, A=${A[*]}"

Output

S1=B1 B2 B3 C1 C2 C3
X1=C3, X2=C2, X3=C1, A=B1 B2 B3

Likewise, the ashift, aunshift can be implemented following similar pattern.

Also note: because of the way bash references work, it is not possible to access variables with the same name as local variables. If the function is called with a variable matching local name, the it will produce an error. Functions modified to use _ prefix for variable, to reduce chance of this problem.

apush _array ABC

Output:
bash: declare: warning: array: circular name reference
bash: warning: array: circular name reference
like image 148
dash-o Avatar answered Sep 23 '25 01:09

dash-o


Original answer

As per the first comment: Not possible on bash (or any shell that runs pipeline components in a subshell environment) without an intermediate file or a named pipe.

Given that the actual code that does something in each case is a one-liner, it's best to just learn the idiom and use it in-line. That is what I have done in all of the code I have that was trying to use these functions.

Update

As per the solution from @dash-o below, the idea is to provide variables in which to pop or shift the removed values. Here is the full set of functions:

apush ()
{
    declare -n arr="$1"
    shift
    arr+=("$@")
}

apop ()
{
    declare -n array=$1
    shift
    declare _i=0
    if [[ $1 =~ ^- ]]
    then
        while (($#))
        do
            if [[ $1 = '-a' ]]
            then
                declare -n output=$2
                shift;shift
                continue;
            fi
            if [[ $1 = '-n' ]]
            then
                declare c=$2
                shift;shift
                continue
            fi
            echo "$1 is an invalid option"
            return 1
        done

        while ((_i<c))
        do
            ((_i+=1))
            output+=("${array[-_i]}")
        done
    else
        for _v ; do
            declare -n _var=$_v
            ((_i+=1))
            # shellcheck disable=SC2034 #https://github.com/koalaman/shellcheck/wiki/SC2034
            _var=${array[-_i]}
        done
    fi
    array=("${array[@]:0:$((${#array[@]}-_i))}")
}

aunshift ()
{
    declare -n arr="$1"
    shift
    arr=("$@" "${arr[@]}")
}

    ashift ()
{
    declare -n array=$1
    shift
    declare _i=-1
    if [[ $1 =~ ^- ]]
    then
        while (($#))
        do
            if [[ $1 = '-a' ]]
            then
                declare -n output=$2
                shift;shift
                continue;
            fi
            if [[ $1 = '-n' ]]
            then
                declare c=$2
                shift;shift
                continue
            fi
            echo "$1 is an invalid option"
            return 1
        done

        while ((_i+1<c))
        do
            ((_i+=1))
            output+=("${array[_i]}")
        done
    else
        for _v ; do
            declare -n _var=$_v
            ((_i+=1))
            # shellcheck disable=SC2034 #https://github.com/koalaman/shellcheck/wiki/SC2034
            _var=${array[_i]}
        done
    fi
    array=("${array[@]:((_i+1))}")
}

I changed the _n to _i because the variable represents an index into the array, not a number or a count (and it was too visually clashing with the -n option to declare).

Also from @dash-o's answer below, here are the docs in how to use them:

apush    array value1 value2 value3 ...
ashift   array value1 value2 value3 ...

apop     array var1 var2 var3 ...   # Extracted values stored in variables.
apop     array -n 3 -a tgtarray     # 3 extracted values stored in array
                                    # 'tgtarray'. Note that the values are
                                    # ADDED to any existing values already
                                    # in the 'tgtarray'.

aunshift array var1 var2 var3 ...   # Extracted values stored in variables.
aunshift array -n 4 -a tgtarray     # 4 extracted values stored in array
                                    # 'tgtarray'. Note that the values are
                                    # ADDED to any existing values already
                                    # in the 'tgtarray'.
like image 26
mpersico Avatar answered Sep 23 '25 03:09

mpersico