Listar pacotes instalados manualmente de nível superior sem suas dependências

12

Existem várias maneiras de mostrar os pacotes instalados manualmente apt, como:

apt-mark showmanual

Mas, às vezes, esse resultado é demais. Por exemplo, se o usuário instalou o pacote manualmente foo:

apt-get install foo

... e foodependia bare baz, em seguida apt-mark showmanual, produziria:

bar
baz
foo

Como podemos listar apenas os pacotes de nível superior instalados manualmente ( ie foo ) sem suas dependências ( ie não baz, nem bar)?


O código a seguir parece funcionar, mas a chamada GNU algumas centenas de vezes é muito lenta (três horas com uma 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
fonte
Hmm. As respostas e o código OP são todos tão diferentes e retornam dados um pouco diferentes, que estou ficando um pouco confuso sobre quais dados do método são os mais corretos. Talvez seja necessária uma resposta de pesquisa, iniciando com um sistema de teste mínimo e adicionando programas alguns de cada vez para ver como e quando a saída varia.
agc

Respostas:

9

Isso pode ser feito usando a API apt do Python. Os pacotes que você vê apt-mark showmanualsão exatamente os apt.cache.Cache()que is_installedsão verdadeiros e is_auto_installedfalsos. Mas, é mais fácil processar as dependências:

#! /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))

Mesmo isso lista alguns pacotes que eu não esperaria ver lá ( init, grep?!).

muru
fonte
No meu sistema, esse código gera um superconjunto do meu código de 3 horas, mas não mostra surpresas como inite grep(talvez seus dados apt estejam corrompidos?), Também mostra muitas bibliotecas. OTOH, meu código de 3 horas perde alguns itens que deveriam estar lá, itens que o pythoncódigo acima imprime. Possivelmente os itens ausentes não foram instalados apt.
agc
@ AGC isso é provavelmente porque eu não recursão. Vou tentar uma opção recursiva após o fim de semana. Mesmo com recursividade, no entanto, eu esperaria que isso seja muito mais rápido do que chamar apt-rdepends repetidamente
Muru
O pythoncódigo acima é 3600 vezes mais rápido (ou seja, levou 3 segundos) que o meu código (3 horas). Ansioso para testar a versão recursiva ...
AGC
3

O script de shell a seguir procura os pais de todas as dependências 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

Eu usei tsortneste script. Presumo que, ao adicionar um marcador no final sem dependências, o marcador também será a última entrada sem dependências no meu resultado. Para que eu possa diferenciar entre o último pacote sem dependências e o primeiro pacote com dependências.

Percebi um problema com esta solução:
Existem ciclos no gráfico de dependência. Essas entradas são ignoradas por tsort.

selador
fonte
2

Você pode encontrar todos os pacotes instalados manualmente sem o primeiro nível de dependências, como a seguir:

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

Você também pode usar a seguinte mágica de uma linha:

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)
selador
fonte
Muito mais rapido. Isso gera o que é principalmente um superconjunto do código OP, mas também falta alguns, como o dasherpacote. No meu sistema, o código OP passou por sort -Vsaídas 475 linhas, o código de muru produz 914 linhas (incluindo dasher) e o código desta resposta gera 995 linhas.
agc
Sim, meu script não considera a árvore de dependência completa. Você pode tentar adaptá-lo para mais níveis hierárquicos.
Selador # 26/18