1

I'm trying to write a backup script that takes a specific list of disk's UUIDs, mounts them to specified points, rsyncs the data to a specified end point, and then also does a bunch of other conditional checks when that's done. But since there will be a lot of checking after rsync, it would be nice to set each disk's UUID and associated/desired mount point as strings to save hardcoding them throughout the script, and also if say the UUIDs change in future (if a drive is updated or swapped out), this will be easier to maintain the script...

I've been looking at arrays in bash, to make the list of wanted disks, but have questions about how to make this possible, as my experience with arrays is non-existent!

The list of disks---in order of priority wanted to backup---are:

# sdc1  2.7T    UUID 8C1CC0C19D012E29   /media/user/Documents/
# sdb1  1.8T    UUID 39CD106C6FDA5907   /media/user/Photos/
# sdd1  3.7T    UUID 5104D5B708E102C0   /media/user/Video/

... and notice I want to go sdc1, sdb1, sdd1 and so on, (i.e. custom order).

Is it possible to create this list, in order of priority, so it's something like this?

DisksToBackup
        └─1
          └─UUID => '8C1CC0C19D012E29'
          └─MountPoint => '/media/user/Documents/'
        └─2
          └─UUID => '39CD106C6FDA5907'
          └─MountPoint => '/media/user/Photos/'
        └─3
          └─UUID => '5104D5B708E102C0'
          └─MountPoint => '/media/user/Video/'

OR some obviously better idea than this...

And then how to actually use this?

Let's say for example, how to go through our list and mount each disk (I know this is incorrect syntax, but again, I know nothing about arrays:

mount --uuid $DisksToBackup[*][UUID] $DisksToBackup[*][MountPoint]?

Update: Using Linux Mint 19.3

Output of bash --version gives: GNU bash, version 4.4.20(1)

nooblag
  • 678
  • 3
  • 23
  • Which operating system are you on? (I usually use/recommend `blkid` for enumeration purposes in an easy-to-parse way, but can't speak definitively without knowing which specific tools your OS provides). – Charles Duffy Aug 17 '20 at 21:41
  • That said, speaking to what bash-the-language can do: There is no multidimensional array support, but generally it's not needed, as long as you have a new enough version to support associative arrays. Map one (numerically-indexed) array from order to UUID, another (associative) one from UUID to mount point, and there you are. – Charles Duffy Aug 17 '20 at 21:42
  • Thanks. `blkid` I didn't know about that. It looks potentially helpful. The question becomes though, how could I utilise that output to search for matches and put what I'm after into strings? I'm a complete noob to handling strings/data... – nooblag Aug 17 '20 at 21:49
  • Well, let's back up. When you say "search for matches", what's a match? And what exactly is the thing you're after you want to store? – Charles Duffy Aug 17 '20 at 21:50
  • Okay great. So a match would be any of the UUIDs in the list (i.e. 8C1CC0C19D012E29 and 39CD106C6FDA5907 and 5104D5B708E102C0) as these are the disks I know I'm definitely after for now, so if they exist in `blkid` output could we put each matching UUID in $uuid; and 2nd to that would be the UUIDs associated `/dev/`, let's put that in $disk; and then lastly, the `LABEL` would be helpful to keep things human readable, in $label? – nooblag Aug 17 '20 at 21:54
  • I don't see how your comment really answers any questions. I mean, yes, you want to store that data, but that doesn't say anything about how to organize it into arrays &c. – Charles Duffy Aug 17 '20 at 22:01
  • re: `mount --uuid`, there's no reason to reinvent any wheels. `findmnt` already exists. – Charles Duffy Aug 17 '20 at 22:40

2 Answers2

1

As an example of how one could read this data into a series of arrays:

#!/usr/bin/env bash
i=0
declare -g -A "disk$i"
declare -n currDisk="disk$i"
while IFS= read -r line || (( ${#currDisk[@]} )); do : "line=$line"
  if [[ $line ]]; then
    if [[ $line = *=* ]]; then
      currDisk[${line%%=*}]=${line#*=}
    else
      printf 'WARNING: Ignoring unrecognized line: %q\n' "$line" >&2
    fi
  else
    if [[ ${#currDisk[@]} ]]; then
      declare -p "disk$i" >&2  # for debugging/demo: print out what we created
      (( ++i ))
      unset -n currDisk
      declare -g -A "disk$i=( )"
      declare -n currDisk="disk$i"
    fi
  fi
done < <(blkid -o export)

This gives you something like:

declare -g -A disk0=( [PARTLABEL]="primary" [UUID]="1111-2222-3333" [TYPE]=btrfs ...)
declare -g -A disk1=( [PARTLABEL]="esp" [LABEL]="boot" [TYPE]="vfat" ...)

...so you can write code iterating over them doing whatever search/comparison/etc you want. For example:

for _diskVar in "${!disk@}"; do    # iterates over variable names starting with "disk"
  declare -n _currDisk="$_diskVar" # refer to each such variable as _currDisk in turn
  # replace the below with your actual application logic, whatever that is
  if [[ ${_currDisk[LABEL]} = "something" ]] && [[ ${_currDisk[TYPE]} = "something_else" ]]; then
    echo "Found ${_currDisk[DEVNAME]}"
  fi
  unset -n _currDisk  # clear the nameref when done with it
done
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
1

Bash starting with version 4 provides associative arrays but only using a single dimension. Multiple dimensions you would have to simulate using keys such as 'sdc1-uuid' as shown in the following interactive bash examples (remove leading $ and > and bash output when putting into a script).

$ declare -A disks
$ disks=([0-uuid]=8C1CC0C19D012E29 [0-mount]=/media/user/Documents/
>        [1-uuid]=39CD106C6FDA5907 [1-mount]=/media/user/Photos/)
$ echo ${disks[sdc1-uuid]}
8C1CC0C19D012E29
$ echo ${disks[*]}
/media/user/Documents/ 39CD106C6FDA5907 8C1CC0C19D012E29 /media/user/Photos/
$ echo ${!disks[*]}
0-mount 0-uuid 1-uuid 1-mount

However, there is no ordering for the keys (the order of the keys differs from the order in which we defined them). You may want to use a second array as in the following example which allows you to break down the multiple dimensions as well:

$ disks_order=(0 1)
$ for i in ${disks_order[*]}; do
> echo "${disks[$i-uuid]} ${disks[$i-mount]}"
> done
8C1CC0C19D012E29 /media/user/Documents/
39CD106C6FDA5907 /media/user/Photos/

In case you use bash version 3, you need to simulate the associative array using other means. See the question on associative arrays in bash 3 or simply represent your structure in a simple array such as which makes everything more readable anyway:

$ disks=(8C1CC0C19D012E29=/media/user/Documents/
>        39CD106C6FDA5907=/media/user/Photos/)
$ for disk in "${disks[@]}"; do
>   uuid="${disk%=*}"
>   path="${disk##*=}"
>   echo "$uuid $path"
> done
8C1CC0C19D012E29 /media/user/Documents/
39CD106C6FDA5907 /media/user/Photos/

The %=* is a fancy way of saying remove everything after (and including) the = sign. And ##*= to remove everything before (and including) the = sign.

Tom
  • 749
  • 4
  • 16