#!/bin/bash -
# File: create-dpkg-list.sh
# Copyright (c) 2018-2019 Justin Hanekom
# Licensed under the MIT License
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Setup a safe Bash scripting environment
set -o errexit # Exit immediately if an error occurs
set -o noclobber # Do not allow files to be overwritten via redirect
set -o nounset # Do not allow unset variables
# Set the exit code of a pipeline to the rightmost non-zero on error
set -o pipefail
#set -o xtrace # Trace script execution (i.e., debug mode)
# Set the internal field separator to newline or tab, but not space
IFS=$'\n\t'
# Setup a secure Bash scripting environment by: setting a secure path;
# clearing all aliases; clearing the command path hash; setting the hard limit
# to 0 to turn off core dumps; and setting a secure umask
PATH=$(PATH='/bin:/usr/bin' getconf PATH); export PATH
builtin unalias -a
hash -r
ulimit -H -c 0 --
UMASK=002
umask ${UMASK}
# Global constant definitions
readonly OPTIONS='u:d:p:s:rvh'
readonly LONGOPTS='user:,destdir:,prefix:,suffix:,remove,verbose,help'
readonly DEF_PREFIX='dpkg_'
readonly DEF_SUFFIX='.txt'
readonly TIMESTAMP=$(date '+%Y%m%d%H%M%S')
readonly STARTED_AT=$(date '+%s')
# Global variable declarations
USER_NAME=''
DESTDIR=''
PREFIX="${DEF_PREFIX}"
SUFFIX="${DEF_SUFFIX}"
IS_REMOVE=''
IS_VERBOSE=''
################################################################################
# Function: chomp_slash
# Description: Removes any trailing slash ("/") from a string
# Arguments: $1 :- String from which to remove any trailing slashes
# Ouputs: Prints string with trailing slashes removed
function chomp_slash {
local dir="$1"
while [ "${dir:(-1)}" = '/' ]; do
dir=${dir::-1}
done
echo "${dir}"
}
################################################################################
# Function: echo_if_verbose
# Description: Prints $@ if IS_VERBOSE is not empty
# Arguments: $@ :- Content to echo if ${IS_VERBOSE} is not empty
# Requires: ${IS_VERBOSE} set to a non-empty string if verbose is on
# Outputs: Prints $@ if ${IS_VERBOSE} is not empty
function echo_if_verbose {
if [ -n "${IS_VERBOSE}" ]; then
echo "$@"
fi
}
################################################################################
# Function: trim
# Description: Trims any/all whitespace from the beginning and end of a string
# Arguments: $1 :- The string from which to trim whitespace
# Outputs: Prints string with any leading/trailing whitespace removed
function trim {
local str="$1"
str="${str#"${str%%[![:space:]]*}"}"
str="${str%"${str##*[![:space:]]}"}"
echo "$1"
}
################################################################################
# Function: usage_exit
# Description: Prints the usage message for this script and then exits
# Arguments: $1 :- Exit code; defaults to 1
# Outputs: Prints description of how to call this script
function usage_exit {
local exit_code=1
if [[ ${1-} =~ ^[0-9]+$ ]]; then
exit_code="$1"
fi
cat << EOT
Usage: $(basename "$0") [options]...
-u|--user (Required) The owner of the directory and content
-d|--destdir (Required) The directory whose owner has to be set
-p|--prefix The prefix to give the dpkg list; default 'dpkg_'
-s|--suffix The suffix to give the dpkg list; defaults to '.txt'
-r|--remove Causes previous dpkg list files to be removed
-v|--verbose Displays verbose output
-h|--help Displays this message and exits the script
NOTE: The and arguments are mandatory and *must* be supplied
EOT
exit "${exit_code}"
}
################################################################################
# Function: parse_cmd_line
# Description: Parses the command-line using the enhanced version of getopt
# Arguments: $@ :- Command-line arguments (required)
# Requires: Global variables $USER_NAME, $DESTDIR, $PREFIX, $SUFFIX,
# $IS_REMOVE, and $S_VERBOSE
# Outputs: Sets above-mentioned global variables based on given
# command-line arguments
function parse_cmd_line {
# Ensure that the enhanced version of getopt is available
! getopt --test > /dev/null
if (( "${PIPESTATUS[0]}" != 4 )); then
echo "I'm sorry, the enhanced version of getopt is required. Exiting." >&2
exit 1
fi
# Initialize the global variables
USER_NAME=''
DESTDIR=''
PREFIX="${DEF_PREFIX}"
SUFFIX="${DEF_SUFFIX}"
IS_REMOVE=''
IS_VERBOSE=''
# Parse the command-line options
if ! readonly PARSED_OPTIONS=$(getopt --options=${OPTIONS} \
--longoptions=${LONGOPTS} \
--name "$(basename "$0")" -- "$@"); then
echo 'Unknown error parsing getopt options. Exiting.' >&2
exit 2
fi
eval set -- "${PARSED_OPTIONS}"
# Extract command-line options and their arguments, if any
while (( $# >= 1 )); do
case "$1" in
-u|--user)
USER_NAME=$(trim "$2") ; shift 2
;;
-d|--destdir)
DESTDIR=$(chomp_slash "$2") ; shift 2
;;
-p|--prefix)
PREFIX=$(trim "$2"); shift 2
;;
-s|--suffix)
SUFFIX=$(trim "$2"); shift 2
;;
-r|--remove)
IS_REMOVE='true' ; shift
;;
-v|--verbose)
IS_VERBOSE='true' ; shift
;;
-h|--help)
usage_exit 0
;;
--)
shift ; break
;;
*)
usage_exit 3 >&2
;;
esac
done
# Ensure that required options have been supplied
if [[ -z "${USER_NAME}" || -z "${DESTDIR}" ]]; then
usage_exit 4 >&2
fi
} # parse_cmd_line
################################################################################
# Start of program
parse_cmd_line "$@"
readonly DEST_FILENAME="${DESTDIR}/${PREFIX}${TIMESTAMP}${SUFFIX}"
# Optionally remove any previous dpkg list file
if [ -n "$IS_REMOVE" ]; then
sudo rm --force "${DESTDIR}"/"${PREFIX}"*"${SUFFIX}" &>/dev/null
echo_if_verbose "Removed: ${DESTDIR}/${PREFIX}*${SUFFIX}"
fi
# Create the dpkg list file
dpkg -l | grep --extended-regexp '^ii' | \
perl -F'\s\s+' -alne 'printf("%s [%s] %s\n",$F[1],$F[2],splice(@F,4))' | \
sudo tee "${DEST_FILENAME}" 1>/dev/null
echo_if_verbose "Stored 'dpkg -l' to: ${DEST_FILENAME}"
# Change the owner of the generated dpkg list file
sudo chown "${USER_NAME}":"${USER_NAME}" "${DEST_FILENAME}"
echo_if_verbose "Changed owner of: ${DEST_FILENAME} to: ${USER_NAME}"
# Report that we are done, and how long the script took
if [ -n "$IS_VERBOSE" ]; then
readonly ENDED_AT=$(date '+%s')
readonly DIFF=$(( ENDED_AT - STARTED_AT ))
echo "Done, in ${DIFF} seconds"!
fi
# vim: set filetype=sh smartindent autoindent smarttab expandtab tabstop=4 softtabstop=4 shiftwidth=4 autoread