arraysbashsubshell

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+=("$@")
}

Solution

  • 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'.