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.

muthuh
  • 619
  • 7
  • 14
  • Why unnecessary sub-shelling? `block_devices_list=$(ls /sys/block)`, or preferably `block_devices_list=$(lsblk -d)`etc.. – Anubis Aug 07 '17 at 13:18
  • @Anubis The outside bracket in `block_devices_list=($(ls /sys/block))` is needed to save the result as an array. – muthuh Aug 07 '17 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. – Anubis Aug 07 '17 at 14:20
  • @Anubis, `block_devices_list=( /sys/block/* )` avoids [parsing `ls`](http://mywiki.wooledge.org/ParsingLs), 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/}" )` – Charles Duffy Aug 07 '17 at 17:04
  • Which specific version of bash? You've got more/better options with 4.3 or later. – Charles Duffy Aug 07 '17 at 17:06
  • BTW, this question is arguably duplicative of [Assign to a bash array variable indirectly, by dynamically constructed variable name](https://stackoverflow.com/questions/23819839/assign-to-a-bash-array-variable-indirectly-by-dynamically-constructed-variable) – Charles Duffy Aug 07 '17 at 17:25
  • if it's a duplicate it's due to a fact I didn't xnow what I'm looking for until I found it. Sweet ignorance. – muthuh Aug 08 '17 at 08:34

3 Answers3

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

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • 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. – muthuh Aug 07 '17 at 13:46
  • Beware, if your variable values (block devices, partition names) contain special characters, my example code probably needs improvements. – Renaud Pacalet Aug 07 '17 at 13:55
  • I suppose I'll cross that bridge when I get there but does it involve much tinkering in case I do? – muthuh Aug 07 '17 at 14:00
  • 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`)... – Renaud Pacalet Aug 07 '17 at 14:22
  • ... 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. – Renaud Pacalet Aug 07 '17 at 14:28
  • Thank you Ill keep it in mind. – muthuh Aug 07 '17 at 16:55
  • Need more quotes for the initial loop to be correct. `for x in "${list1[@]}"` -- otherwise you have exactly the same (buggy) behavior as `for x in ${list1[*]}`. – Charles Duffy Aug 07 '17 at 17:05
  • I'd **strongly** suggest using namerefs -- as added in bash 4.3 -- to get the desired effect with less hackery involved. – Charles Duffy Aug 07 '17 at 17:06
  • `cmd="declare -a list_${x}=(${tmplist[@]})"; eval "$cmd"` is just buggy. Try it with `x=foo; tmplist=( "hello world" "goodbye world" )` and see what you get. – Charles Duffy Aug 07 '17 at 17:11
  • (hint: You'll get `list_foo=( "hello" "world" "goodbye" "world" )`, having entirely lost the original quoting). – Charles Duffy Aug 07 '17 at 17:23
  • @CharlesDuffy Thanks for your wise suggestions of improvement. However, if you take 1 minute to read my answer, which is just an example, you will see that there is no bug and no need for quotes in **this** example. In one more minute you can copy-paste it and see that it works perfectly. And if you have 2 more minutes you can read the other comments, some of them elaborate on this. – Renaud Pacalet Aug 08 '17 at 08:43
  • @RenaudPacalet, when you say "this example", do you mean this data? The whole point of being a teaching resource is to teach people practices they're going to be using elsewhere, where the original constraints (particularly including *implied* constraints) may not hold. If an answer comes with limitations on the scope where it's useful without unintended side effects, *at barest minimum* those limitations should be called out explicitly, rather than left for folks relying on said answer to discover by accident. – Charles Duffy Aug 08 '17 at 14:56
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
Anubis
  • 6,995
  • 14
  • 56
  • 87
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.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 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. – muthuh Aug 08 '17 at 08:40
  • `"$blockdev"/"${blockdev##*/}"[[:digit:]]*` this way it does not recognise the sd card's `mmcblk0` partitions `mmcblk0p1` and `mmcblk0p2` :/ – muthuh Aug 09 '17 at 13:25
  • @soocki, oooh, good catch. I've updated this with a less-fragile heuristic. – Charles Duffy Aug 09 '17 at 14:36