Enumerar paquetes instalados manualmente de nivel superior sin sus dependencias

12

Hay muchas formas de mostrar los paquetes instalados manualmente usando apt, como:

apt-mark showmanual

Pero a veces esa salida es demasiado. Por ejemplo, si el usuario instaló manualmente el paquete foo:

apt-get install foo

... y foodependía de , bary bazluego apt-mark showmanualgeneraría:

bar
baz
foo

¿Cómo podemos enumerar solo los paquetes de nivel superior instalados manualmente ( es decir foo ) sin sus dependencias ( es decirbaz , no , ni bar)?


El siguiente código parece funcionar, pero las llamadas de GNU unas cientos de veces son demasiado lentas (tres horas con una CPU de 4 núcleos):parallelapt-rdepends

apt-mark showmanual | 
tee /tmp/foo | 
parallel "apt-rdepends -f Depends,PreDepends,Suggests,Recommends {} |
          tail +2" 2> /dev/null | 
tr -s ' ' '\n' | 
grep -v '[():]' | 
sort -Vu | 
grep -wv -f - /tmp/foo
agc
fuente
Hmm Las respuestas, y el código OP, son muy diferentes, y devuelven datos algo diferentes, que estoy un poco confuso sobre qué datos del método son los más correctos. Quizás sea necesaria una respuesta a la encuesta, comenzando con un sistema de prueba mínimo y agregando programas de a pocos para ver cómo y cuándo varían los resultados.
agc

Respuestas:

9

Esto podría hacerse usando la API de Python apt. Los paquetes que ve apt-mark showmanualson exactamente los apt.cache.Cache()que is_installedson verdaderos y is_auto_installedfalsos. Pero, es más fácil procesar las dependencias:

#! /usr/bin/env python3

from apt import cache

manual = set(pkg for pkg in cache.Cache() if pkg.is_installed and not pkg.is_auto_installed)
depends = set(dep_pkg.name for pkg in manual for dep in pkg.installed.get_dependencies('PreDepends', 'Depends', 'Recommends') for dep_pkg in dep)

print('\n'.join(pkg.name for pkg in manual if pkg.name not in depends))

Incluso esto enumera algunos paquetes que no esperaría ver allí ( init, grep?!).

muru
fuente
En mi sistema, ese código genera un superconjunto de mi código de 3 horas, pero no muestra sorpresas como inity grep(¿quizás sus datos aptos están corruptos?), También muestra demasiadas bibliotecas. OTOH, mi código de 3 horas pierde algunos elementos que deberían estar allí, elementos que pythonimprime el código anterior . Posiblemente no se instalaron los elementos faltantes apt.
agc
@agc eso es probablemente porque no recurrí. Probaré una opción recursiva después del fin de semana. Sin embargo, incluso con la recursividad, esperaría que esto sea mucho más rápido que llamar a apt-rdepends repetidamente
muru
El pythoncódigo anterior es 3600 veces más rápido (es decir, tardó 3 segundos) que mi código (3 horas). Estamos ansiosos por probar la versión recursiva ...
agc
3

El siguiente script de shell busca los padres de todas las dependencias instaladas.

function get_installed_packages() {
    apt list --installed | sed 's#/.*##'
}

function get_installed_packages_with_deps() {
    dpkg-query --show --showformat '${Package} ${Depends} \
        ${Pre-Depends}\n' $(get_installed_packages) | 
    sed 's/ ([^(]*)//g; s/:any\|,//g'
}

function get_package_relations() {
    awk '{print $1 " " $1; for(i = 2; i <= NF; i++) print $1 " " $i;}'
}

function add_marker() {
    echo "~ ~"
}

function resolve_parents() {
    tsort | sed -n '1,/~/ p' | head -n -1
}

(get_installed_packages_with_deps | get_package_relations; add_marker) | 
resolve_parents

Utilicé tsorten este script. Supongo que al agregar un marcador al final sin dependencias, el marcador también será la última entrada sin dependencias en mi resultado. Entonces puedo diferenciar entre el último paquete sin dependencias y el primer paquete con dependencias.

Noté un problema con esta solución:
hay ciclos en el gráfico de dependencia. Esas entradas son ignoradas por tsort.

Sealor
fuente
2

Puede encontrar todos los paquetes instalados manualmente sin su primer nivel de dependencias de la siguiente manera:

apt-mark showmanual | sort > manually-installed.txt

apt show $(apt-mark showmanual) 2>/dev/null | 
grep -e ^Depends -e ^Pre-Depends > deps1.txt

cat deps1.txt | 
sed 's/^Depends: //; s/^Pre-Depends: //; 
     s/(.*)//g; s/:any//g' > deps2.txt

cat deps2.txt | tr -d ',|' | tr ' ' '\n' | grep -v ^$ |
sort -u > all-dep-packages.txt

grep -v -F -f all-dep-packages.txt manually-installed.txt

También puedes usar la siguiente magia de una línea:

apt-mark showmanual | sort | grep -v -F -f <(apt show $(apt-mark showmanual) 2> /dev/null | grep -e ^Depends -e ^Pre-Depends | sed 's/^Depends: //; s/^Pre-Depends: //; s/(.*)//g; s/:any//g' | tr -d ',|' | tr ' ' '\n' | grep -v ^$ | sort -u)
Sealor
fuente
Mucho mas rápido. Esto genera lo que en su mayoría es un superconjunto del código OP, pero también pierde algunos, como el dasherpaquete. En mi sistema, el código OP canalizado a través de las sort -Vsalidas de 475 líneas, el código de muru genera 914 líneas, (incluido dasher), y el código de esta respuesta genera 995 líneas.
agc
Sí, mi script no considera el árbol de dependencia completo. Podría intentar adaptarlo para más niveles de jerarquía.
Sealor