如何安装特定版本的Ubuntu软件包?

我想设置一个新的虚拟机,并安装一些指定的软件包(名称和版本),这些软件包是提供的。
例如,apache2的版本为2.2.20-1ubuntu1,并且带有所有依赖项。即使服务器上有新版本的此软件包,也应该安装这个版本。
解决方案必须适用于多个(n个)"设置"。另一个虚拟机可能需要较旧版本的apache2。
目前我知道一些可以安装确切软件包的方法,但扩展性不太好:
  1. 手动将所有所需的*.deb复制到每个虚拟机,并输入:dpkg -i ... -> 可能有效,但容易出错(手动脚本等)。
  2. 为每个设置创建和使用一个新的Ubuntu存储库 -> 不可行,因为我需要n个存储库。
  3. 设置一次机器,然后复制虚拟机/创建快照 -> 不可行,因为我需要存储n个虚拟机。
我的问题可以被称为补丁管理,但我不想将软件包更新到当前版本。我的目标是安装旧版本的软件包。
6个回答

你可以使用apt-get来安装特定版本的软件包,只要它在apt知道的存档中。根据apt-get的手册:
引用: 通过在软件包名称后面加上等号和要选择的软件包的版本,可以选择安装特定版本的软件包。这将导致该版本被定位并选择进行安装。或者,可以通过在软件包名称后面加上斜杠和发行版的版本或存档名称(稳定版、冻结版、不稳定版)来选择特定的发行版。
例如,你可以这样做:
sudo apt-get install apache2=2.2.20-1ubuntu1

请注意,在这种情况下,您可能需要自行解决一些依赖关系问题,但如果出现任何问题,apt-get会告诉您是什么原因导致的。在我的11.10系统上,我需要执行以下步骤才能使其正常工作:
sudo apt-get install apache2=2.2.20-1ubuntu1 \
                     apache2.2-common=2.2.20-1ubuntu1 \
                     apache2.2-bin=2.2.20-1ubuntu1 \
                     apache2-mpm-worker=2.2.20-1ubuntu1

您可以按照以下方式显示可用的软件包版本:
sudo apt list -a apache2

要查看可用的版本,您可以通过以下方式进行查询:

sudo apt-cache madison ^apache2

如果不起作用,请在更新软件包列表之前运行sudo apt-get update
然后复制版本或使用以下语法:
sudo apt-get install apache2=2.2.\*

要检查您安装的版本,请运行以下命令:
dpkg -l 'apache2*' | grep ^i

如果版本信息被截断,请尝试以下方法:
COLUMNS=100 dpkg -l <packageName>

14非常感谢您指出=2.2\*选项作为使用通配符来选择任何可用子版本的方法。这正是我想知道但不知道如何问的 :) - ᴍᴇʜᴏᴠ
1无法找到软件包 ^apache2。在运行 $sudo apt-get update 之后,但是对我来说 sudo apt-get install apache2=2.2* 是有效的。 - Braian Mellor
2@BraianMellor apt-get madison已经过时了,请使用apt-get policy代替,apt-get policy ^apache2是可以的。 - netawater
sudo 在使用 apt-cache 命令时不是必需的;另外,作为附注,apt-cache policy <pkg> 也是一种类似的替代方法。 - creanion
在Ubuntu 22.*上,“madison”选项只显示当前版本(而且答案中的其他命令要么返回错误,要么无法运行)。 - undefined

我将在之前的答案基础上,介绍 apt 家族中其他有用的版本控制命令。要查看可用的版本,请运行 apt-cache policy
# apt-cache policy apache2
apache2:
  Installed: (none)
  Candidate: 2.4.7-1ubuntu4.5
  Version table:
     2.4.10-1ubuntu1.1~ubuntu14.04.1 0
        100 http://us.archive.ubuntu.com/ubuntu/ trusty-backports/main amd64 Packages
     2.4.7-1ubuntu4.5 0
        500 http://security.ubuntu.com/ubuntu/ trusty-security/main amd64 Packages
     2.4.7-1ubuntu4 0
        500 http://us.archive.ubuntu.com/ubuntu/ trusty/main amd64 Packages

然后,如其他地方所提到的,使用apt-get安装特定版本。
# apt-get install apache2=2.4.7-1ubuntu4.5
...

