El administrador de aplicaciones RPM es la base fundamental  para instalar, actualizar o remover paquetes (grupos de programas) en distribuciones de Linux como Fedora.

Puedes utilizar Python o Bash para encontrar las aplicaciones más pesadas que fueron instaladas con RPM en tu sistema. Te sorprenderá cuanto espacio se puede sangrar con aplicaciones pequeñas que ya no son utilizadas a medida que el sistema pasa más tiempo en uso.

El proyecto de Fedora tiene una guía excelente que te muestra como buscar esos paquetes (sin embargo, está muy enfocada a Python 2, versión ya expirada del lenguaje).

En este breve artículo yo te voy a mostrar como puedes hacer lo mismo desde Bash o Python, ordenando los resultados para mostrar los más pesados en su sistema.

Antes de comenzar: Instalando  los vínculos nativos de Python 3 con RPM

El primer paso es vincular Python 3 con las librerías de RPM en C:

[josevnz@macmini2 ~]$ sudo dnf install python3-rpm.x86_64
[sudo] password for josevnz: 
Last metadata expiration check: 2:27:29 ago on Sun 20 Jun 2021 04:08:17 AM EDT.
Package python3-rpm-4.14.2.1-5.fc30.x86_64 is already installed.Dependencies resolved.
Nothing to do.
Complete!

Ya veremos dentro de poco para qué vamos a necesitar esta librería para utilizar RPM de manera transparente desde Python.

¿Cómo mostrar el tamaño de los paquetes instalados? Hagamos una búsqueda con rpm -qi

En mi sistema yo tengo instalado el compilador gcc, vamos a preguntar detalles de instalación manualmente:

[josevnz@dmaf5 ~]$ rpm -qi gcc
Name        : gcc
Version     : 10.3.1
Release     : 1.fc33
Architecture: x86_64
Install Date: Fri 30 Apr 2021 07:41:37 PM EDT
Group       : Unspecified
Size        : 84899923
License     : GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD
Signature   : RSA/SHA256, Sun 25 Apr 2021 01:38:31 PM EDT, Key ID 49fd77499570ff31
Source RPM  : gcc-10.3.1-1.fc33.src.rpm
Build Date  : Fri 23 Apr 2021 04:45:08 AM EDT
Build Host  : buildvm-x86-09.iad2.fedoraproject.org
Packager    : Fedora Project
Vendor      : Fedora Project
URL         : http://gcc.gnu.org
Bug URL     : https://bugz.fedoraproject.org/gcc
Summary     : Various compilers (C, C++, Objective-C, ...)
Description :
The gcc package contains the GNU Compiler Collection version 10.
You'll need this package in order to compile C code.

El tamaño es 85 Megabytes (Size : 84899923 bytes).

Ahora que sabemos como preguntar por un paquete de manera específica, vamos a preguntar por TODOS los paquetes instalados en el sistema:

[josevnz@dmaf5 ~]$ for rpm in $(rpm -qa); do \
  rpm -qi $rpm|grep ':'| grep -i -P 'name|size'|grep  -i -v 'summary' \
done
Name        : fonts-filesystem
Size        : 0
Name        : xkeyboard-config
Size        : 5630701
Name        : fedora-logos
Size        : 9225131
Name        : hyperv-daemons-license
Size        : 18693
Name        : dejavu-sans-mono-fonts
Size        : 1275600
Name        : dejavu-sans-fonts
Size        : 5930860
Name        : langpacks-core-font-en
Size        : 351
Name        : mozilla-filesystem
Size        : 0
Name        : google-noto-fonts-common
Size        : 16087
Name        : fuse-common
Size        : 38
Name        : adobe-source-code-pro-fonts
Size        : 1886539
Name        : dejavu-serif-fonts

No está mal para el primer intento, pero si examinas el código con más atención te darás cuenta de que estamos creando varios procesos adicionales para obtener las lista de todos los paquetes (rpm -qa), y filtrando la salida por pantalla (grep -i -P).

Esto es muy ineficiente. Por suerte, una segunda versión nos permitirá obtener sólo los campos de interés de todos los paquetes instalados:

rpm -qa --queryformat "%{NAME}-%{VERSION} %{SIZE}\n"
libeconf-0.4.0 46163
open-vm-tools-11.3.0 3344432
nano-5.8 2941058
nano-default-editor-5.8 514
open-vm-tools-desktop-11.3.0 477277
selinux-policy-3.14.6 25207
selinux-policy-targeted-3.14.6 31863369
glibc-all-langpacks-2.32 227552812
glibc-common-2.32 8084692
glibc-langpack-en-2.32 6301592
glibc-2.32 15078299
glibc-headers-x86-2.32 2082521
glibc-devel-2.32 1251998
libbytesize-2.6 89308

Mejor, pero los resultados aún no están ordenados por tamaño, ¿no es así?. Vamos a cambiar eso:

