Автор Тема: Удаление хвостов удаляемых пакетов по признаку ненужности другим пакетам  (Прочитано 4295 раз)

Оффлайн ruslandh

  • Поспешай не торопясь !
  • Модератор глобальный
  • *****
  • Сообщений: 32 361
  • Учиться .... Телепатами не рождаются, ими ....
Всё это конечно интересно, но при попытке удаления, например glib2, у вас сносится практически всё. А послу удаления python-base - вы остаётесь без питона.

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
Я тоже кое-что накостылил... Оно почти работает, но например для geany определяет такой список пакетов:
gnome-pty-helper geany-data libvte geany

что неправильно. Пытаемся понять почему, и видим:
$ sudo apt-get remove libvte
Чтение списков пакетов... Завершено
Построение дерева зависимостей... Завершено
Следующие пакеты будут УДАЛЕНЫ:
  geany geany-data libvte synaptic synaptic-usermode xfce4-default xfce4-full xfce4-regular xfce4-terminal
0 будет обновлено, 0 новых установлено, 9 пакетов будет удалено и 3 не будет обновлено.
Необходимо получить 0B архивов.
После распаковки будет освобождено 17,8MB дискового пространства.
Продолжить? [Y/n] ^C
$ rpm --whatrequires  --qf '%{name}\n' -q libvte
geany

Получается что на rpm --whatrequires нельзя полагаться? Может что-нибудь "забыть"?




Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
Собственно "экспериментальный костыль" для проверки алгоритма на python3 получился такой:
Назвать его "условно рабочим" мешает пока  "особенность" rpm которая приведена в предыдущем посте.

from __future__ import print_function

import subprocess
import sys

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def getDependency(package) :
    resp = subprocess.check_output(["rpm" ,"--requires" , "--qf" ,"'%{REQUIRENAME}'", "-q" , package  ],universal_newlines=True);
    s = str(resp);
    if s and len(s.strip()) > 0:
        return s.replace("'","").split(sep="\n")
    else:
        return None


def getAllpackages() :
    resp = subprocess.check_output(["rpm", "-qa" , "--qf", "'%{name}\n'"],universal_newlines=True);
    s = str(resp)
    return s.replace("'","").split(sep="\n")


def getWhatReq(package) :

    resp = subprocess.check_output(["rpm", "--whatrequires",  "--qf", "'%{name}\n'", "-q",  package  ],universal_newlines=True, stderr=None)
    s = str(resp);
    if s and len(s.strip()) > 0:
        return s.replace("'","").split(sep="\n")
    else:
        return None

candidats = {}
all = getAllpackages()

blacklist = set() # set(["dbus","control","grep","sed","samba_common","samba-client","udev","shadow-utils","tcb-utils","pam0-tcb","nss_tcb"])


def process_pckg_list(pckg_list) :


    for package in pckg_list :
        try:
            dep = candidats[package]
            hasBefore = True
        except KeyError:
            dep = set()
            candidats[package] = dep
            hasBefore = False

        try :
            list = getDependency(package)
            if list == None:
                return True
        except subprocess.CalledProcessError :
            eprint("erron while processing :", package)
            # xtree.pop(package)
            return False

        for s in list :
            if "(" in s :
                continue
            if "/" in s  :
                continue
            try :
                pname = s.strip().split()[0].strip()
            except IndexError:
                continue;
            if pname in blacklist :
                continue
            # print(package, "::", s,"->",pname)
            dep.add(pname)
            if pname not in candidats:
                status = process_pckg_list([pname])
                if not status :
                    try :
                        if not hasBefore :
                            eprint("blacklist:", pname );
                            blacklist.add(pname)
                            del candidats[pname]
                    except KeyError :
                        pass

    return True


def main():
    args = sys.argv  #[-1]
    args.pop(0)
    eprint(args)
    process_pckg_list(args)
    # main(["Ristetto"])

    #    print(len(candidats))
    #    print(candidats)

    #    print("------------------")
    needNextPass = False
    passCount = 1

    class NeedPassException(Exception):
        pass

    dep_cache = {}
    eprint(candidats)
    while (True):
        #       print ("------------pass ", passCount)
        passCount += 1
        needNextPass = False
        try:
            for p in candidats:
                try:
                    list = dep_cache[p]
                except KeyError:
                    try:
                        list = getWhatReq(p)
                    except subprocess.CalledProcessError:
                        dep_cache[p] = []
                        continue
                for r in list:
                    if len(r.strip()) == 0:
                        continue
                    if r not in candidats:
                        needNextPass = True
                        xlist = candidats[p]
                        for c1 in xlist:
                            try:
                                del candidats[c1]
                            except KeyError:
                                pass
                        del candidats[p]
                        raise NeedPassException
                    pass
                pass
        except NeedPassException:
            continue
        if not needNextPass:
            break
    pass
    result = ''
    eprint("Всего найдено пакетов:", len(candidats.keys()))
    for s in candidats.keys():
        result = result + s + " ";
    print(result)


