packages bug fix; added pkgi and pkgr

This commit is contained in:
2026-10-13 14:09:00 +02:00
parent 3fbe9e85e2
commit f78bfe5859
4 changed files with 326 additions and 2 deletions
+2
View File
@@ -128,6 +128,8 @@ A bar-style prompt showing current time, execution time of the last command
| `get_pkgmgr` | packages | Detect the active package manager of the running distribution (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`) | | `get_pkgmgr` | packages | Detect the active package manager of the running distribution (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`) |
| `pinfo` | info | Display system information read from `/proc` and `/sys` only | | `pinfo` | info | Display system information read from `/proc` and `/sys` only |
| `pkgf` | packages | Find which installed package owns a given file (distro-aware via `get_pkgmgr`) | | `pkgf` | packages | Find which installed package owns a given file (distro-aware via `get_pkgmgr`) |
| `pkgi` | packages | Install packages with the distribution's manager (distro-aware via `get_pkgmgr`); `-y` assumes yes, `-n` only shows the command; uses `sudo` (or `doas`) when not root |
| `pkgr` | packages | Uninstall packages; `-p`/`--purge` also deletes configuration files (apt, pacman, apk; other managers just remove), `-d`/`--deps` also removes dependencies no longer needed (`--auto-remove`, `-Rs`, `--clean-deps`, `depclean`, ... — apk always does, dnf/yum are told not to unless `-d`); `-y`, `-n` as `pkgi` |
| `pkgs` | packages | Search for a pattern in installed package names (distro-aware via `get_pkgmgr`) | | `pkgs` | packages | Search for a pattern in installed package names (distro-aware via `get_pkgmgr`) |
| `ppg` | processes | Look for the given pattern in running processes | | `ppg` | processes | Look for the given pattern in running processes |
| `ppn` | processes | List processes matching an exact command name | | `ppn` | processes | List processes matching an exact command name |
+5
View File
@@ -11,6 +11,9 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
### Added ### Added
- `pkgi` (install) and `pkgr` (uninstall, with `-p`/`--purge` and `-d`/`--deps`)
commands for apt, dnf/yum, zypper, pacman, apk, portage, xbps and nix; both
accept `-y`/`--yes` and `-n`/`--dry-run` and use `sudo` (or `doas`) when needed.
- `utaz -i` / `--ignore-case` and `-s` / `--case-sensitive` switches to match - `utaz -i` / `--ignore-case` and `-s` / `--case-sensitive` switches to match
archive extensions case-insensitively or not; the default is set by the new archive extensions case-insensitively or not; the default is set by the new
`UTAZ_DEFAULT_IGNORE_CASE` key of the `[compress]` section (default `0`). `UTAZ_DEFAULT_IGNORE_CASE` key of the `[compress]` section (default `0`).
@@ -106,6 +109,8 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
403, ...) are now failures instead of saved error pages; `urlencode` 403, ...) are now failures instead of saved error pages; `urlencode`
mis-encoded accents under `LC_ALL` and now uses `%20` for spaces; `isipv4` mis-encoded accents under `LC_ALL` and now uses `%20` for spaces; `isipv4`
leaked a variable. leaked a variable.
- `packages.sh`: `pkgf ls` now looks the command up in the `PATH`; `pkgs`
accepts patterns starting with `-`; `suse` is recognised in `ID_LIKE`.
- `compress.sh`: option flags and loop variables no longer leak into the - `compress.sh`: option flags and loop variables no longer leak into the
interactive shell. interactive shell.
+2
View File
@@ -102,6 +102,8 @@ help()
printf "myextip\t\tGet information about your public IP address\n" printf "myextip\t\tGet information about your public IP address\n"
printf "pinfo\t\tDisplay system information read from /proc and /sys only\n" printf "pinfo\t\tDisplay system information read from /proc and /sys only\n"
printf "pkgf\t\tFind which installed package owns a given file (distro-aware)\n" printf "pkgf\t\tFind which installed package owns a given file (distro-aware)\n"
printf "pkgi\t\tInstall packages (distro-aware, sudo/doas when needed)\n"
printf "pkgr\t\tUninstall packages; -p purges configuration, -d removes unneeded dependencies\n"
printf "pkgs\t\tSearch for a pattern in installed package names (distro-aware)\n" printf "pkgs\t\tSearch for a pattern in installed package names (distro-aware)\n"
printf "ppg\t\tLook for the given pattern in running processes\n" printf "ppg\t\tLook for the given pattern in running processes\n"
printf "ppn\t\tList processes matching an exact command name\n" printf "ppn\t\tList processes matching an exact command name\n"
+317 -2
View File
@@ -71,7 +71,7 @@ get_pkgmgr()
echo "yum" echo "yum"
return 0 return 0
;; ;;
opensuse*|sles|sled) opensuse*|suse|sles|sled)
echo "zypper" echo "zypper"
return 0 return 0
;; ;;
@@ -193,7 +193,8 @@ pkgs()
;; ;;
esac esac
"${list_cmd[@]}" | grep ${grep_opt:+"$grep_opt"} "$pkg" # -e: a pattern starting with "-" must not be read as a grep option
"${list_cmd[@]}" | grep ${grep_opt:+"$grep_opt"} -e "$pkg"
} }
export -f pkgs export -f pkgs
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -240,6 +241,15 @@ pkgf()
disp E "Please specify a file path." disp E "Please specify a file path."
return 1 return 1
} }
# A bare command name that is not a file here ("pkgf ls") is looked up in
# the PATH: the package managers expect a path, and would otherwise match
# the name as a pattern against every file of every package.
if [[ $file != */* && ! -e $file ]]; then
local _found
_found=$(command -v -- "$file" 2>/dev/null) && [[ $_found == /* ]] && file="$_found"
fi
# "./" keeps a name starting with "-" from being read as an option
[[ $file == -* ]] && file="./$file"
local pkgmgr local pkgmgr
pkgmgr=$(get_pkgmgr) || { pkgmgr=$(get_pkgmgr) || {
@@ -279,4 +289,309 @@ export -f pkgf
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Internal helper: run a package manager command, with root privileges when
# needed (sudo, else doas), or only display it in dry-run mode.
# Usage: _pkg_run <needs_root 0|1> <dry_run 0|1> <command> [args...]
_pkg_run()
{
local needs_root="$1" dry="$2"
shift 2
local -a prefix=()
if (( needs_root )) && [[ $EUID -ne 0 ]]; then
if command -v sudo >/dev/null 2>&1; then
prefix=(sudo)
elif command -v doas >/dev/null 2>&1; then
prefix=(doas)
elif (( ! dry )); then
disp E "Root privileges are required and neither sudo nor doas is available."
return 1
fi
fi
if (( dry )); then
# %q gives a copy-pasteable command line
printf '%q ' "${prefix[@]}" "$@"
printf '\n'
return 0
fi
if ! command -v "$1" >/dev/null 2>&1; then
disp E "'$1' is not installed."
return 1
fi
"${prefix[@]}" "$@"
}
export -f _pkg_run
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Install packages with the package manager of the distribution.
# Usage: pkgi [options] <package> [package...]
pkgi()
{
local assume_yes=0 dry_run=0
local PARSED
PARSED=$(getopt -o hyn --long help,yes,dry-run -n 'pkgi' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"pkgi --help\" to display usage."
return 1
fi
# Restore the getopt-normalised (shell-quoted) argument list
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "pkgi: Install packages (distro-aware via get_pkgmgr).\n\n"
printf "Usage: pkgi [options] <package> [package...]\n\n"
printf "Options:\n"
printf "\t-h, --help\tDisplay this help screen\n"
printf "\t-y, --yes\tDo not ask for confirmation (where the manager asks)\n"
printf "\t-n, --dry-run\tOnly display the command that would be run\n\n"
printf "Root privileges are obtained with sudo (or doas) when needed.\n"
return 0
;;
-y|--yes)
assume_yes=1
shift
;;
-n|--dry-run)
dry_run=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
if [[ $# -eq 0 ]]; then
disp E "Please specify at least one package name."
return 1
fi
local pkg
for pkg in "$@"; do
# Not passed after "--": not every manager understands it
if [[ $pkg == -* ]]; then
disp E "Invalid package name: '$pkg'."
return 1
fi
done
local pkgmgr
pkgmgr=$(get_pkgmgr) || {
disp E "No usable package manager could be detected on this system."
return 2
}
local -a cmd=()
local needs_root=1
case "$pkgmgr" in
apt)
cmd=(apt-get install)
(( assume_yes )) && cmd+=(-y)
;;
dnf|yum)
cmd=("$pkgmgr" install)
(( assume_yes )) && cmd+=(-y)
;;
zypper)
cmd=(zypper install)
(( assume_yes )) && cmd+=(-y)
;;
pacman)
cmd=(pacman -S)
(( assume_yes )) && cmd+=(--noconfirm)
;;
apk)
# apk add never asks for confirmation
cmd=(apk add)
;;
portage)
cmd=(emerge)
(( assume_yes )) || cmd+=(--ask)
;;
xbps)
cmd=(xbps-install -S)
(( assume_yes )) && cmd+=(-y)
;;
nix)
# nix-env works on the user's profile: no root needed
cmd=(nix-env -i)
needs_root=0
;;
*)
disp E "Package manager '$pkgmgr' is not supported by pkgi."
return 2
;;
esac
_pkg_run "$needs_root" "$dry_run" "${cmd[@]}" "$@"
}
export -f pkgi
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Uninstall packages with the package manager of the distribution.
# Usage: pkgr [options] <package> [package...]
# -p, --purge also delete the configuration files (apt purge, pacman -n,
# apk --purge); managers without such a mode just remove
# -d, --deps also remove the dependencies no longer needed. Without it,
# the managers that do it by default (dnf, yum) are told not to
# (apk always cleans up: it has no way to prevent it)
pkgr()
{
local purge=0 deps=0 assume_yes=0 dry_run=0
local PARSED
PARSED=$(getopt -o hpdyn --long help,purge,deps,yes,dry-run -n 'pkgr' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"pkgr --help\" to display usage."
return 1
fi
# Restore the getopt-normalised (shell-quoted) argument list
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "pkgr: Uninstall packages (distro-aware via get_pkgmgr).\n\n"
printf "Usage: pkgr [options] <package> [package...]\n\n"
printf "Options:\n"
printf "\t-h, --help\tDisplay this help screen\n"
printf "\t-p, --purge\tAlso delete configuration files (apt, pacman, apk)\n"
printf "\t-d, --deps\tAlso remove dependencies that are no longer needed\n"
printf "\t-y, --yes\tDo not ask for confirmation (where the manager asks)\n"
printf "\t-n, --dry-run\tOnly display the command that would be run\n\n"
printf "Root privileges are obtained with sudo (or doas) when needed.\n"
return 0
;;
-p|--purge)
purge=1
shift
;;
-d|--deps)
deps=1
shift
;;
-y|--yes)
assume_yes=1
shift
;;
-n|--dry-run)
dry_run=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
if [[ $# -eq 0 ]]; then
disp E "Please specify at least one package name."
return 1
fi
local pkg
for pkg in "$@"; do
if [[ $pkg == -* ]]; then
disp E "Invalid package name: '$pkg'."
return 1
fi
done
local pkgmgr
pkgmgr=$(get_pkgmgr) || {
disp E "No usable package manager could be detected on this system."
return 2
}
local -a cmd=() post=()
local needs_root=1 purge_done=0
case "$pkgmgr" in
apt)
# --auto-remove also drops the automatically installed packages
# nothing needs any more
if (( purge )); then cmd=(apt-get purge); purge_done=1; else cmd=(apt-get remove); fi
(( deps )) && cmd+=(--auto-remove)
(( assume_yes )) && cmd+=(-y)
;;
dnf|yum)
cmd=("$pkgmgr" remove)
# These managers remove the unneeded dependencies by default
(( deps )) || cmd+=(--setopt=clean_requirements_on_remove=False)
(( assume_yes )) && cmd+=(-y)
;;
zypper)
cmd=(zypper remove)
(( deps )) && cmd+=(--clean-deps)
(( assume_yes )) && cmd+=(-y)
;;
pacman)
# -n: no .pacsave backups of the configuration files;
# -s: also remove dependencies nothing else needs
local flags="-R"
(( purge )) && { flags+="n"; purge_done=1; }
(( deps )) && flags+="s"
cmd=(pacman "$flags")
(( assume_yes )) && cmd+=(--noconfirm)
;;
apk)
cmd=(apk del)
(( purge )) && { cmd+=(--purge); purge_done=1; }
;;
portage)
cmd=(emerge --unmerge)
(( assume_yes )) || cmd+=(--ask)
# depclean removes everything no longer needed, as autoremove does
(( deps )) && post=(emerge --depclean)
;;
xbps)
cmd=(xbps-remove)
(( deps )) && cmd+=(-R)
(( assume_yes )) && cmd+=(-y)
;;
nix)
cmd=(nix-env -e)
needs_root=0
# Dependencies live in the store: collect what is now unreachable
(( deps )) && post=(nix-collect-garbage)
;;
*)
disp E "Package manager '$pkgmgr' is not supported by pkgr."
return 2
;;
esac
if (( purge && ! purge_done )); then
disp W "$pkgmgr has no purge mode: the packages are removed normally."
fi
_pkg_run "$needs_root" "$dry_run" "${cmd[@]}" "$@" || return $?
if (( ${#post[@]} )); then
# Only reached when the removal itself succeeded
_pkg_run "$needs_root" "$dry_run" "${post[@]}"
fi
}
export -f pkgr
# ------------------------------------------------------------------------------
# EOF # EOF