Lo hice y lo entendí

El blog de Vicente Navarro
23 sep

Mostrar un árbol de los paquetes instalados que dependen de otro en Debian

De la época que estuve usando Gentoo recuerdo con especial cariño dos cosas: la flexibilidad y la posibilidad de personalización durante el compilado de los parámetros USE, y las herramientas de gestión de paquetes de Portage: el emerge y el equery del Gentoolkit.

El emerge viene a ser una unión de los comandos de Debian dpkg, apt-get, apt-cache y apt-build. Nigún misterio por esa parte…

Sin embargo, el equery que, como su propio nombre indica, se usa para hacer queries a la base de datos de paquetes, resulta muy útil. Por ejemplo, se puede obtener la lista de paquetes que dependen de uno dado (ejemplo copiado de Gentoolkit):

# equery depends pygtk
[ Searching for packages depending on pygtk... ]
app-office/dia-0.93
dev-python/gnome-python-2.0.0-r1
gnome-extra/gdesklets-core-0.26.2
media-gfx/gimp-2.0.4
x11-libs/vte-0.11.11-r1

También nos permite obtener un árbol de dependencias de un paquete dado, pero en este caso no se trata de los paquetes de los que depende el paquete, sino de sus dependencias:

# equery depgraph cdrtools
Displaying dependencies for app-cdr/cdrtools-2.01_alpha37
`-- app-cdr/cdrtools-2.01_alpha37
 `-- sys-libs/glibc-2.3.4.20040808 (virtual/libc)
  `-- sys-kernel/linux-headers-2.4.22 (virtual/os-headers)
   `-- sys-apps/baselayout-1.10.4
    `-- sys-apps/sysvinit-2.85-r1
     `-- sys-apps/gawk-3.1.3-r1
      `-- sys-apps/util-linux-2.12-r4
          `-- sys-apps/sed-4.0.9
        `-- sys-libs/ncurses-5.4-r4
            `-- sys-apps/pam-login-3.14
            `-- sys-libs/pam-0.77-r1
                 `-- sys-libs/cracklib-2.7-r10
               `-- sys-apps/miscfiles-1.3-r1
              `-- app-arch/gzip-1.3.5-r1
              `-- sys-apps/portage-2.0.50-r10

En Debian, a menudo quiero eliminar un paquete y las dependencias no me lo permiten:

 # dpkg -P libxine1
dpkg: dependency problems prevent removal of libxine1:
 totem-xine depends on libxine1 (>= 1.1.2-5).
dpkg: error processing libxine1 (--purge):
 dependency problems - not removing
Errors were encountered while processing:
 libxine1

Es posible que esté tan convencido de querer eliminar el paquete que me decida incluso a eliminar sus dependencias:

# dpkg -P totem
dpkg: dependency problems prevent removal of totem:
 gnome-desktop-environment depends on totem (>= 1.4.3).
dpkg: error processing totem (--purge):
 dependency problems - not removing
Errors were encountered while processing:
 totem

Pero claro, llegados a este punto, me temo que el gnome-desktop-environment no lo quiero eliminar. De momento, los paquetes ya me los ha dejado marcados como que no los quiero instalados para aprovechar la mínima ocasión para eliminarlos:

# dpkg -l | egrep -v ^ii                                                                 
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Installed/Config-files/Unpacked/Failed-config/Half-installed
|/ Err?=(none)/Hold/Reinst-required/X=both-problems (Status,Err: uppercase=bad)
||/ Name             Version              Description
+++-================-====================-============================================
pi  libxine1         1.1.2+dfsg-4         the xine video/media player library, binary 
pi  totem            2.16.5-3             A simple media player for the Gnome desktop

Como a mí me da manía dejarlos así, pues en este punto haría un “apt-get --reinstall install libxine1 totem” para que vuelvan a estar en estado ii.

La forma elegante de prever y al mismo tiempo tantear si podríamos borrar el paquete sería haber usado el “dpkg -P” con una de las tres opciones que evitan que la acción se haga en realidad:

       --no-act | --dry-run | --simulate
              Do everything which is supposed to be done, but  don't  write  any  changes.
              This  is  used  to  see what would happen with the specified action, without
              actually modifying anything.
 
              Be sure to give --no-act before the action-parameter, or you  might  end  up
              with  undesirable  results. (e.g. dpkg --purge foo --no-act will first purge
              package foo and then try to purge package --no-act, even though you probably
              expected it to actually do nothing)

Pero bueno, esta forma de trabajar por tanteo me parece un atraso tras haber visto lo que puede hacer el equery.

Hay una herramienta llamada wajig, disponible desde el repositorio oficial de Debian 4.0, que permite simplificar muchas tareas de administración de Debian. La lista de tareas que nos permite hacer wajig fácilmente es impresionante. Para el propósito del tema que estamos tratando, nos interesa el comando dependents:

dependents     List of packages which depend/recommend/suggest the package

Pero desafortunadamente, el comando nos muestra una lista de los paquetes disponibles que dependen de uno dado, no de los los paquetes instalados:

# wajig dependents totem
r education-standalone
d gnome-desktop-environment
r gnome-volume-manager
r magicdev
s nautilus
r peercast-handlers
d totem-gstreamer
d totem-xine
r tunapie

La letra del principio de las líneas indica si el paquete d=depends, r=recommends o s=suggests nuestro paquete.

Bueno, de aquí a obtener el equivalente a un “equery depends” estamos a un paso. Lo podemos hacer con el siguiente script (disponible para descargar, debdependents):

#!/bin/bash
wajig dependents $1 | while read i
do
   if dpkg -l $(echo $i | awk '{ print $2 }' ) 2>&1 | grep ^i > /dev/null 2>&1
   then
      echo $i
   fi
done
# ./debdependents libxine1
d totem-xine
# ./debdependents openssl
d ca-certificates
s exim4-base
c libssl0.9.8
s mutt

Desafortunadamente, el “wajig dependents” ya es bastante lento, de modo que todos estos “dpkg -i“, awk y egrep no ayudan precisamente a que vaya más rápido.

Podemos ir incluso más lejos. Con la base del script anterior, y usando un poco de recursividad, podemos obtener un árbol de paquetes que dependen de uno dado hasta un determinado nivel. Es un árbol parecido al del “equery depgraph” pero recordemos que aquél es de las dependencias de un paquete, no los paquetes que tienen como dependencia a éste. El nivel del árbol no puede ser ilimitado porque hay grupos de paquetes que se dependen mutuamente (p.e. el totem y el totem-xine). Este es el árbol:

# ./debdependentstree libxine1 3                                                         
libxine1
    |-- totem-xine
        |-- totem
            |-- gnome-desktop-environment *
            |-- totem-xine *
        |-- totem-mozilla

Esta es la ayuda del script:

 # ./debdependentstree 
 
Parameters:
  1 -> installed package
  2 -> tree level
 
Example:
 
$ debdependentstree foo 2
foo
  |--bar
     |--barfoo *
  |--foobar
 
The symbol * means that the maximum tree level has been reached

Y el script en cuestión es éste (disponible para descargar, debdependentstree):

#!/bin/bash
#
# debdependentstree: a simple script to show a tree of dependent packages of a given one
# 
# Licensed under the GPL license. (C) Vicente Navarro Jover (http://www.vicente-navarro.com/blog/)
#

isinstalled() {
  dpkg -l $1 2> /dev/null | grep ^i 2>&1 > /dev/null
}

dependents() {
  # $1 -> package
  # $2 -> current level

  local showspaces=""
  local i=""
  local j=""
  local deptype=""
  local package=""

  for (( j=0 ; j<$2 ; j++ )); do showspaces="${showspaces}""    "; done
  wajig dependents $1 | while read i
  do
    deptype=$(echo $i | awk '{ print $1 }')
    package=$(echo $i | awk '{ print $2 }')
    if [[ $deptype == "d" ]] && isinstalled $package
    then
      if [[ $2 -eq $maxlevel ]]
      then
         echo "${showspaces}"'|-- '${package}' *'
      else
         echo "${showspaces}"'|-- '${package}
         dependents $package $(( $2+1 ))
      fi
    fi
  done
}

usage() {
   echo
   echo "Parameters:"
   echo "  1 -> installed package"
   echo "  2 -> tree level"
   echo
   echo "Example:"
   echo
   echo "$ debdependentstree foo 2"
   echo "foo"
   echo "  |--bar"
   echo "     |--barfoo *"
   echo "  |--foobar"  
   echo
   echo "The symbol * means that the maximum tree level has been reached"
}

if [[ ! -n $2 ]]
then
   usage
   exit 1
fi

if [[ ! ( $2 -gt 0 ) ]]
then
   echo "The second parameter is not a number higher than 0"
   exit 3
fi

if isinstalled $1
then
   echo $1
   maxlevel=$2
   currlevel=1
   dependents $1 $currlevel
   exit 0
else
   echo Package $1 is not installed!
   exit 2
fi

Como ejemplo del problema que comentaba con varios paquetes que se requieren mutuamente, si volvemos a ejecutar el script para el paquete libxine1 con un nivel mayor, nos damos cuenta de que no acabaría nunca:

 # ./debdependentstree libxine1 5
libxine1
    |-- totem-xine
        |-- totem
            |-- gnome-desktop-environment
            |-- totem-xine
                |-- totem
                    |-- gnome-desktop-environment *
                    |-- totem-xine *
                |-- totem-mozilla
        |-- totem-mozilla

Podemos poner algunos ejemplos más:

# ./debdependentstree iceweasel 3
iceweasel
    |-- firefox
    |-- iceweasel-dom-inspector
    |-- iceweasel-gnome-support
        |-- firefox-gnome-support
# ./debdependentstree openssl 3                                                                 
openssl
    |-- ca-certificates
        |-- libcurl3
            |-- openoffice.org-core *
        |-- libcurl3-gnutls
            |-- git-core *
# ./debdependentstree openssh-client 2
openssh-client
    |-- openssh-server
        |-- ssh *
        |-- ssh-krb5 *
    |-- ssh
        |-- openssh-server *
        |-- ssh-askpass-gnome *
    |-- ssh-askpass-gnome
    |-- ssh-krb5
        |-- ssh-askpass-gnome *

Recordemos que la ejecución de este script puede resultar muy lenta, hecho agravado por la recursividad del script, con la que bash no se lleva excesivamente bien. En el capítulo de variables locales del Advanced Bash-Scripting Guide se permiten esta broma:

# Does bash permit recursion?
# Well, yes, but...
# It's so slow that you gotta have rocks in your head to try it.

Variables locales, por cierto, absolutamente necesarias para la recursión. Se pueden definir con la etiqueta local, como se puede ver en la función dependents() del script.

Por cierto, en el árbol sólo se muestran los paquetes required, no los recommended ni los suggested. Este comportamiento se podría variar modificando esta línea:

if [[ $deptype == "d" ]] && isinstalled $package

Por supuesto, se agracen sugerencias para mejorar el script o avisos de bugs.

Para finalizar, comentar que he coloreado los scripts para pasarlos a HTML con el comando enscript:

# enscript -Esh --language=html debdependentstree -odebdependentstree.html --color

Hay algunos ejemplos de uso del enscript en el capítulo Text Conversion/Filter Tools del GNU/Linux Command-Line Tools Summary.

El código HTML que devuelve no valida como XHTML 1.0, así que he usado este comando para convertirlo:

# cat debdependentstree.html | sed 's_<B>_<strong>_g' | sed 's_</B>_</strong>_g' | sed 's_<I>_<em>_g' | \
sed 's_</I>_</em>_g' | sed 's_><FONT COLOR="_ style="color:_g'| sed 's_</FONT>__g' > debdependentstree_xhtml.html

Actualización 24/9/07: Acabo de descubrir un par de comandos de Debian que permiten hacer más o menos lo que yo buscaba: apt-rdepends y apt-cache rdepends:

# apt-cache --installed rdepends libxine1 libxine1
Reverse Depends:
  totem-xine
# apt-rdepends -r --state-follow=Installed --state-show=Installed libxine1
Reading package lists... Done
Building dependency tree... Done
libxine1
  Reverse Depends: totem-xine (>= 2.16.5-3)
totem-xine
  Reverse Depends: totem (>= 2.16.5-3)
  Reverse Depends: totem-mozilla (>= 2.16.5-3)
totem
  Reverse Depends: gnome-desktop-environment (>= 1:2.14.3.6)
gnome-desktop-environment
totem-mozilla

¡Ya me extrañaba a mí que Debian no tuviera algo así! Estas herramientas quitan bastante utilidad al script propuesto anteriormente pero, en cualquier caso, creo que la salida en árbol sigue resultando muy atractiva visualmente, ya que nos permite tener varios niveles del árbol de dependencias en un sólo golpe de vista.

:wq

Entradas relacionadas

5 Comentarios a “Mostrar un árbol de los paquetes instalados que dependen de otro en Debian”

  • Ringmaster dice:

    Interesante para los que usamos derivados de Debian, como (k)ubuntu. Muy instructivo. Me guardo el enlace ¡Gracias!

  • Ringmaster ¡A ti por la visita!

  • muzzol dice:

    no hay que tener miedo de cargar-se paquetes como gnome-desktop-environment, que en realidad són meta-paquetes, es decir, listas o alias de otros paquetes. a veces simplemente eliminando un juego o algun programa pequeño ya nos obliga a deshacernos del meta-paquete que lo contiene.

  • muzzol Lo que comentas es muy interesante, porque aunque yo sí sabía que es un meta-paquete, pensaba que si lo eliminábamos, eliminábamos todos los paquetes que lo forman. ¡Lo tendré que probar! ¡Muchas gracias!

Trackbacks y pingbacks:

Tema LHYLE09, creado por Vicente Navarro