if __name__ == "__main__":
    main()

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
С таким вот костылём, получаем список уникальных имён пакетов без разбора на библиотеки:
Код: [Выделить]

$ apt-cache depends medit | grep -v '<' | sort | uniq | sed 's/^.* //g;s/[0-9]*://g'
glib2-2.32.3-alt0.M60P.1
glibc-core-2.11.3-alt8.M60P.5
...
[/code]

apt-get такой формат пакета не понимает:
$ apt-cache show glib2-2.32.3-alt0.M60P.1
W: Невозможно найти пакет glib2-2.32.3-alt0.M60P.1
E: Не найдено ни одного пакета

И второе, что меня смущает в apt-cache в приложении к этой задаче - как я понимаю оно выведет список всех имеющихся в репозитории пакетов имеющих  зависимости на указанный, в том числе и тех которые не установлены в системе. Или тут я не прав?


Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
Вот такая команда вроде позволяет получить список пакетов удобоваримом виде.
$ apt-cache whatdepends  libvte | grep -v '<' | grep -v libvte | grep -v Требует | sort | uniq | sed 's/-[^-]*-[^-]*$//'
  amtterm
  AutoScan
  geany
  geany-plugins
  genius
  gnome-mud
  grip
  gsql
  gtkterm
  libgnome-desktop-sharp
  lxde-lxterminal
  mate-terminal
  python-module-vte
  synaptic
  termit
  tilda
  vte
  xfce4-terminal

Вечером попробую в алгоритме rpm --whatdepends заменить на apt-cache c выкидыванием из возвращаемого списка тех пакетов, которые в системе не установлены.

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
с apt-cache вариант не выглядит "проходным" - очень сильно все медленно работает с ним. Т.е производительность в этом случае абсолютно не приемлемая получается.

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
Чего-то получилось...
$ sudo apt-get remove `python3 ./DepTree.py xfce4-default`
{'xfce4-default', 'xfce4-full', 'xfce4-regular'}
предупреждение: пакет xfce-freedesktop-menu не установлен
erron while processing : xfce-freedesktop-menu
blacklist: xfce-freedesktop-menu
предупреждение: пакет pam-common не установлен
erron while processing : pam-common
blacklist: pam-common
предупреждение: пакет glibc-crypt_blowfish не установлен
erron while processing : glibc-crypt_blowfish
blacklist: glibc-crypt_blowfish
предупреждение: пакет cpp не установлен
erron while processing : cpp
blacklist: cpp
предупреждение: пакет ntfsprogs не установлен
erron while processing : ntfsprogs
blacklist: ntfsprogs
предупреждение: пакет screen-saver-engine не установлен
erron while processing : screen-saver-engine
blacklist: screen-saver-engine
Всего найдено пакетов для удаления: 102
Чтение списков пакетов... Завершено
Построение дерева зависимостей... Завершено
Следующие пакеты будут УДАЛЕНЫ:
  dconf eject enchant gnome-online-accounts gst-plugins-bad1.0 gst-plugins-good1.0 gtk2-themes-xfce4 gvfs gvfs-backend-admin gvfs-backend-afc gvfs-backend-afp
  gvfs-backend-cdda gvfs-backend-dnssd gvfs-backend-goa gvfs-backend-google gvfs-backend-mtp gvfs-backend-nfs gvfs-backend-recent-files gvfs-backend-smb
  gvfs-backends hddtemp libdconf libgarcon-settings-manager-menu libthunar libtumbler libupower libxfce4panel libxfsm lm_sensors3 mousepad nfs-clients
  nfs-utils orage parole pavucontrol realmd ristretto rodent-icon-theme rpcbind samba-client samba-common-libs samba-common-tools samba-libs thunar
  thunar-archive-plugin thunar-media-tags-plugin thunar-volman-plugin tumbler unzip upower xarchiver xdg-user-dirs xfce-polkit xfce4-about xfce4-appfinder
  xfce4-battery-plugin xfce4-calculator-plugin xfce4-clipman-plugin xfce4-cpufreq-plugin xfce4-cpugraph-plugin xfce4-datetime-plugin xfce4-default xfce4-dict
  xfce4-diskperf-plugin xfce4-eyes-plugin xfce4-fsguard-plugin xfce4-full xfce4-genmon-plugin xfce4-hardware-monitor-plugin xfce4-kbdleds-plugin
  xfce4-mailwatch-plugin xfce4-minimal xfce4-mount-plugin xfce4-netload-plugin xfce4-notes-plugin xfce4-notifyd xfce4-panel xfce4-places-plugin
  xfce4-power-manager xfce4-pulseaudio-plugin xfce4-quicklauncher-plugin xfce4-regular xfce4-screenshooter xfce4-sensors-plugin xfce4-session
  xfce4-session-engines xfce4-settings xfce4-smartbookmark-plugin xfce4-systemload-plugin xfce4-taskmanager xfce4-terminal xfce4-time-out-plugin
  xfce4-timer-plugin xfce4-verve-plugin xfce4-weather-plugin xfce4-whiskermenu-plugin xfce4-wmdock-plugin xfce4-xkb-plugin xfconf-utils xfdesktop xfwm4
  xfwm4-themes
