0

My goal is to create a list of partitions for each block device listed in /sys/block;

#!/bin/bash
block_devices_list=($(ls /sys/block))
partition_list=($(cat /proc/partitions | awk '{print $4}'))
unset partition_list[0]

for block_device in ${block_devices_list[@]}; do
    for partition in ${partition_list[@]}; do
        partitions+=($(echo $partition | grep $block_device))
    done
    # Right here?
    unset partitions
done

Every time the outside 'for loop' completes it's cycle it ends up with an array of partitions for a particular block device. At that point I would like to transfer that data to a separate array, dynamically named after the device it belongs to (like 'partitions_sda' for example).

I have read a few questions/answers about 'dynamic' variable names, 'associative' arrays and whatnot but don't seem to be able to figure this out. Any help much appreciated.

7
  • Why unnecessary sub-shelling? block_devices_list=$(ls /sys/block), or preferably block_devices_list=$(lsblk -d)etc.. Commented Aug 7, 2017 at 13:18
  • @Anubis The outside bracket in block_devices_list=($(ls /sys/block)) is needed to save the result as an array. Commented Aug 7, 2017 at 13:31
  • Ah, I thought I saw a sub-shell. Anyway I believe you wont need to invoke all those different tools for what you are trying to do. You can do the same by just parsing the lsblk output and some bash magic. see my answer below. Commented Aug 7, 2017 at 14:20
  • @Anubis, block_devices_list=( /sys/block/* ) avoids parsing ls, which is widely (and rightly) considered bad practice; if one wants to avoid the directory-name prefixes, one can then run block_devices_list=( "${block_devices_list[@]#/sys/block/}" ) Commented Aug 7, 2017 at 17:04
  • Which specific version of bash? You've got more/better options with 4.3 or later. Commented Aug 7, 2017 at 17:06

3 Answers 3

0

As a best-practices example (for bash 4.3 or newer):

#!/bin/bash

for blockdev in /sys/block/*; do
  devname=${blockdev##*/}               # remove leading path elements
  devname=${devname//-/_}               # rename dashes in device name to underscores
  declare -a "partitions_${devname}=()" # define an empty array
  declare -n _current_partitions="partitions_$devname"        # define a nameref
  for part in "$blockdev"/*/dev; do   # iterate over partitions
    [[ -e $part ]] || continue                                # skip if no matches
    part=${part%/dev}                                         # strip trailing /dev
    _current_partitions+=( "${part##*/}" )                    # add match via nameref
  done
  unset -n _current_partitions          # clear the nameref
  declare -p "partitions_$devname"      # print our resulting array
done

For my local test VM, this emits:

declare -a partitions_dm_0=()
declare -a partitions_dm_1=()
declare -a partitions_sda=([0]="sda1" [1]="sda2")
declare -a partitions_sdb=()
declare -a partitions_sr0=()

...which is correct, as sda is the only partitioned device.


The basic mechanism here is the nameref: declare -n name1=name2 will allow one to refer to the variable name2 under the name name1, including updates or assignments, until unset -n name1 is performed.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for suggesting nameref, seems like the right tool for the job. This is an improvement to my poor little script I wouldn't dare to ask for. Danke schön.
"$blockdev"/"${blockdev##*/}"[[:digit:]]* this way it does not recognise the sd card's mmcblk0 partitions mmcblk0p1 and mmcblk0p2 :/
@soocki, oooh, good catch. I've updated this with a less-fragile heuristic.
0

Not sure I understood what you are trying to do but this is an example that you can, maybe, start with:

#!/bin/bash
list1=(a b c)
list2=(j k l)
for x in ${list1[@]}; do
    for y in ${list2[@]}; do
        tmplist+=(${x}${y})
    done
    cmd="declare -a list_${x}=(${tmplist[@]})"
    eval $cmd
    unset tmplist
done
echo "list_a: ${list_a[@]}"
echo "list_b: ${list_b[@]}"
echo "list_c: ${list_c[@]}"

It shows how to create arrays which names are computed from other variables, how to unset a temporary array...

12 Comments

Perhaps I wasn't making myself clear but creating arrays which names are computed from other variables is exactly what I was after. So you need to declare a 'command' cmd and eval it. Interesting stuff. Your solution adapted to my example works like a charm, thank you.
Beware, if your variable values (block devices, partition names) contain special characters, my example code probably needs improvements.
I suppose I'll cross that bridge when I get there but does it involve much tinkering in case I do?
Well, if your block devices have names with special characters (spaces, dashes...) your first difficulty will probably be with invalid bash variable names (list_${x} in my example). You will then have to normalize these names or to generate valid names and associate them to the real names, using, e.g. another associative array (declare -A)...
... Another potential problem is with the names of your partitions. If they contains spaces, for instance, (${tmplist[@]}) will evaluate as more array entries than expected. Use cmd="declare -a list_${x}=(\"\${tmplist[@]}\")", instead, or something equivalent, and it should work.
|
0

I would create two types of arrays. One for containing disk names, then different arrays for each disk for storing partition specific information.

#!/usr/bin/env bash                                                                                                                                   
while read -r l; do
    t="${l#* }"
    if [[ $t == "disk" ]]; then
        # disks list contains all the disk names
        disks+="${l% *} "
    elif [[ $t == "part" ]]; then
        # for each disk 'XXX', a separate partitions array 'parts_XXX' is created
        [[ $l =~ [^a-z0-9]*([a-z]*)([0-9]*)\  ]] && d="${BASH_REMATCH[1]}" && p="${BASH_REMATCH[2]}"
        eval parts_$d+=\"my-value=\$d\$p \" # append to partitians array
    else
        echo "unknown type $t in $l"
        exit 1
    fi  
done <<< $(lsblk -n --output=NAME,TYPE | tr -s ' ')

# arrays are created. now iterate them
for i in ${disks[@]}; do
    echo "iterating partitions of disk: $i"
    # following is the name of the current disks partitions array
    var=parts_$i[@]
    # iterate partitians array of the current disk
    for j in ${!var}; do
        echo ">> $j"
    done
done

1 Comment

That's a different beast altogether. Let me have a look.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.