现在你可以通过再次运行apt-cache policy来查看你安装的版本。
# apt-cache policy apache2
apache2:
  Installed: 2.4.7-1ubuntu4.5
  Candidate: 2.4.7-1ubuntu4.5
  Version table:
     2.4.10-1ubuntu1.1~ubuntu14.04.1 0
        100 http://us.archive.ubuntu.com/ubuntu/ trusty-backports/main amd64 Packages
 *** 2.4.7-1ubuntu4.5 0
        500 http://security.ubuntu.com/ubuntu/ trusty-security/main amd64 Packages
        100 /var/lib/dpkg/status
     2.4.7-1ubuntu4 0
        500 http://us.archive.ubuntu.com/ubuntu/ trusty/main amd64 Packages

如果你不想在更新时安装新版本,可以使用apt-mark将软件包固定住。
# apt-mark hold apache2
apache2 set on hold.

假设一个新版本的apache2被添加到软件包索引中,并且您的机器已经通过"apt-get update"同步了。当您下次运行"apt-get upgrade"时,您将看到以下信息:
# apt-get upgrade
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Calculating upgrade... Done
The following packages have been kept back:
  apache2
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.

根据psusi的解释,旧版本不会保存在Ubuntu软件仓库中,但显然你仍然可以在launchpad上找到它们。所以,你需要前往以下网址(将trusty和amd64替换为你的Ubuntu版本和架构):

https://launchpad.net/ubuntu/trusty/amd64/apache2

选择您想要的版本,然后将deb文件下载并使用以下命令进行安装:
dpkg -i apache2_2.4.7-1ubuntu4.20_amd64.deb

再次,将文件名更改为您的文件。如果您不得不降级很多软件包,这可能会变得繁琐,但如果您非常迫切,这总比没有好。

实际上,这是不可能的,因为旧版本没有被保留在存档中,所以除非你有一个旧版本的副本放在某个地方,否则你无法安装它。首先,你应该问自己为什么要安装一个旧版本。在一个稳定的发布版本中,发布新版本的主要原因是为了修复安全漏洞,你肯定不想运行一个存在漏洞的服务器,对吧?

