21

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[++]}
yael
  • 13,106

5 Answers5

32

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
ilkkachu
  • 138,973
  • Strictly speaking you are not shifting the array, but re-assigning it to a subset of itself. For large arrays that may be inefficient. – U. Windl May 05 '21 at 13:34
  • 1
    @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. – ilkkachu May 05 '21 at 14:14
  • Well there's "almost" a shift operator. See https://unix.stackexchange.com/a/648243/320598 – U. Windl May 06 '21 at 05:48
  • @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. – ilkkachu May 06 '21 at 09:55
  • 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. – Steffen Heil Jan 02 '22 at 13:25
  • @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). – ilkkachu Jan 02 '22 at 15:41
11

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
Hauke Laging
  • 90,279
  • can we do the set for variable as set -- $list_of_folders ( while list_of_folders="sdb sdc sdd sde" – yael Dec 31 '17 at 13:48
  • @yael Yes, you can use set -- $list_of_folders but again: String variables are not the way to go: set -- "${folders[@]}" – Hauke Laging Dec 31 '17 at 14:02
  • 1
    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. – cas Dec 31 '17 at 14:12
  • just one question , is there any choice to do something like - ${folderArray[++]} – yael Dec 31 '17 at 14:16
  • @cas that's what I have in my answer. I don't get this set approach either. – PesaThe Dec 31 '17 at 20:12
  • @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. – Hauke Laging Dec 31 '17 at 20:51
  • @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 :) – PesaThe Dec 31 '17 at 23:42
4

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
PesaThe
  • 583
  • 3
  • 7
3

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.

jimmij
  • 47,140
  • 1
    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. – Hauke Laging Dec 31 '17 at 14:03
  • @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. – jimmij Dec 31 '17 at 14:11
  • just one question , is there any choice to do something like - ${folderArray[++]} – yael Dec 31 '17 at 14:16
  • 1
    @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). – jimmij Dec 31 '17 at 14:22
  • 1
    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. – cas Dec 31 '17 at 14:27
0

The question you are asking is how to use ++ to increment an array index.

Like this:

folderArray=( sdb sdc sdd sde sdf sdg  )

counter=0

for i in disk1 disk2 disk3 disk4 disk5 disk6; do folder_name=${folderArray[counter++]} echo mkdir /data/$folder_name done

The index of an array (what's inside the []) is in an Arithmetic environment, so the post increment (++) work.


The question on the title use the term shift. Shifting like the positional arguments could be done by re-building the array (a slow process).

folderArray=( sdb sdc sdd sde sdf sdg  )

shiftArray=( "${folderArray[@]}" )

counter=0 for i in disk1 disk2 disk3 disk4 disk5 disk6; do echo mkdir /data/$shiftArray

shiftArray=( "${shiftArray[@]:1}" )

done


But it seems simpler to use the array indexes directly:

folderArray=( sdb sdc sdd sde sdf sdg  )

for((i=0;i<${#folderArray[@]};)); do echo mkdir /data/"${folderArray[i++]}" done