It has always bugged me that dnf search has no indication in the output whether a package is already installed.
In fact, this feature request already exists for at least 10 years.
Let’s do it manually!
The standard output looks like this:
john@thinkpad ~> dnf search --cacheonly keepass
Updating and loading repositories:
Repositories loaded.
Matched fields: name (exact)
keepass.x86_64 Password manager
Matched fields: name, summary
perl-File-KeePass.noarch Interface to KeePass V1 and V2 database files
python3-pass-import+keepass.noarch Metapackage for python3-pass-import: keepass extras
python3-pykeepass.noarch Python library to interact with keepass databases
Matched fields: name
keepassxc.x86_64 Cross-platform password manager
Matched fields: summary
kpcli.noarch KeePass Command Line Interface (CLI) / interactive shell
First, we fetch the installed packages (and their installation reason) into an indexable list:
~> set -l installed dnf '%{name} [%{reason}]\n' "*keepass*" 2>/dev/null
~> echo $installed
keepassxc [User]
The reason is one of Dependency, External User, Group, User and Weak Dependency, hence we can later use the first character (D/E/G/U/W) as a unique abbreviation.
Now let’s extract the package name from the dnf search output above.
python3-pass-import+keepass.noarch Metapackage for python3-pass-import: keepass extras
We can use a simple capture group ^\s+([^\.]+)\..*:
~> string replace -r '^\s+([^\.]+)\..*' '$1' ' python3-pass-import+keepass.noarch Metapackage for python3-pass-import: keepass extras'
python3-pass-import+keepass
and use this string to extract the reason from the list of installed packages (<pkg_name> [<reason>]).
But we first have to escape all characters that have a special regex meaning:
~> string escape --style=regex "python3-pass-import+keepass"
python3\-pass\-import\+keepass
And voila:
~> string match -rg "^python3\-pass\-import\+keepass \[(.*)\]" $installed
User
If this output is non-zero, then we have the two informations that a) this package is installed and b) the reason why it was installed.
Wrap everything up in a function:
function ds -d "dnf search with [installed] markers"
set -l term $argv
if test -z "$term"
echo "Usage: "status" <search-term>"
return 1
end
# installed packages -> local array
# E.g. `gcc-c++ [User]`
set -l installed dnf '%{name} [%{reason}]\n' "*$term*" 2>/dev/null
dnf search --cacheonly "$term" 2>/dev/null | while read -l line
# package lines start with a space
# E.g. ' gcc.x86_64 Various compilers (C, C++, Objective-C, ...)'
if string match -qr '^\s+\S' "$line"
# extract `gcc`
set -l pkg_name string '^\s+([^\.]+)\..*' '$1' "$line"
# ecape in case it contains regex special characters (e.g. ++)
set pkg_name string"$pkg_name"
# extract the reason
set -l reason string "^$pkg_name \[(.*)\]" $installed
if set -q reason[1]
# extract the first character
set reason string"$reason"
echo "[$reason]$line"
else
echo " $line"
end
else
echo "$line"
end
end
end~> ds keepass
Updating and loading repositories:
Repositories loaded.
Matched fields: name (exact)
keepass.x86_64 Password manager
Matched fields: name, summary
perl-File-KeePass.noarch Interface to KeePass V1 and V2 database files
[U] python3-pass-import+keepass.noarch Metapackage for python3-pass-import: keepass extras
[D] python3-pykeepass.noarch Python library to interact with keepass databases
Matched fields: name
[U] keepassxc.x86_64 Cross-platform password manager
Matched fields: summary
kpcli.noarch KeePass Command Line Interface (CLI) / interactive shell
This works nicely but the colors are gone!
To fix that, we need a little hack:
dnf probably checks internally if it’s attached to a terminal and only then prints color codes, even if we explicitly tell it to do so (--color=always).
Check for yourself:
dnf search --cacheonly --color=always keepass | while read -l line
echo $line
end
But we can give it the terminal it wants (dnf install util-linux-script):
script -q -c "dnf search --cacheonly --color=always keepass" /dev/null | while read -l line
echo $line
end
Now the colors should appear, e.g. in our line from above:
python3-pass-import+keepass.noarch Metapackage for python3-pass-import: keepass extras
But this is just the rendered version from the terminal.
What’s actually present is the following:
python3-pass-import+ESC[32mkeepassESC[0m.noarch Metapackage for python3-pass-import: ESC[32mkeepassESC[0m extras
Since I do not aim to support all ANSI codes, a simple \x1b\[[0-9;]+m should suffice:
# remove ansi color codes
set -l pkg_name string '\x1b\[[0-9;]+m' '' "$line"
# extract `gcc`
set pkg_name string '^\s+([^\.]+)\..*' '$1' "$pkg_name"
With some additional own color codes,
echoset_color"[$reason]"set_color"$line"
the final version is:
function ds -d "dnf search with [installed] markers"
argparse 'h/help' -- $argv
or return
set -l term $argv
if set -q _flag_help; or test -z "$term"
echo "Usage: "status" <search-term>"
printf '\n[X] if installed, with X one of the following reasons:\n'
printf 'D: Dependency\tE: External User\tG: Group\nU: User \tW: Weak Dependency'
return 1
end
# installed packages -> local array
# E.g. `gcc-c++ [User]`
set -l installed dnf '%{name} [%{reason}]\n' "*$term*" 2>/dev/null
# Exclude color ansi codes from the beginning if stdout is not a tty
set -l istty false
if isatty stdout
set istty true
end
begin
if $istty
script -q -c "dnf search --color=always --cacheonly \"$term\" 2>/dev/null" /dev/null
else
# no color codes
dnf search --cacheonly --color=never "$term" 2>/dev/null
end
end | while read -l line
# package lines start with a space
# E.g. ' gcc.x86_64 Various compilers (C, C++, Objective-C, ...)'
if string match -qr '^\s+\S' "$line"
# remove ansi color codes
set -l pkg_name string '\x1b\[[0-9;]+m' '' "$line"
# extract `gcc`
set pkg_name string '^\s+([^\.]+)\..*' '$1' "$pkg_name"
# ecape in case it contains regex special characters (e.g. +)
set pkg_name string"$pkg_name"
# extract the reason
set -l reason string "^$pkg_name \[(.*)\]" $installed
# if installed
if set -q reason[1]
# extract the first character
set reason string"$reason"
if $istty
# add some color
echoset_color"[$reason]"set_color"$line"
else
echo "[$reason]$line"
end
else
echo " $line"
end
else
echo "$line"
end
end
end~> ds keepass
Matched fields: name (exact)
keepass.x86_64 Password manager
Matched fields: name, summary
perl-File-KeePass.noarch Interface to KeePass V1 and V2 database files
[U] python3-pass-import+keepass.noarch Metapackage for python3-pass-import: keepass extras
[D] python3-pykeepass.noarch Python library to interact with keepass databases
Matched fields: name
[U] keepassxc.x86_64 Cross-platform password manager
Matched fields: summary
kpcli.noarch KeePass Command Line Interface (CLI) / interactive shell