31我需要这个来复制旧环境的精确副本,以供开发目的使用。是否可以设置一个镜像,不删除旧的软件包版本,以便访问所需的软件包?或者我需要设置多个仓库,只保存差异部分? - ayckoster
1@ayckoster,当然可以,你可以运行自己的镜像,或者手动保存所有安装的软件包以备后用(它们会被缓存在 /var/cache/apt/archives 目录下)。 - psusi
24@psusi:并不完全公平的回应,可能有很多原因。在我的情况下,更新版本可能实际上存在一个错误,为了确认这一点,需要使用旧版本进行双重检查。只是一个例子。 - Cookie
1这就是为什么Ubuntu实际上是7个发行版,每个都采用滚动发布模式。现在,我被困住了,无法在我的云端工作,因为Ubuntu在一个核心软件包上出现了回退问题,我无法降级。 - nomen
2@nomen,不,这是一个单一的发行版,不使用滚动发布模式。我们非常注重确保在稳定版本中修复关键错误和安全漏洞时,不会引起退步问题。但如果确实发生了这种情况,请提交一个带有回归发布标签的错误报告,我们将对其进行修复或回滚处理。 - psusi
@psusi:显然,我已经提交了一个错误报告。而且在我能够对此采取任何行动之前,它将不会得到修复。并且包的旧版本已从存档中删除。这就是使Trusty、Precise等成为滚动发行版的原因。Ubuntu是个品牌,而不是一个发行版。 - nomen
@nomen,那不是滚动发布的意思。滚动发布是指系统会持续获得新的、更新的软件,而不是停留在发布时的版本,并且只获取修复关键问题的小补丁。 - psusi
1@psusi:事实上,我无法降级到以前的、没有问题的版本(这样我就可以始终使用他们提供的“最新”版本),这使得它成为一个滚动发行版。是的,我知道它既是滚动式又是稳定的。但它仍然是滚动式的。 - nomen
5@nomen,如果你想为单词创造自己的意义,那么与他人进行沟通将会很困难。对于世界上其他人来说,滚动发布并不是这个意思,滚动发布恰恰相反,并且与稳定发布互斥。现在我想起来了,原始版本随发布一起提供,并保存在“-release”存储区,而更新则放在“-updates”或“-security”存储区,所以虽然无法回到先前的更新,但可以回到最初发布的版本。 - psusi
2@psusi:“滚动发布是指软件不断获得新的、更新的版本,而不是停留在发布时的旧版本。” — psusi。你猜怎么着 —— 在版本号后面加上-1ubuntu就成了一个新版本的软件包。滚动发布的重要之处在于它是滚动式的。通过官方手段是无法回退的。你对于毫无实质内容的语义争论让我感到愤怒。 - nomen
1@nomen,不是的。新版本意味着新的上游版本,通常会有许多新功能和其他更改,而不仅仅是一个关键性错误修复。如果你想看一个滚动发布的例子,可以看arch、debian unstable或者mint的滚动发布。所有自称为滚动发布的发行版都导入了添加新功能的新上游发布。去年有很多人想要将Ubuntu改为滚动发布模式(包括我),但最终决定不这样做。 - psusi
1完美。所以你试图重新定义“新”来表示“上游”。这并不否定我的观点。是你在曲解语言来表达观点。 - nomen
1@psusi: 让我这样说吧。"旧版本可以使用,但是_______不能。" 我应该如何填写这个空白,以满足你对语言的滥用?我认为几乎每个英语使用者都会在那里说"新的"。然而,你却指责我为单词编造了自己的意义。 - nomen
@nomen,不,我告诉你的是全世界其他人对滚动发布的定义。是在使用不同的定义。 - psusi
8@psusi:这里还有一个问题给你。Unison必须在所有机器上始终使用相同的版本。然而,在13.10版本中,它与12.04 LTS版本不同。那现在怎么办?当遇到这样的问题时,得到类似为什么要这样做的回答真的非常没有帮助,对每个人来说都是浪费时间。 - Cookie
@Cookie,你到底在说什么?不同的发布版本当然有不同的版本号,而在完全不同的发布版本上运行一个针对另一个版本的软件包是不被支持的。这个问题不是关于那个的。 - psusi
@psusi:根据http://www.cis.upenn.edu/~bcpierce/unison/download/releases/stable/unison-manual.html上的说明:“服务器机器上安装的Unison版本必须与客户机上的Unison版本相同。”我只是想知道在您的世界中,除了要求每台Ubuntu机器都使用相同的版本之外,还有什么其他方法可以实现这一点? - Cookie
1@Cookie,建议你在本地构建和安装它,而不是使用Ubuntu软件仓库。 - psusi
@psusi:所以你的意思是,我们应该始终从源代码构建,而不是使用带有等号的软件仓库来获取特定版本吗? - Cookie
2@Cookie,你不能使用=操作符,因为代码库中最多只有两个版本:发布时的版本和最新的更新。如果你想要两个完全不同的发布使用相同的版本,目前没有支持的方法可以实现这个需求,所以你需要自己从源代码构建,并处理可能出现的问题。 - psusi
1@psusi:啊,我没意识到这一点。我以为gcc有很多版本,但仔细看了之后,发现它们是作为独立的软件包进行维护的。 - Cookie
2降级的另一个原因是有些人无事可做,只会重写本来就很好的软件,结果雷鸟(Thunderbird)失去了系统托盘图标。所以我宁愿接受一些我永远看不到的漏洞,也不想一直保持雷鸟窗口的打开状态。如果我能降级到52版本的话,那就更好了。 - soger
1非常抱歉如果我在这里发表这个评论的地方不对,但是像Docker这样的工具是独特合适来解决这个问题的。如果发现了特定软件包的问题,您可以回滚到一个包含您所需软件包版本的早期容器镜像。此外,对于较大的网站,使用具有长期过期时间的内容缓存代理部分解决了这个问题。 - deoren

还要考虑对次要版本进行"通配符匹配"

我今天刚刚了解到,PPA的次要版本有时会被移除并替换为其他版本。例如,最近Git PPA https://launchpad.net/~git-core/+archive/ubuntu/ppa 移除了 1:2.36.0-0ppa1~ubuntu20.04.1 并用 1:2.36.1-0ppa1~ubuntu20.04.1 替代了它。这导致了我之前的一些Docker设置出现问题。

幸运的是,我发现在apt install中可以使用通配符,所以我替换了那个有问题的:

sudo apt install git=1:2.36.0-0ppa1~ubuntu20.04.1

使用:

sudo apt install git='1:2.36.*'

希望这样能让我的脚本运行更长一些。