skip to Main Content

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

2

Answers


  1. 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 :
    
    Login or Signup to reply.
  2. 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.
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search