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 :