rpm -qa --queryformat "%{NAME}-%{VERSION} %{SIZE}\n"|sort -r -k 2

syslinux-extlinux-nonlinux-6.04 999992
gettext-libs-0.21 999260
qt5-qtbase-5.15.2 9985025
kyotocabinet-libs-1.2.78 997743
libvncserver-0.9.13 997434
ncurses-libs-6.2 996671
slirp4netns-1.1.12 99520
elfutils-libelf-0.185 992326
foomatic-db-4.0 9909787
kf5-kio-file-widgets-5.79.0 989146
libtdb-1.4.3 98892
libgee-0.20.4 988908
sane-backends-libs-1.0.32 98864
libglusterfs0-8.6 988032
brasero-libs-3.12.2 986280
perl-YAML-LibYAML-0.82 98597
cracklib-dicts-2.9.6 9815139
procps-ng-3.3.16 981207

Esta versión sólo utiliza dos instrucciones (rpm, sort), sin embargo podemos hacerlo mucho mejor utilizando Python (para eso utilizaremos el paquete que vincula a Python con RPM, que instalamos en el paso anterior):

#!/usr/bin/env python3
import rpm
if __name__ == "__main__":
	for package in rpm.TransactionSet().dbMatch():
    	print(f"{package['name'].decode('utf-8')}-{package['version'].decode('utf-8')}: {package['size']}")

Salida, sólo 5 líneas de código:

[josevnz@macmini2 ~]$ ./bin/rpmqas.py
libxcrypt-compat-4.4.16-3: 262994
mailx-12.5-31: 568045
perl-threads-shared-1.59-3: 82318
pinentry-1.1.0-5: 214262
libkcapi-1.1.4-1: 95499
python-pip-wheel-19.0.3-7: 1238786
rubygem-psych-3.1.0-124: 145717
...

Ahora ordenamos los resultados de esta búsqueda utilizando una función anónima (lamba expression) la cual se encarga de ordenar por el tamaño del paquete:

#!/usr/bin/env python3
import rpm
if __name__ == "__main__":    
for package in sorted(rpm.TransactionSet().dbMatch(), key=lambda package: package['size'], reverse=True):
	print(f"{package['name'].decode('utf-8')}-{package['version'].decode('utf-8')}: {package['size']}")

La salida ordenada:

linux-firmware-20200421: 256914779
conda-4.8.4: 228202733
glibc-all-langpacks-2.29: 217752640
docker-ce-cli-19.03.12: 168609825c
lang-libs-8.0.0: 117688777
wireshark-cli-3.2.3: 108810552
llvm-libs-8.0.0: 108310728
docker-ce-19.03.12: 106517368
ansible-2.9.9: 102477070
containerd.io-1.2.13: 102082914
iwl7260-firmware-25.30.13.0: 91053086

Pero las ventajas de utilizar Python en vez de Bash no se queda solamente en la salida ordenada; Podemos hacer que los números salgan con el formato correcto (linux-firmware-20200421 256,914,779 bytes comparado con linux-firmware-20200421 256914779).

Usando 'f-string': (format-string o cadenas con formato):

!/usr/bin/env python3
import rpm
if __name__ == "__main__":    
	for package in sorted(rpm.TransactionSet().dbMatch(), key=lambda package: package['size'], reverse=True):
    print(f"{package['name'].decode('utf-8')}-{package['version'].decode('utf-8')}: {package['size']:,.0f}")

La salida final de nuestro pequeño programa:

linux-firmware-20200421: 256,914,779
conda-4.8.4: 228,202,733
glibc-all-langpacks-2.29: 217,752,640
docker-ce-cli-19.03.12: 168,609,825
clang-libs-8.0.0: 117,688,777
wireshark-cli-3.2.3: 108,810,552
llvm-libs-8.0.0: 108,310,728
docker-ce-19.03.12: 106,517,368
ansible-2.9.9: 102,477,070...

¿Qué aprendimos escribiendo estos pequeños programas?

  1. Siempre asegúrate si hay una forma más eficiente de completar una tarea. Pero no te obsesiones con optimizando prematuramente, verifica que tu aplicación funciona correctamente.
  2. Bash es muy bueno para hacer un prototipo rápido, pero lenguajes como Python te pueden facilitar la creación de programas más poderosos sin mucho esfuerzo.
  3. Muchas herramientas del sistema operativo vienen con vinculaciones (native bindings) que te ayudarán a escribir código más eficiente,  en vez de tener que llamar varios programas uno tras otro para procesar resultados (como una línea de ensamblaje de autos).
  4. Ensaya y prueba varias técnicas para resolver un problema es quizás la parte más divertida de programar.
  5. Hay mucho más que aprender sobre RPM, la guía de Red Hat (En Inglés) tiene muchos más detalles sobre esta herramienta.