0 будет обновлено, 0 новых установлено, 102 пакетов будет удалено и 3 не будет обновлено.
Необходимо получить 0B архивов.
После распаковки будет освобождено 122MB дискового пространства.
Продолжить? [Y/n] ^C
По крайней мере лишнего вроде не зацепило, и нашло если не все то многое.
Сам "костыль" (вернее его рабочий макет для проверки алгоритма):
from __future__ import print_function

import subprocess
import sys

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def getDependency(package) :
    if package == "":
        return None
    # resp = subprocess.check_output(["/bin/sh", "-c", "apt-cache depends "+ package +" | grep -v '<' | grep -v Требует | sort | uniq | sed 's/-[^-]*-[^-]*$//'"],universal_newlines=True);

    resp = subprocess.check_output(["rpm" ,"--requires" , "--qf" ,"'%{REQUIRENAME}'", "-q" , package  ],universal_newlines=True);
    s = str(resp);
    if s and len(s.strip()) > 0:
        return s.replace("'","").split(sep="\n")
    else:
        return None


def getAllpackages() :
    resp = subprocess.check_output(["rpm", "-qa" , "--qf", "'%{name}\n'"],universal_newlines=True);
    s = str(resp)
    return s.replace("'","").split(sep="\n")


def getWhatReq(package) :
    # args = ["/bin/sh", "-c",  "apt-cache whatdepends  " + package + " | grep -v '<' | grep -v Требует | sort | uniq | sed 's/-[^-]*-[^-]*$//'"]
    # resp = subprocess.check_output(["rpm", "--whatrequires",  "--qf", "'%{name}\n'", "-q",  package  ],universal_newlines=True, stderr=None)
    args = ["/bin/sh", "-c", "rpm --test -e " + package + " 2>&1 >/dev/null |  egrep  -o -e 'для.+' | sed 's/для //' | sed 's/-[^-]*-[^-]*$//'"]

    resp = subprocess.check_output(args,universal_newlines=True, stderr=None)

    s = str(resp);
    if s and len(s.strip()) > 0:
        return s.replace("'","").split(sep="\n")
    else:
        return None

candidats = {}
all = getAllpackages()

blacklist = set() # set(["dbus","control","grep","sed","samba_common","samba-client","udev","shadow-utils","tcb-utils","pam0-tcb","nss_tcb"])


def process_pckg_list(pckg_list) :


    for package in pckg_list :
        try:
            dep = candidats[package]
            hasBefore = True
        except KeyError:
            dep = set()
            candidats[package] = dep
            hasBefore = False

        try :
            list = getDependency(package)
            if list == None:
                return True
        except subprocess.CalledProcessError :
            eprint("erron while processing :", package)
            # xtree.pop(package)
            return False

        for s in list :
            s = s.strip()
            if len(s) ==0 :
                continue
            if "(" in s :
                continue
            if "/" in s  :
                continue

            try :
                pname = s.strip().split()[0].strip()
            except IndexError:
                eprint("index error:",s)
                continue;
            if pname in blacklist :
                continue
            # print(package, "::", s,"->",pname)
            dep.add(pname)
            if pname not in candidats:
                status = process_pckg_list([pname])
                if not status :
                    try :
                        if not hasBefore :
                            eprint("blacklist:", pname );
                            blacklist.add(pname)
                            del candidats[pname]
                    except KeyError :
                        pass

    return True


