Home > Blockchain >  Indirect reference to hashmap in bash
Indirect reference to hashmap in bash

Time:10-29

I am writing a script to install packages from .deb files, but first, I would like to check if each package is already installed. I have a config file that contains the information for the packages as hashmaps, like this:

declare -A package_a=(
[name]="utility-blah"
[ver]="1.2"
[arch]="amd64"
)
declare -A package_b=(
[name]="tool-bleh"
[ver]="3.4"
[arch]="all"
)
#and so on and so forth

My install script sources the config file, and I would like it to iterate over the packages, checking if they are installed, and installing them if they are not, like this:

source packages.config
declare -a packageList=("package_a" "package_b" "package_d")
for package in ${packageList[@]}; do
    # Check if the specific version is installed already
    if apt show ${package[name]}=${package[ver]}; then
        echo ${package[name]} ${package[ver]} is already installed.
    else
        echo Installing ${package[name]}
        sudo apt install path/to/files/${package[name]}_${package[ver]}_${package[arch]}.deb
    fi
done    

How can I have package point to the hashmap containing the information about the package and use it in the following commands?

I'm using Bash 4.4.20 on Ubuntu 18.04

CodePudding user response:

This kind of input data is better suited for JSON rather than using bash associative arrays and indirection.

Lets say you have a packages.json:

{
  "packages": [
    {
      "package": "package_a",
      "name": "utility-blah",
      "ver": "1.2",
      "arch": "amd64"
    },
    {
      "package": "package_b",
      "name": "utility-bleh",
      "ver": "3.4",
      "arch": "all"
    },
    {
      "package": "apache2",
      "name": "Apache2 http server",
      "ver": "2.4.52-1ubuntu4.1",
      "arch": "all"
    }
  ]
}

Such simple POSIX-shell script is able to process it as you need:

#! /bin/sh

# Fields are tab-delimited, records end with newline
IFS=$(printf '\t')

# Parses json input into record lines
jq -r '.packages[]|(.package   "\t"   .name   "\t"   .ver)' packages.json |

  # Iterates records, reading fields
  while read -r package name ver; do
    {
      # Query package for installed status and version
      # formatted into two fields
      dpkg-query -W --showformat='${db:Status-Abbrev}\t${Version}' "${package}" || :
    } 2>/dev/null | {

      # Reads status and installed version
      read -r status installed_ver _

      # If status is installed 'ii ' and installed version matches'
      if [ "${status}x" = 'ii x' ] && [ "${ver}x" = "${installed_ver}x" ]; then
        printf '%s %s is already installed.\n' "${name}" "${ver}"
      else
        printf 'Installing %s.\n' "${name}"
      fi
    }
  done

Example output:

nstalling utility-blah.
nstalling utility-bleh.
Apache2 http server 2.4.52-1ubuntu4.1 is already installed.

CodePudding user response:

One idea using a nameref:

source packages.config
declare -a packageList=("package_a" "package_b" "package_d")

for pkg in "${packageList[@]}"; do                                      # change variable name
    declare -n package=pkg                                            # declare nameref; rest of code remains the same ...

    # Check if the specific version is installed already
    if apt show ${package[name]}=${package[ver]}; then
        echo ${package[name]} ${package[ver]} is already installed.
    else
        echo Installing ${package[name]}
        sudo apt install path/to/files/${package[name]}_${package[ver]}_${package[arch]}.deb
    fi
done  

Or (as M. Nejat Aydin and Benjamin W. have pointed out) the declare -n can go before the while loop, eg:

declare -n package

for package in "${packageList[@]}"; do                                          
    # Check if the specific version is installed already
    if apt show ${package[name]}=${package[ver]}; then
        echo ${package[name]} ${package[ver]} is already installed.
    else
        echo Installing ${package[name]}
        sudo apt install path/to/files/${package[name]}_${package[ver]}_${package[arch]}.deb
    fi
done  

Simple test:

declare -n package

for package in ${packageList[@]}; do 
    echo "${!package} : ${package[name]}"
done

This generates:

package_a : utility-blah
package_b : tool-bleh
package_d :
  • Related