how to shift array value in bash

17,700

Solution 1

A general remark. It does not make sense to define an array like this:

folder_mount_point_list="sdb sdc sdd sde sdf sdg"
folderArray=( $folder_mount_point_list )

You would do this instead:

folderArray=(sdb sdc sdd sde sdf sdg)

Now to your question:

set -- sdb sdc sdd sde sdf sdg
for folder_name; do
    mkdir "/data/$folder_name"
done

or

set -- sdb sdc sdd sde sdf sdg
while [ $# -gt 0 ]; do
    mkdir "/data/$1"
    shift
done

Solution 2

To answer the question in the title, you can "shift" an array with the substring/subarray notation. shift itself works with just the positional parameters.

$ a=(a b c d e)
$ a=("${a[@]:1}")
$ echo "${a[@]}"
b c d e

Similarly, to 'pop' the last item off the array: a=("${a[@]:0:${#a[@]} - 1}" ) or unset "a[${#a[@]}-1]"

So if you wanted to, you could do this:

a=(foo bar doo)
b=(123 456 789)
while [ "${#a[@]}" -gt 0 ]; do
    echo "$a $b"
    a=("${a[@]:1}")
    b=("${b[@]:1}")
done

But it trashes the arrays, and the "shifting" assignments probably copy the data around unnecessarily, so just indexing as usual might be better.

a=(foo bar doo)
b=(123 456 789)
i=0
while [ "$i" -lt "${#a[@]}" ]; do
    echo "${a[i]} ${b[i]}"
    i=$((i+1))
done

Or maybe use an associative array instead, if you don't care about the order of the items. "${!arr[@]}" gives the keys in an unspecified order, probably not the order they were assigned in:

declare -A arr=([foo]=123 [bar]=456 [doo]=789)
for k in "${!arr[@]}"; do
    echo "$k ${arr[$k]}"
done

Solution 3

You can simply loop over all values, no shifting needed:

folderArray=(sdb sdc sdd sde sdf sdg)

for folder in "${folderArray[@]}"; do
    mkdir "/data/$folder"
done

Solution 4

You don't need any loop for that:

folderArray=(sdb sdc sdd sde sdf sdg)
IFS=,
eval mkdir /data/{"${folderArray[*]}"}

The trick is that if an array is double-quoted with subscript * ("${array[*]}") it expands to a single word with the value of each array element separated by the first character of the IFS variable. After that we use brace expansion mechanism to attach /data/ to each array member and evaluate the whole thing.

Share:
17,700
yael
Author by

yael

Updated on September 18, 2022

Comments

  • yael
    yael over 1 year

    we want to build 6 mount point folders as example

    /data/sdb
    /data/sdc
    /data/sdd
    /data/sde
    /data/sdf
    /data/sdg
    

    so we wrote this simple bash script using array

    folder_mount_point_list="sdb sdc sdd sde sdf sdg"
    
    folderArray=( $folder_mount_point_list )
    
    counter=0
    for i in disk1 disk2 disk3 disk4 disk4 disk5 disk6
    do
    folder_name=${folderArray[counter]}
    mkdir /data/$folder_name
    let counter=$counter+1
    done
    

    now we want to change the code without counter and let=$counter=counter+1

    is it possible to shift each loop the array in order to get the next array value?

    as something like

    ${folderArray[++]}
    
    • RomanPerekhrest
      RomanPerekhrest over 6 years
      what's for i in disk1 disk2 disk3 disk4 disk4 disk5 disk6 for as it's not being used within loop body?
  • yael
    yael over 6 years
    can we do the set for variable as set -- $list_of_folders ( while list_of_folders="sdb sdc sdd sde"
  • Hauke Laging
    Hauke Laging over 6 years
    @yael Yes, you can use set -- $list_of_folders but again: String variables are not the way to go: set -- "${folders[@]}"
  • Hauke Laging
    Hauke Laging over 6 years
    Why so complicated? cd /data ; mkdir "${folderArray[@]}" I have done the same before but I would not in a case like this. But +1 for the advanced approach.
  • jimmij
    jimmij over 6 years
    @HaukeLaging Yes, that would be simpler in case of mkdir command. And and even array is not needed, just cd /data; mkdir abc def as normal person would do. But could not be as simple for other tasks, so it is good to know how to quickly attach a string to each array element without a loop.
  • Alessio
    Alessio over 6 years
    why are you even using set -- ....? that hack is only needed in shells that don't support arrays - there's no need for it in a shell that does supports arrays. for folder_name in "${folderArray[@]}"; do ... ; done is all that's needed.
  • yael
    yael over 6 years
    just one question , is there any choice to do something like - ${folderArray[++]}
  • yael
    yael over 6 years
    just one question , is there any choice to do something like - ${folderArray[++]}
  • jimmij
    jimmij over 6 years
    @yael You can do something like echo "${folderArray[((counter++))]}" if you really like this approach. Stuff inside (()) is evaluated as math (notice lack of $ in front of counter).
  • Alessio
    Alessio over 6 years
    and when you start writing shell code like that, you realise that putting a little time into learning perl or python would be a good idea. i.e. just because you can do something with bash, doesn't mean you should.
  • PesaThe
    PesaThe over 6 years
    @cas that's what I have in my answer. I don't get this set approach either.
  • Hauke Laging
    Hauke Laging over 6 years
    @cas The OP explicitly asked for "shift". But shift works with positional parameters only. Of course, maybe I misunderstood the OP by taking that expression literally.
  • PesaThe
    PesaThe over 6 years
    @HaukeLaging I think OP just wanted to get rid of the counter while preserving the same functionality. That doesn't necessarily mean OP wants to shift the array. I think it's just an unlucky phrasing :)
  • U. Windl
    U. Windl about 3 years
    Strictly speaking you are not shifting the array, but re-assigning it to a subset of itself. For large arrays that may be inefficient.
  • ilkkachu
    ilkkachu about 3 years
    @U.Windl, that's probably why the "shift" there is in quotes. And the answer does say that indexing as usual might be better... But since there's no operation to directly shift an array in the shell (it's not Perl), that's what you can do. If you have a large dataset and care about performance, you probably shouldn't use the shell to begin with.
  • U. Windl
    U. Windl about 3 years
    Well there's "almost" a shift operator. See unix.stackexchange.com/a/648243/320598
  • ilkkachu
    ilkkachu about 3 years
    @U.Windl, as you noticed, it doesn't shift the indexes, just removes one of them. The only point for shifting I can see is to be able to point to the first element using an unchanging index. With the positional parameters, you can do that, since after a shift, the new first arg is $1. Similarly shift @a in Perl makes $a[0] the new first element of the array. But that doesn't work with unset "x[0]", you still need to keep track of the index of the element you want to access, and then you don't even need the "shifting". So, just indexing as usual might be better.
  • Steffen Heil
    Steffen Heil over 2 years
    Instead of i=0; while [ "$i" -lt "${#a[@]}" ]; do it would be simpler and probably faster to do for i in {0..$((${#a[@]}-1))}; do.
  • ilkkachu
    ilkkachu over 2 years
    @SteffenHeil, maybe. The problem is that it pretty much only works in ksh as Bash does brace expansion before variable expansion (rather a useless order, IMO, but here we are). {1..${#a[@]}} works in zsh, though, and one could use "${!a[@]}" also with a regular array (in Bash and ksh).