def main():
    args = sys.argv  #[-1]
    args.pop(0)
    args1 = args.copy()
    for p in args :
        list = getWhatReq(p)
        if list:
            list.remove('')
            args1.extend(list)
        args1 = set(args1)
    eprint(args1)
    process_pckg_list(args1)
    # main(["Ristetto"])

    #    print(len(candidats))
    #    print(candidats)

    #    print("------------------")
    needNextPass = False
    passCount = 1

    class NeedPassException(Exception):
        pass

    dep_cache = {}
    #eprint(candidats)


    while (True):
        # eprint ("------------pass ", passCount)
        passCount += 1
        needNextPass = False
        try:
            for p in candidats:
                try:
                    list = dep_cache[p]
                except KeyError:
                    try:
                        list = getWhatReq(p)
                        dep_cache[p] = list
                    except subprocess.CalledProcessError:
                        # eprint("p=",p)
                        dep_cache[p] = []
                        continue
                if list == None :
                    # eprint("getWhatReq(",p ,") return None" )
                    continue

                for r in list:
                    r = r.strip()
                    if len(r) == 0:
                        continue
                    if r == p:
                        # eprint("skip [" + r + "]")
                        continue

                    if r not in all :
                        continue
                    if r not in candidats:
                        needNextPass = True
                        xlist = candidats[p]
                        for c1 in xlist:
                            try:
                                del candidats[c1]
                                # eprint("removed ",c1)
                            except KeyError:
                                pass
                        try :
                            del candidats[p]
                            # eprint("removed ", p)
                        except KeyError:
                            pass

                        raise NeedPassException
                    pass
                pass
        except NeedPassException:
            continue
        if not needNextPass:
            break
    pass
    result = ''
    eprint("Всего найдено пакетов для удаления:", len(candidats.keys()))
    for s in candidats.keys():
        result = result + s + " ";
    print(result)


if __name__ == "__main__":
    main()
Он сам ничего не удаляет, просто возвращает список пакетов, пригодный для "скармливанию" apt-get remove

« Последнее редактирование: 17.03.2017 13:41:38 от ASte »

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
Всё это конечно интересно, но при попытке удаления, например glib2, у вас сносится практически всё.
ruslandh это не так. Алгоритм такой: если от пакета зависит хотя бы один пакет не входящий в список кандидатов на удаление, то такой пакет из кандидатов выбывает.
В этом случае ничего такого как вы описываете не произойдёт. Неоднозначность есть в более других случаях, об этом немного попозже.

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
stranger573
А что за неоднозначность Вы имеете в виду? Расскажите пожалуйста подробнее.

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
to ASte тогда скрипт лучше так:
apt-cache depends "+ package +" | grep -v '<' | sort | uniq | sed 's/^.* //g;s/-[^-]*-[^-]*$//'
"grep -v Требует" приколачивает скрипт к русской локали, а это плохо.
Кроме того, посмотрите мой вывод выше для "apt-cache depends medit" так теряется последняя строка для xml-utils, а это тоже надо обрабатывать. Почему там так я не знаю, возможно это необязательная зависимость или другие какие причины.

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
stranger573
А что за неоднозначность Вы имеете в виду? Расскажите пожалуйста подробнее.
Да, сейчас, только соображу как описать.

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
Неоднозначность возникает в случае, если в список кандидатов по depends попадает самостоятельное приложение (то которое может использоваться само по себе). К примеру вы изначально захотели удалить firefox-vimperator, который зависит от firefox. В этом случае на вылет пойдёт firefox вместе со всем от чего зависит. Решить нужно его удалять или нет может только пользователь. В настоящем варианте получаем список из которого можно исключить firefox (если требуется его оставить). Но ветки пакетов от которых он зависит там так и останутся (отследить и исключить их вручную достаточно проблематично). В результате firefox всё равно вылетает.

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
Разрулить эту неоднозначность можно попробовать так:
-------------
В графике:
Вывести список кандидатов на удаление с активированными чекбоксами и предложить пользователю снять галочки с пакетов которые он хотел бы оставить. Самостоятельные приложения в отличии от библиотек или модулей пользователю более-менее известны. Пакеты с которых сняты галочки должны попасть в блэклист. Дальше отправляем этот список на новый круг для перестройки всего дерева с учётом блэклиста.

-------------
оставаясь в консоли:
Вывести список кандидатов на удаление и предложить пользователю написать список пакетов которые он хотел бы оставить. Оставленные пользователем пакеты в блэклист и на новый круг для перестройки дерева.

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
Поскольку скрипт  принимает список пакетов в качестве параметров, можно сделать  по аналогии с apt-get install package package-. Когда "-" в после названия пакета удаляет его вместо установки.

Минус (или "+" тут нужно подумать что "логичнее") в конце параметра - пакет помечается как неудаляемый.

Графику и интерактивность делать не хочу.

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
От использования apt-cache я в скрипте отказался. По причине того что после замены rpm на apt-cache начались жуткие тормоза. И в этом случае нужно как-то модернизировать сам алгоритм, чтобы уменьшить значительно (по крайней мене на порядок) количество обращений к apt-cache.
Т.е. ИМХО лучше пропустить несколько пакетов, чем выполнение скрипта минуты (вплоть до десятков минут) на не самом слабом компьютере.
« Последнее редактирование: 17.03.2017 21:34:03 от ASte »