我可以删除重复的快照包中的旧版本吗?

在检查系统中安装的快照包时,我注意到一些包存在重复,其中一个具有较旧的修订号,而另一个具有更新的修订号。对于这样的重复包,我的问题如下:

  1. 为什么会出现重复?
  2. 我可以删除较旧的包以确保更好的磁盘空间管理吗?
  3. 如何删除较旧的包?

以下是具有和没有重复的包的示例:

$ du -hcs /var/lib/snapd/snaps/*
31M /var/lib/snapd/snaps/2048x_3.snap
286M    /var/lib/snapd/snaps/atom_282.snap
4.0K    /var/lib/snapd/snaps/bare_5.snap
72M /var/lib/snapd/snaps/bitwarden_58.snap
72M /var/lib/snapd/snaps/bitwarden_59.snap
196M    /var/lib/snapd/snaps/blender_1113.snap
214M    /var/lib/snapd/snaps/blender_1237.snap
9.1M    /var/lib/snapd/snaps/canonical-livepatch_119.snap
9.1M    /var/lib/snapd/snaps/canonical-livepatch_126.snap
148M    /var/lib/snapd/snaps/chromium_1854.snap
148M    /var/lib/snapd/snaps/chromium_1864.snap
17M /var/lib/snapd/snaps/chromium-ffmpeg_23.snap
18M /var/lib/snapd/snaps/chromium-ffmpeg_24.snap
....

~$ ls -lh /var/lib/snapd/snaps/
total 12G
-rw------- 2 root root  31M Aug  5 06:23 2048x_3.snap
-rw------- 2 root root 286M Aug  5 08:35 atom_282.snap
-rw------- 2 root root 4.0K Sep 22 18:17 bare_5.snap
-rw------- 1 root root  72M Oct 30 00:20 bitwarden_58.snap
-rw------- 1 root root  72M Dec  9 04:28 bitwarden_59.snap
-rw------- 1 root root 196M Nov 18 04:06 blender_1113.snap
-rw------- 1 root root 214M Dec  4 09:39 blender_1237.snap
-rw------- 2 root root 9.1M Nov 17 21:06 canonical-livepatch_119.snap
-rw------- 2 root root 9.1M Nov 22 22:39 canonical-livepatch_126.snap
-rw------- 1 root root 148M Dec 16 04:28 chromium_1854.snap
-rw------- 1 root root 148M Jan  8 08:33 chromium_1864.snap
-rw------- 1 root root  17M Sep  3 06:29 chromium-ffmpeg_23.snap
-rw------- 2 root root  18M Nov 29 14:23 chromium-ffmpeg_24.snap
....
在我查看的系统上,/var/lib/snapd/snaps/*所占用的总磁盘空间为12,180.248 MB。所有重复的软件包(即相同软件包的旧版本)所占用的磁盘空间为4,163.1 MB。简而言之,目前旧版本的软件包占据了12,180.248 MB的34.18%。这似乎是我之前没有意识到使用SNAP应用程序会带来的成本。

假设你不介意冒险的话...为什么不在一个安全的地方备份/var/.../snaps/文件夹,删除旧版本,重新启动,看看是否有效呢?你总是可以恢复备份,然后自己回答这个问题。 - cocomac
2顺便提一下,如果你的空间不足,考虑安装这些软件的 apt 版本。它们更轻巧,一切方面都更好。 - Error404
1@cocomac 我希望我不必进行实验来了解我的问题的答案,因为SNAP软件包已经有一段时间在实践中使用了。现在应该有一些标准操作程序或常识来处理这个问题。 - Sun Bear
Snaps又要改变了(例如:https://ubuntu.com//blog/the-future-of-snapcraft),这可能是@Someone在思考/谈论的内容...适用于一个snap的规则,可能**不适用于**在不同环境下创建的其他snap(core16、core18、core20和即将推出的core22),所以它不是一个*单一*的规则集;尽管当然有很多规则适用于所有情况。 - guiverc
@某人 snap refresh 没有移除重复的软件包。 - Sun Bear
@某人 我发现Snap软件包通常比apt软件包更为更新。然而,它们有时可能会更麻烦使用。一些功能需要额外的命令来启用。但我听说它们使用起来更轻便。感谢你提供的许多有帮助的意见。 :) - Sun Bear
@某人 我已经将你、matigo和user535733的意见融入到这个remove_disabled_snap_pkgs.py Python脚本中,来完成这项任务。 - Sun Bear
@SunBear 干得好!我喜欢把所有事情都用脚本来做。我已经克隆了你的代码库,但还没有测试过。希望它能正常工作 :D - Error404
@某人 谢谢。如果需要改进/纠正,请告诉我。在我的这边是有效的。 :) - Sun Bear
@某人,我已经给脚本添加了更多的评论。 - Sun Bear
3个回答

回答你的问题: 为什么它们会重复? ⇢ 它们是不同的修订版本,而不是重复。 我可以删除旧的软件包以确保更好的磁盘空间管理吗? ⇢ 是的。毕竟这是你的电脑。 如何删除旧的软件包? 你可以在终端中这样做:
snap remove {snap} --revision={revision}
你还可以告诉系统限制自己的过去版本数量,就像这样:
sudo snap set system refresh.retain=2

注意: 值必须介于220之间,一般推荐选择像23这样的数字以节省存储空间,并在发生错误更新时可以进行回滚。

如果您想列出所有快照及其版本,可以运行以下命令:

snap list --all
这将给你带来类似的东西:
Name                 Version                     Rev    Tracking         Publisher   Notes
bare                 1.0                         5      latest/stable    canonical✓  base
canonical-livepatch  10.0.1                      119    latest/stable    canonical✓  disabled
canonical-livepatch  10.1.2                      126    latest/stable    canonical✓  -
core                 16-2.52                     11798  latest/stable    canonical✓  core,disabled
core                 16-2.52.1                   11993  latest/stable    canonical✓  core
core18               20211028                    2253   latest/stable    canonical✓  base
core18               20211015                    2246   latest/stable    canonical✓  base,disabled
core20               20211115                    1242   latest/stable    canonical✓  base,disabled
core20               20211129                    1270   latest/stable    canonical✓  base
gnome-3-28-1804      3.28.0-19-g98f9e67.98f9e67  145    latest/stable    canonical✓  disabled
gnome-3-28-1804      3.28.0-19-g98f9e67.98f9e67  161    latest/stable    canonical✓  -
gnome-3-34-1804      0+git.3556cb3               77     latest/stable/…  canonical✓  -
gnome-3-34-1804      0+git.3556cb3               72     latest/stable/…  canonical✓  disabled
gnome-3-38-2004      0+git.cd626d1               87     latest/stable    canonical✓  -
gnome-3-38-2004      0+git.6ba6040               76     latest/stable    canonical✓  disabled
gtk-common-themes    0.1-52-gb92ac40             1515   latest/stable/…  canonical✓  disabled
gtk-common-themes    0.1-59-g7bca6ae             1519   latest/stable/…  canonical✓  -
snap-store           3.38.0-66-gbd5b8f7          558    latest/stable/…  canonical✓  -
snap-store           3.38.0-64-g23c4c77          547    latest/stable/…  canonical✓  disabled
snapd                2.53.2                      14066  latest/stable    canonical✓  snapd,disabled
snapd                2.53.4                      14295  latest/stable    canonical✓  snapd

需要一个脚本吗? 重要提示:在继续之前,您应该检查计算机上的 snap list --all 命令的输出,并且以下是一个脚本,如果您使用的区域设置不是 en_US.UTF-8,则不应该直接复制/粘贴。 脚本内容如下:
#!/bin/bash
# This script will remove disabled snap revisions.
set -eu

LANG=C snap list --all | awk '/disabled/{print $1, $3}' |
    while read name rev; do
        snap remove "$name" --revision="$rev"
    done
这将运行`snap list -all`命令,并提取包含单词`disabled`的行。这将根据您的语言环境而有所不同,因此请先检查函数的输出,然后更新`awk '/disabled/`以替换为在您的输出中找到的标签。 将脚本保存到文件中(例如`scrub-snaps.sh`),然后将其设置为可执行。
sudo chmod +x scrub-snaps.sh

现在你可以运行它,记得使用 sudo

sudo ./scrub-snaps.sh
注意:脚本中并没有包含sudo,但如果您希望在其中添加它,可以这样做。无论如何,如果需要,您都将被要求输入密码。

你有没有一个命令可以删除所有已安装的Snap软件包的旧版本?逐个删除它们真的很麻烦。 - Sun Bear
谢谢。我还在这里分享了一个答案(链接:https://askubuntu.com/a/1386544/541417),以便在执行任何禁用的Snap包删除之前进行视觉检查。 - Sun Bear
你可以创建一个别名来执行这个oneliner - Pablo Bianchi

保留至少一个旧版本的快照是设计中固有的。 快照包最初是为各种环境设计的,这些环境通常没有人工管理员,或者无法连接键盘和显示器,比如手机或物联网设备。对于这些系统来说,弹性是一个关键要求:如果应用程序崩溃或升级损坏,需要一种无需人工干预的回滚保证。因此,至少需要保留一个旧版本。 对于传统桌面和服务器用户来说,他们并不太关心回滚功能。他们喜欢快照的不同设计元素:独立于操作系统的安全自动升级、只读的squashfs防篡改、进程限制等等。 但这都是一个统一的标准,所以你也可以获得回滚功能,即使你认为自己不会使用它。 你不能“禁用”快照的回滚功能——它实际上不是一个“特性”,而是一个关键的设计元素。

谢谢你的解释。根据我所查看的系统,我发现Snap的最小冗余特性的成本至少占所有Snap包的总磁盘空间的34%。 - Sun Bear
1尽管设计意图是为了冗余性,Snap开发团队是否还提供了一个命令来删除所有较旧的snap软件包修订版本?此功能是为了更好地服务于那些并不太关心回滚功能的经典桌面和服务器用户。 - Sun Bear
当然,@matigo的回答解释了“如何”。而这个回答则通过解释“为什么”来补充完善。 - user535733

在 @matigo 的回答基础上,我编写了一个 Python 脚本 来自动删除已禁用的 SNAP 包。它允许用户在继续(或不继续)删除过程之前对 snap 包进行可视化检查。链接中展示了执行脚本的示例。

remove_disabled_snap_pkgs.py

#!/bin/python3
''' This python script automates the removal of all disabled SNAP packages in
a system. Doing so helps free up the system's disk space. This outcome can be
significant in the situation where many disabled SNAP packages are retained in
the system.
'''
from subprocess import run, PIPE, CalledProcessError
from pathlib import Path
import sys

# Assumptions
SNAP_PKGS_PATH = Path('/var/lib/snapd/snaps/')
# Also, at a minimum, this directory has at least one xxx.snap file there. 


def snap_list():
    '''Function to execute a bash 'snap list' cmd and returns a Python
    dictionary of info of the ACTIVE SNAPCRAFT pkgs in the system.

    pkgs_dict = {Name : {'Version':'xxx', 'Rev':'xxx', 'Tracking':'xxx',
                         'Publisher':'xxx', 'Notes':'xxx'}
                }
    '''
    try:
        cmd = ['snap', 'list']
        completed = run(cmd, check=True, stdout=PIPE)
    except CalledProcessError as err:
        print('ERROR:', err)
    else:
        headers = completed.stdout.decode('utf-8').splitlines()[0].split()
        pkgs=[line.split() for line in
              completed.stdout.decode('utf-8').splitlines()[1:]]
        pkgs_dict = {}
        for pkg in pkgs:
            pkgs_dict[pkg[0]] = {i:pkg[n+1] for n, i in enumerate(headers[1:])}
        return pkgs_dict


# 1. Get all SNAPCRAFT pkgs in system
all_path = sorted(SNAP_PKGS_PATH.glob('*.snap'))
all_size = sum([p.stat().st_size for p in all_path])

# 2. Get active SNAPCRAFT pkgs in system
active_snap_pkgs = snap_list()
active_path = [SNAP_PKGS_PATH / Path(k+'_'+v['Rev']+'.snap')
               for k, v in active_snap_pkgs.items()]
active_size = sum([p.stat().st_size for p in active_path])

# 3. Display info and instructions in terminal
print(f'ALL (ACTIVE & DISABLED) SNAP PACKAGES IN SYSTEM:')
for n, i in enumerate(all_path):
    size = i.stat().st_size
    if i in active_path:
        print(f'Active\t{size:>12}\t{i}')
    else:
        print(f'      \t{size:>12}\t{i}')

# 4. Show stats on total size of All, Active & Disabled SNAPCRAFT packages 
width = 12
disabled_size = all_size - active_size
print('\nSIZE OF SNAP PACKAGES:')
print(f'1. All      : {all_size:>{width}} bytes')
print(f'2. Active   : {active_size:>{width}} bytes')
print(f'2. Disabled : {disabled_size:>{width}} bytes or '
      f'{(disabled_size/all_size):.2%} of All')

# 5. Make decision to remove or not to remove Disabled SNAPCRAFT packages 
if disabled_size > 0:
    print(f'\nREMOVE ALL DISABLED SNAP PACKAGES? [y/n]')
    while True:
        decision = input()
        if decision in ['y', 'Y', 'yes', 'Yes', 'YES']:
            print('Removal in progress... pls wait')
            for p in all_path:
                if p not in active_path:
                    stem = p.stem
                    bar_index = stem.index('_')
                    name = stem[:bar_index]
                    revision = stem[bar_index+1:]
                    cmd = ['sudo', 'snap', 'remove', name,
                           '--revision='+revision]
                    print(f"\n{' '.join(cmd)}")
                    run(cmd, stdout=sys.stdout, stderr=sys.stderr,
                        encoding='utf8')
            print(f'\nREMOVE ALL DISABLED SNAP PACKAGES? COMPLETED.')
            break
        elif decision in ['n', 'N', 'no', 'No', 'NO']:
            print(f'\nNO REMOVAL IS PERFORMED.')
            break
        else:
            print('Please enter only "y" or "n":')
else:
    print(f'\nNO REMOVAL IS NEEDED.')