分类 技术 下的文章

EPEL 代表 “Extra Packages for Enterprise Linux”,它是一个自由开源的附加软件包仓库,可用于 CentOS 和 RHEL 服务器。顾名思义,EPEL 仓库提供了额外的软件包,这些软件在 CentOS 8RHEL 8 的默认软件包仓库中不可用。

在本文中,我们将演示如何在 CentOS 8 和 RHEL 8 服务器上启用和使用 EPEL 存储库。

EPEL 仓库的先决条件

  • 最小化安装的 CentOS 8 和 RHEL 8 服务器
  • root 或 sudo 管理员权限
  • 网络连接

在 RHEL 8.x 服务器上安装并启用 EPEL 仓库

登录或 SSH 到你的 RHEL 8.x 服务器,并执行以下 dnf 命令来安装 EPEL rpm 包,

[root@linuxtechi ~]# dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm -y

上面命令的输出将如下所示,

dnf-install-epel-repo-rehl8

EPEL rpm 包成功安装后,它将自动启用并配置其 yum/dnf 仓库。运行以下 dnfyum 命令,以验证是否启用了 EPEL 仓库,

[root@linuxtechi ~]# dnf repolist epel
或者
[root@linuxtechi ~]# dnf repolist epel -v

epel-repolist-rhel8

在 CentOS 8.x 服务器上安装并启用 EPEL 仓库

登录或 SSH 到你的 CentOS 8 服务器,并执行以下 dnfyum 命令来安装 epel-release rpm 软件包。在 CentOS 8 服务器中,EPEL rpm 在其默认软件包仓库中。

[root@linuxtechi ~]# dnf install epel-release -y
或者
[root@linuxtechi ~]# yum install epel-release -y

执行以下命令来验证 CentOS 8 服务器上 EPEL 仓库的状态,

[root@linuxtechi ~]# dnf repolist epel
Last metadata expiration check: 0:00:03 ago on Sun 13 Oct 2019 04:18:05 AM BST.
repo id              repo name                                               status
*epel                Extra Packages for Enterprise Linux 8 - x86_64          1,977
[root@linuxtechi ~]#
[root@linuxtechi ~]# dnf repolist epel -v
……………………
Repo-id      : epel
Repo-name    : Extra Packages for Enterprise Linux 8 - x86_64
Repo-status  : enabled
Repo-revision: 1570844166
Repo-updated : Sat 12 Oct 2019 02:36:32 AM BST
Repo-pkgs    : 1,977
Repo-size    : 2.1 G
Repo-metalink: https://mirrors.fedoraproject.org/metalink?repo=epel-8&arch=x86_64&infra=stock&content=centos
  Updated    : Sun 13 Oct 2019 04:28:24 AM BST
Repo-baseurl : rsync://repos.del.extreme-ix.org/epel/8/Everything/x86_64/ (34 more)
Repo-expire  : 172,800 second(s) (last: Sun 13 Oct 2019 04:28:24 AM BST)
Repo-filename: /etc/yum.repos.d/epel.repo
Total packages: 1,977
[root@linuxtechi ~]#

以上命令的输出说明我们已经成功启用了 EPEL 仓库。让我们在 EPEL 仓库上执行一些基本操作。

列出 EPEL 仓库种所有可用包

如果要列出 EPEL 仓库中的所有的软件包,请运行以下 dnf 命令,

[root@linuxtechi ~]# dnf repository-packages epel list
……………
Last metadata expiration check: 0:38:18 ago on Sun 13 Oct 2019 04:28:24 AM BST.
Installed Packages
epel-release.noarch                   8-6.el8                @epel
Available Packages
BackupPC.x86_64                       4.3.1-2.el8            epel
BackupPC-XS.x86_64                    0.59-3.el8             epel
CGSI-gSOAP.x86_64                     1.3.11-7.el8           epel
CGSI-gSOAP-devel.x86_64               1.3.11-7.el8           epel
Field3D.x86_64                        1.7.2-16.el8           epel
Field3D-devel.x86_64                  1.7.2-16.el8           epel
GraphicsMagick.x86_64                 1.3.33-1.el8           epel
GraphicsMagick-c++.x86_64             1.3.33-1.el8           epel
…………………………
zabbix40-web-mysql.noarch             4.0.12-1.el8           epel
zabbix40-web-pgsql.noarch             4.0.12-1.el8           epel
zerofree.x86_64                       1.1.1-3.el8            epel
zimg.x86_64                           2.8-4.el8              epel
zimg-devel.x86_64                     2.8-4.el8              epel
zstd.x86_64                           1.4.2-1.el8            epel
zvbi.x86_64                           0.2.35-9.el8           epel
zvbi-devel.x86_64                     0.2.35-9.el8           epel
zvbi-fonts.noarch                     0.2.35-9.el8           epel
[root@linuxtechi ~]#

从 EPEL 仓库中搜索软件包

假设我们要搜索 EPEL 仓库中的 Zabbix 包,请执行以下 dnf 命令,

[root@linuxtechi ~]# dnf repository-packages epel list | grep -i zabbix

上面命令的输出类似下面这样,

epel-repo-search-package-centos8

从 EPEL 仓库安装软件包

假设我们要从 EPEL 仓库安装 htop 包,运行以下 dnf 命令,

语法:

# dnf –enablerepo=”epel” install <包名>
[root@linuxtechi ~]# dnf --enablerepo="epel" install htop -y

注意:如果我们在上面的命令中未指定 –enablerepo=epel,那么它将在所有可用的软件包仓库中查找 htop 包。

本文就是这些内容了,我希望上面的步骤能帮助你在 CentOS 8 和 RHEL 8 服务器上启用并配置 EPEL 仓库,请在下面的评论栏分享你的评论和反馈。


via: https://www.linuxtechi.com/enable-epel-repo-centos8-rhel8-server/

作者:Pradeep Kumar 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

数组是一种有用的数据类型,用于管理在连续内存位置中建模最好的集合元素。下面是如何有效地使用它们。

 title=

有使用 C 或者 FORTRAN 语言编程经验的人会对数组的概念很熟悉。它们基本上是一个连续的内存块,其中每个位置都是某种数据类型:整型、浮点型或者诸如此类的数据类型。

Java 的情况与此类似,但是有一些额外的问题。

一个数组的示例

让我们在 Java 中创建一个长度为 10 的整型数组:

int[] ia = new int[10];

上面的代码片段会发生什么?从左到右依次是:

  1. 最左边的 int[] 将变量的类型声明为 int 数组(由 [] 表示)。
  2. 它的右边是变量的名称,当前为 ia
  3. 接下来,= 告诉我们,左侧定义的变量赋值为右侧的内容。
  4. = 的右侧,我们看到了 new,它在 Java 中表示一个对象正在被初始化中,这意味着已为其分配存储空间并调用了其构造函数(请参见此处以获取更多信息)。
  5. 然后,我们看到 int[10],它告诉我们正在初始化的这个对象是包含 10 个整型的数组。

因为 Java 是强类型的,所以变量 ia 的类型必须跟 = 右侧表达式的类型兼容。

初始化示例数组

让我们把这个简单的数组放在一段代码中,并尝试运行一下。将以下内容保存到一个名为 Test1.java 的文件中,使用 javac 编译,使用 java 运行(当然是在终端中):

import java.lang.*;

public class Test1 {

    public static void main(String[] args) {
        int[] ia = new int[10];                              // 见下文注 1
        System.out.println("ia is " + ia.getClass());        // 见下文注 2
        for (int i = 0; i < ia.length; i++)                  // 见下文注 3
            System.out.println("ia[" + i + "] = " + ia[i]);  // 见下文注 4
    }

}

让我们来看看最重要的部分。

  1. 我们声明和初始化了长度为 10 的整型数组,即 ia,这显而易见。
  2. 在下面的行中,我们看到表达式 ia.getClass()。没错,ia 是属于一个对象,这行代码将告诉我们是哪个类。
  3. 在紧接的下一行中,我们看到了一个循环 for (int i = 0; i < ia.length; i++),它定义了一个循环索引变量 i,该变量遍历了从 0 到比 ia.length 小 1 的序列,这个表达式告诉我们在数组 ia 中定义了多少个元素。
  4. 接下来,循环体打印出 ia 的每个元素的值。

当这个程序编译和运行时,它产生以下结果:

me@mydesktop:~/Java$ javac Test1.java
me@mydesktop:~/Java$ java Test1
ia is class [I
ia[0] = 0
ia[1] = 0
ia[2] = 0
ia[3] = 0
ia[4] = 0
ia[5] = 0
ia[6] = 0
ia[7] = 0
ia[8] = 0
ia[9] = 0
me@mydesktop:~/Java$

ia.getClass() 的输出的字符串表示形式是 [I,它是“整数数组”的简写。与 C 语言类似,Java 数组以第 0 个元素开始,扩展到第 <数组大小> - 1 个元素。如上所见,我们可以看到数组 ia 的每个元素都(似乎由数组构造函数)设置为零。

所以,就这些吗?声明类型,使用适当的初始化器,就完成了吗?

好吧,并没有。在 Java 中有许多其它方法来初始化数组。

为什么我要初始化一个数组,有其它方式吗?

像所有好的问题一样,这个问题的答案是“视情况而定”。在这种情况下,答案取决于初始化后我们希望对数组做什么。

在某些情况下,数组自然会作为一种累加器出现。例如,假设我们正在编程实现计算小型办公室中一组电话分机接收和拨打的电话数量。一共有 8 个分机,编号为 1 到 8,加上话务员的分机,编号为 0。 因此,我们可以声明两个数组:

int[] callsMade;
int[] callsReceived;

然后,每当我们开始一个新的累计呼叫统计数据的周期时,我们就将每个数组初始化为:

callsMade = new int[9];
callsReceived = new int[9];

在每个累计通话统计数据的最后阶段,我们可以打印出统计数据。粗略地说,我们可能会看到:

import java.lang.*;
import java.io.*;

public class Test2 {

    public static void main(String[] args) {

        int[] callsMade;
        int[] callsReceived;

        // 初始化呼叫计数器

        callsMade = new int[9];
        callsReceived = new int[9];

        // 处理呼叫……
        //   分机拨打电话:callsMade[ext]++
        //   分机接听电话:callsReceived[ext]++

        // 汇总通话统计

        System.out.printf("%3s%25s%25s\n", "ext", " calls made",
                "calls received");
        for (int ext = 0; ext < callsMade.length; ext++) {
            System.out.printf("%3d%25d%25d\n", ext,
                    callsMade[ext], callsReceived[ext]);
        }

    }

}

这会产生这样的输出:

me@mydesktop:~/Java$ javac Test2.java
me@mydesktop:~/Java$ java Test2
ext               calls made           calls received
  0                        0                        0
  1                        0                        0
  2                        0                        0
  3                        0                        0
  4                        0                        0
  5                        0                        0
  6                        0                        0
  7                        0                        0
  8                        0                        0
me@mydesktop:~/Java$

看来这一天呼叫中心不是很忙。

在上面的累加器示例中,我们看到由数组初始化程序设置的零起始值可以满足我们的需求。但是在其它情况下,这个起始值可能不是正确的选择。

例如,在某些几何计算中,我们可能需要将二维数组初始化为单位矩阵(除沿主对角线———左上角到右下角——以外所有全是零)。我们可以选择这样做:

double[][] m = new double[3][3];
for (int d = 0; d < 3; d++) {
    m[d][d] = 1.0;
}

在这种情况下,我们依靠数组初始化器 new double[3][3] 将数组设置为零,然后使用循环将主对角线上的元素设置为 1。在这种简单情况下,我们可以使用 Java 提供的快捷方式:

double[][] m = {
        {1.0, 0.0, 0.0},
        {0.0, 1.0, 0.0},
        {0.0, 0.0, 1.0}};

这种可视结构特别适用于这种应用程序,在这种应用程序中,它便于复查数组的实际布局。但是在这种情况下,行数和列数只在运行时确定时,我们可能会看到这样的东西:

int nrc;
// 一些代码确定行数和列数 = nrc
double[][] m = new double[nrc][nrc];
for (int d = 0; d < nrc; d++) {
    m[d][d] = 1.0;
}

值得一提的是,Java 中的二维数组实际上是数组的数组,没有什么能阻止无畏的程序员让这些第二层数组中的每个数组的长度都不同。也就是说,下面这样的事情是完全合法的:

int [][] differentLengthRows = {
     {1, 2, 3, 4, 5},
     {6, 7, 8, 9},
     {10, 11, 12},
     {13, 14},
     {15}};

在涉及不规则形状矩阵的各种线性代数应用中,可以应用这种类型的结构(有关更多信息,请参见此 Wikipedia 文章)。除此之外,既然我们了解到二维数组实际上是数组的数组,那么以下内容也就不足为奇了:

differentLengthRows.length

可以告诉我们二维数组 differentLengthRows 的行数,并且:

differentLengthRows[i].length

告诉我们 differentLengthRowsi 行的列数。

深入理解数组

考虑到在运行时确定数组大小的想法,我们看到数组在实例化之前仍需要我们知道该大小。但是,如果在处理完所有数据之前我们不知道大小怎么办?这是否意味着我们必须先处理一次以找出数组的大小,然后再次处理?这可能很难做到,尤其是如果我们只有一次机会使用数据时。

Java 集合框架很好地解决了这个问题。提供的其中一项是 ArrayList 类,它类似于数组,但可以动态扩展。为了演示 ArrayList 的工作原理,让我们创建一个 ArrayList 对象并将其初始化为前 20 个斐波那契数字

import java.lang.*;
import java.util.*;

public class Test3 {

    public static void main(String[] args) {

        ArrayList<Integer> fibos = new ArrayList<Integer>();

        fibos.add(0);
        fibos.add(1);
        for (int i = 2; i < 20; i++) {
            fibos.add(fibos.get(i - 1) + fibos.get(i - 2));
        }

        for (int i = 0; i < fibos.size(); i++) {
            System.out.println("fibonacci " + i + " = " + fibos.get(i));
        }

    }
}

上面的代码中,我们看到:

  • 用于存储多个 IntegerArrayList 的声明和实例化。
  • 使用 add() 附加到 ArrayList 实例。
  • 使用 get() 通过索引号检索元素。
  • 使用 size() 来确定 ArrayList 实例中已经有多少个元素。

这里没有展示 put() 方法,它的作用是将一个值放在给定的索引号上。

该程序的输出为:

fibonacci 0 = 0
fibonacci 1 = 1
fibonacci 2 = 1
fibonacci 3 = 2
fibonacci 4 = 3
fibonacci 5 = 5
fibonacci 6 = 8
fibonacci 7 = 13
fibonacci 8 = 21
fibonacci 9 = 34
fibonacci 10 = 55
fibonacci 11 = 89
fibonacci 12 = 144
fibonacci 13 = 233
fibonacci 14 = 377
fibonacci 15 = 610
fibonacci 16 = 987
fibonacci 17 = 1597
fibonacci 18 = 2584
fibonacci 19 = 4181

ArrayList 实例也可以通过其它方式初始化。例如,可以给 ArrayList 构造器提供一个数组,或者在编译过程中知道初始元素时也可以使用 List.of()array.aslist() 方法。我发现自己并不经常使用这些方式,因为我对 ArrayList 的主要用途是当我只想读取一次数据时。

此外,对于那些喜欢在加载数据后使用数组的人,可以使用 ArrayListtoArray() 方法将其实例转换为数组;或者,在初始化 ArrayList 实例之后,返回到当前数组本身。

Java 集合框架提供了另一种类似数组的数据结构,称为 Map(映射)。我所说的“类似数组”是指 Map 定义了一个对象集合,它的值可以通过一个键来设置或检索,但与数组(或 ArrayList)不同,这个键不需要是整型数;它可以是 String 或任何其它复杂对象。

例如,我们可以创建一个 Map,其键为 String,其值为 Integer 类型,如下:

Map<String, Integer> stoi = new Map<String, Integer>();

然后我们可以对这个 Map 进行如下初始化:

stoi.set("one",1);
stoi.set("two",2);
stoi.set("three",3);

等类似操作。稍后,当我们想要知道 "three" 的数值时,我们可以通过下面的方式将其检索出来:

stoi.get("three");

在我的认知中,Map 对于将第三方数据集中出现的字符串转换为我的数据集中的一致代码值非常有用。作为数据转换管道的一部分,我经常会构建一个小型的独立程序,用作在处理数据之前清理数据;为此,我几乎总是会使用一个或多个 Map

值得一提的是,ArrayListArrayListMapMap 是很可能的,有时也是合理的。例如,假设我们在看树,我们对按树种和年龄范围累计树的数目感兴趣。假设年龄范围定义是一组字符串值(“young”、“mid”、“mature” 和 “old”),物种是 “Douglas fir”、“western red cedar” 等字符串值,那么我们可以将这个 Map 中的 Map 定义为:

Map<String, Map<String, Integer>> counter = new Map<String, Map<String, Integer>>();

这里需要注意的一件事是,以上内容仅为 Map创建存储。因此,我们的累加代码可能类似于:

// 假设我们已经知道了物种和年龄范围
if (!counter.containsKey(species)) {
    counter.put(species,new Map<String, Integer>());
}
if (!counter.get(species).containsKey(ageRange)) {
    counter.get(species).put(ageRange,0);
}

此时,我们可以这样开始累加:

counter.get(species).put(ageRange, counter.get(species).get(ageRange) + 1);

最后,值得一提的是(Java 8 中的新特性)Streams 还可以用来初始化数组、ArrayList 实例和 Map 实例。关于此特性的详细讨论可以在此处此处中找到。


via: https://opensource.com/article/19/10/initializing-arrays-java

作者:Chris Hermansen 选题:lujun9972 译者:laingke 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

上一篇文章中,我们研究了什么是 RPM 软件包。它们是包含文件和元数据的档案文件。当安装或卸载 RPM 时,此元数据告诉 RPM 在哪里创建或删除文件。正如你将在上一篇文章中记住的,元数据还包含有关“依赖项”的信息,它可以是“运行时”或“构建时”的依赖信息。

例如,让我们来看看 fpaste。你可以使用 dnf 下载该 RPM。这将下载 Fedora 存储库中可用的 fpaste 最新版本。在 Fedora 30 上,当前版本为 0.3.9.2:

$ dnf download fpaste

...
fpaste-0.3.9.2-2.fc30.noarch.rpm

由于这是个构建 RPM,因此它仅包含使用 fpaste 所需的文件:

$ rpm -qpl ./fpaste-0.3.9.2-2.fc30.noarch.rpm
/usr/bin/fpaste
/usr/share/doc/fpaste
/usr/share/doc/fpaste/README.rst
/usr/share/doc/fpaste/TODO
/usr/share/licenses/fpaste
/usr/share/licenses/fpaste/COPYING
/usr/share/man/man1/fpaste.1.gz

源 RPM

在此链条中的下一个环节是源 RPM。Fedora 中的所有软件都必须从其源代码构建。我们不包括预构建的二进制文件。因此,要制作一个 RPM 文件,RPM(工具)需要:

  • 给出必须要安装的文件,
  • 例如,如果要编译出这些文件,则告诉它们如何生成这些文件,
  • 告知必须在何处安装这些文件,
  • 该特定软件需要其他哪些依赖才能正常工作。

源 RPM 拥有所有这些信息。源 RPM 与构建 RPM 相似,但顾名思义,它们不包含已构建的二进制文件,而是包含某个软件的源文件。让我们下载 fpaste 的源 RPM:

$ dnf download fpaste --source

...
fpaste-0.3.9.2-2.fc30.src.rpm

注意文件的结尾是 src.rpm。所有的 RPM 都是从源 RPM 构建的。你也可以使用 dnf 轻松检查“二进制” RPM 的源 RPM:

$ dnf repoquery --qf "%{SOURCERPM}" fpaste
fpaste-0.3.9.2-2.fc30.src.rpm

另外,由于这是源 RPM,因此它不包含构建的文件。相反,它包含有关如何从中构建 RPM 的源代码和指令:

$ rpm -qpl ./fpaste-0.3.9.2-2.fc30.src.rpm
fpaste-0.3.9.2.tar.gz
fpaste.spec

这里,第一个文件只是 fpaste 的源代码。第二个是 spec 文件。spec 文件是个配方,可告诉 RPM(工具)如何使用源 RPM 中包含的源代码创建 RPM(档案文件)— 它包含 RPM(工具)构建 RPM(档案文件)所需的所有信息。在 spec 文件中。当我们软件包维护人员添加软件到 Fedora 中时,我们大部分时间都花在编写和完善 spec 文件上。当软件包需要更新时,我们会回过头来调整 spec 文件。你可以在 https://src.fedoraproject.org/browse/projects/ 的源代码存储库中查看 Fedora 中所有软件包的 spec 文件。

请注意,一个源 RPM 可能包含构建多个 RPM 的说明。fpaste 是一款非常简单的软件,一个源 RPM 生成一个“二进制” RPM。而 Python 则更复杂。虽然只有一个源 RPM,但它会生成多个二进制 RPM:

$ sudo dnf repoquery --qf "%{SOURCERPM}" python3
python3-3.7.3-1.fc30.src.rpm
python3-3.7.4-1.fc30.src.rpm

$ sudo dnf repoquery --qf "%{SOURCERPM}" python3-devel
python3-3.7.3-1.fc30.src.rpm
python3-3.7.4-1.fc30.src.rpm

$ sudo dnf repoquery --qf "%{SOURCERPM}" python3-libs
python3-3.7.3-1.fc30.src.rpm
python3-3.7.4-1.fc30.src.rpm

$ sudo dnf repoquery --qf "%{SOURCERPM}" python3-idle
python3-3.7.3-1.fc30.src.rpm
python3-3.7.4-1.fc30.src.rpm

$ sudo dnf repoquery --qf "%{SOURCERPM}" python3-tkinter
python3-3.7.3-1.fc30.src.rpm
python3-3.7.4-1.fc30.src.rpm

用 RPM 行话来讲,“python3” 是“主包”,因此该 spec 文件将称为 python3.spec。所有其他软件包均为“子软件包”。你可以下载 python3 的源 RPM,并查看其中的内容。(提示:补丁也是源代码的一部分):

$ dnf download --source python3
python3-3.7.4-1.fc30.src.rpm

$ rpm -qpl ./python3-3.7.4-1.fc30.src.rpm
00001-rpath.patch
00102-lib64.patch
00111-no-static-lib.patch
00155-avoid-ctypes-thunks.patch
00170-gc-assertions.patch
00178-dont-duplicate-flags-in-sysconfig.patch
00189-use-rpm-wheels.patch
00205-make-libpl-respect-lib64.patch
00251-change-user-install-location.patch
00274-fix-arch-names.patch
00316-mark-bdist_wininst-unsupported.patch
Python-3.7.4.tar.xz
check-pyc-timestamps.py
idle3.appdata.xml
idle3.desktop
python3.spec

从源 RPM 构建 RPM

现在我们有了源 RPM,并且其中有什么内容,我们可以从中重建 RPM。但是,在执行此操作之前,我们应该设置系统以构建 RPM。首先,我们安装必需的工具:

$ sudo dnf install fedora-packager

这将安装 rpmbuild 工具。rpmbuild 需要一个默认布局,以便它知道源 RPM 中每个必需组件的位置。让我们看看它们是什么:

# spec 文件将出现在哪里?
$ rpm -E %{_specdir}
/home/asinha/rpmbuild/SPECS

# 源代码将出现在哪里?
$ rpm -E %{_sourcedir}
/home/asinha/rpmbuild/SOURCES

# 临时构建目录是哪里?
$ rpm -E %{_builddir}
/home/asinha/rpmbuild/BUILD

# 构建根目录是哪里?
$ rpm -E %{_buildrootdir}
/home/asinha/rpmbuild/BUILDROOT

# 源 RPM 将放在哪里?
$ rpm -E %{_srcrpmdir}
/home/asinha/rpmbuild/SRPMS

# 构建的 RPM 将放在哪里?
$ rpm -E %{_rpmdir}
/home/asinha/rpmbuild/RPMS

我已经在系统上设置了所有这些目录:

$ cd
$ tree -L 1 rpmbuild/
rpmbuild/
├── BUILD
├── BUILDROOT
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS

6 directories, 0 files

RPM 还提供了一个为你全部设置好的工具:

$ rpmdev-setuptree

然后,确保已安装 fpaste 的所有构建依赖项:

sudo dnf builddep fpaste-0.3.9.2-3.fc30.src.rpm

对于 fpaste,你只需要 Python,并且它肯定已经安装在你的系统上(dnf 也使用 Python)。还可以给 builddep 命令一个 spec 文件,而不是源 RPM。在手册页中了解更多信息:

$ man dnf.plugin.builddep

现在我们有了所需的一切,从源 RPM 构建一个 RPM 就像这样简单:

$ rpmbuild --rebuild fpaste-0.3.9.2-3.fc30.src.rpm
..
..

$ tree ~/rpmbuild/RPMS/noarch/
/home/asinha/rpmbuild/RPMS/noarch/
└── fpaste-0.3.9.2-3.fc30.noarch.rpm

0 directories, 1 file

rpmbuild 将安装源 RPM 并从中构建你的 RPM。现在,你可以使用 dnf 安装 RPM 以使用它。当然,如前所述,如果你想在 RPM 中进行任何更改,则必须修改 spec 文件,我们将在下一篇文章中介绍 spec 文件。

总结

总结一下这篇文章有两点:

  • 我们通常安装使用的 RPM 是包含软件的构建版本的 “二进制” RPM
  • 构建 RPM 来自于源 RPM,源 RPM 包括用于生成二进制 RPM 所需的源代码和规范文件。

如果你想开始构建 RPM,并帮助 Fedora 社区维护我们提供的大量软件,则可以从这里开始: https://fedoraproject.org/wiki/Join_the_package_collection_maintainers

如有任何疑问,请发邮件到 Fedora 开发人员邮件列表,我们随时乐意为你提供帮助!


via: https://fedoramagazine.org/how-rpm-packages-are-made-the-source-rpm/

作者:Ankur Sinha FranciscoD 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你可以通过多种方法来调整 Ubuntu,以自定义其外观和行为。我发现最简单的方法是使用 GNOME 优化工具。它也被称为 GNOME Tweak 或简单地称为 Tweak(优化)。

在过去的教程中,我已经多次介绍过它。在这里,我列出了你可以使用此工具执行的所有主要优化。

我在这里使用的是 Ubuntu,但是这些步骤应该适用于使用 GNOME 桌面环境的任何 Linux 发行版。

在 Ubuntu 18.04 或其它版本上安装 GNOME 优化工具

GNOME 优化工具可从 Ubuntu 中的 Universe 存储库中安装,因此请确保已在“软件和更新”工具中启用了该仓库:

在 Ubuntu 中启用 Universe 存储库

之后,你可以从软件中心安装 GNOME 优化工具。只需打开软件中心并搜索 “GNOME Tweaks” 并从那里安装它:

从软件中心安装 GNOME 优化工具

或者,你也可以使用命令行通过 apt 命令安装此软件:

sudo apt install gnome-tweaks

用优化工具定制 GNOME 桌面

GNOME 优化工具使你可以进行许多设置更改。其中的某些更改(例如墙纸更改、启动应用程序等)也可以在官方的“系统设置”工具中找到。我将重点介绍默认情况下“设置”中不可用的优化。

1、改变主题

你可以通过各种方式在 Ubuntu 中安装新主题。但是,如果要更改为新安装的主题,则必须安装GNOME 优化工具。

你可以在“ 外观 Appearance ”部分找到主题和图标设置。你可以浏览可用的主题和图标并设置你喜欢的主题和图标。更改将立即生效。

通过 GNOME 优化更改主题

2、禁用动画以提速你的桌面体验

应用程序窗口的打开、关闭、最大化等操作都有一些细微的动画。你可以禁用这些动画以稍微加快系统的速度,因为它会稍微使用一点资源。

禁用动画以获得稍快的桌面体验

3、控制桌面图标

至少在 Ubuntu 中,你会在桌面上看到“ 家目录 Home ”和“ 垃圾箱 Trash ”图标。如果你不喜欢,可以选择禁用它。你还可以选择要在桌面上显示的图标。

在 Ubuntu 中控制桌面图标

4、管理 GNOME 扩展

我想你可能知道 GNOME 扩展。这些是用于桌面的小型“插件”,可扩展 GNOME 桌面的功能。有大量的 GNOME 扩展,可用于在顶部面板中查看 CPU 消耗、获取剪贴板历史记录等等。

我已经写了一篇安装和使用 GNOME 扩展的详细文章。在这里,我假设你已经在使用它们,如果是这样,可以从 GNOME 优化工具中对其进行管理。

管理 GNOME 扩展

5、改变字体和缩放比例

你可以在 Ubuntu 中安装新字体,并使用这个优化工具在系统范围应用字体更改。如果你认为桌面上的图标和文本太小,也可以更改缩放比例。

更改字体和缩放比例

6、控制触摸板行为,例如在键入时禁用触摸板,使触摸板右键单击可以工作

GNOME 优化工具还允许你在键入时禁用触摸板。如果你在笔记本电脑上快速键入,这将很有用。手掌底部可能会触摸触摸板,并导致光标移至屏幕上不需要的位置。

在键入时自动禁用触摸板可解决此问题。

键入时禁用触摸板

你还会注意到当你按下触摸板的右下角以进行右键单击时,什么也没有发生。你的触摸板并没有问题。这是一项系统设置,可对没有实体右键按钮的任何触摸板(例如旧的 Thinkpad 笔记本电脑)禁用这种右键单击功能。两指点击可为你提供右键单击操作。

你也可以通过在“ 鼠标单击模拟 Mouse Click Simulation ”下设置为“ 区域 Area ”中而不是“ 手指 Fingers ”来找回这项功能。

修复右键单击问题

你可能必须重新启动 Ubuntu 来使这项更改生效。如果你是 Emacs 爱好者,还可以强制使用 Emacs 键盘绑定。

7、改变电源设置

电源这里只有一个设置。它可以让你在盖上盖子后将笔记本电脑置于挂起模式。

GNOME 优化工具中的电源设置

8、决定什么显示在顶部面板

桌面的顶部面板显示了一些重要的信息。在这里有日历、网络图标、系统设置和“ 活动 Activities ”选项。

你还可以显示电池百分比、添加日期及时间,并显示星期数。你还可以启用鼠标热角,以便将鼠标移至屏幕的左上角时可以获得所有正在运行的应用程序的活动视图。

GNOME 优化工具中的顶部面板设置

如果将鼠标焦点放在应用程序窗口上,你会注意到其菜单显示在顶部面板中。如果你不喜欢这样,可以将其关闭,然后应用程序菜单将显示应用程序本身。

9、配置应用窗口

你可以决定是否在应用程序窗口中显示最大化和最小化选项(右上角的按钮)。你也可以改变它们的位置到左边或右边。

应用程序窗口配置

这里还有其他一些配置选项。我不使用它们,但你可以自行探索。

10、配置工作区

GNOME 优化工具还允许你围绕工作区配置一些内容。

在 Ubuntu 中配置工作区

总结

对于任何 GNOME 用户,GNOME 优化(Tweaks)工具都是必备工具。它可以帮助你配置桌面的外观和功能。 我感到惊讶的是,该工具甚至没有出现在 Ubuntu 的主存储库中。我认为应该默认安装它,要不,你就得在 Ubuntu 中手动安装 GNOME 优化工具。

如果你在 GNOME 优化工具中发现了一些此处没有讨论的隐藏技巧,为什么不与大家分享呢?


via: https://itsfoss.com/gnome-tweak-tool/

作者:Abhishek Prakash 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

早在 2015 年,Brian Will 撰写了一篇有挑衅性的博客:面向对象编程:一个灾难故事。他随后发布了一个名为面向对象编程很糟糕的视频,该视频更加详细。

我建议你花些时间观看视频,下面是我的一段总结:

OOP 的柏拉图式理想是一堆相互解耦的对象,它们彼此之间发送无状态消息。没有人真的像这样制作软件,Brian 指出这甚至没有意义:对象需要知道向哪个对象发送消息,这意味着它们需要相互引用。该视频大部分讲述的是这样一个痛点:人们试图将对象耦合以实现控制流,同时假装它们是通过设计解耦的。

总的来说,他的想法与我自己的 OOP 经验产生了共鸣:对象没有问题,但是我一直不满意的是面向对象建模程序控制流,并且试图使代码“正确地”面向对象似乎总是在创建不必要的复杂性。

有一件事我认为他无法完全解释。他直截了当地说“封装没有作用”,但在脚注后面加上“在细粒度的代码级别”,并继续承认对象有时可以奏效,并且在库和文件级别封装是可以的。但是他没有确切解释为什么有时会奏效,有时却没有奏效,以及如何和在何处划清界限。有人可能会说这使他的 “OOP 不好”的说法有缺陷,但是我认为他的观点是正确的,并且可以在根本状态和偶发状态之间划清界限。

如果你以前从未听说过“ 根本 essential ”和“ 偶发 accidental ”这两个术语的使用,那么你应该阅读 Fred Brooks 的经典文章《没有银弹》。(顺便说一句,他写了许多很棒的有关构建软件系统的文章。)我以前曾写过关于根本和偶发的复杂性的文章,这里有一个简短的摘要:软件是复杂的。部分原因是因为我们希望软件能够解决混乱的现实世界问题,因此我们将其称为“根本复杂性”。“偶发复杂性”是所有其它的复杂性,因为我们正尝试使用硅和金属来解决与硅和金属无关的问题。例如,对于大多数程序而言,用于内存管理或在内存与磁盘之间传输数据或解析文本格式的代码都是“偶发的复杂性”。

假设你正在构建一个支持多个频道的聊天应用。消息可以随时到达任何频道。有些频道特别有趣,当有新消息传入时,用户希望得到通知。而其他频道静音:消息被存储,但用户不会受到打扰。你需要跟踪每个频道的用户首选设置。

一种实现方法是在频道和频道设置之间使用 映射 map (也称为哈希表、字典或关联数组)。注意,映射是 Brian Will 所说的可以用作对象的抽象数据类型(ADT)。

如果我们有一个调试器并查看内存中的映射对象,我们将看到什么?我们当然会找到频道 ID 和频道设置数据(或至少指向它们的指针)。但是我们还会找到其它数据。如果该映射是使用红黑树实现的,我们将看到带有红/黑标签和指向其他节点的指针的树节点对象。与频道相关的数据是根本状态,而树节点是偶发状态。不过,请注意以下几点:该映射有效地封装了它的偶发状态 —— 你可以用 AVL 树实现的另一个映射替换该映射,并且你的聊天程序仍然可以使用。另一方面,映射没有封装根本状态(仅使用 get()set() 方法访问数据并不是封装)。事实上,映射与根本状态是尽可能不可知的,你可以使用基本相同的映射数据结构来存储与频道或通知无关的其他映射。

这就是映射 ADT 如此成功的原因:它封装了偶发状态,并与根本状态解耦。如果你思考一下,Brian 用封装描述的问题就是尝试封装根本状态。其他描述的好处是封装偶发状态的好处。

要使整个软件系统都达到这一理想状况相当困难,但扩展开来,我认为它看起来像这样:

  • 没有全局的可变状态
  • 封装了偶发状态(在对象或模块或以其他任何形式)
  • 无状态偶发复杂性封装在单独函数中,与数据解耦
  • 使用诸如依赖注入之类的技巧使输入和输出变得明确
  • 组件可由易于识别的位置完全拥有和控制

其中有些违反了我很久以来的直觉。例如,如果你有一个数据库查询函数,如果数据库连接处理隐藏在该函数内部,并且唯一的参数是查询参数,那么接口会看起来会更简单。但是,当你使用这样的函数构建软件系统时,协调数据库的使用实际上变得更加复杂。组件不仅以自己的方式做事,而且还试图将自己所做的事情隐藏为“实现细节”。数据库查询需要数据库连接这一事实从来都不是实现细节。如果无法隐藏某些内容,那么显露它是更合理的。

我对将面向对象编程和函数式编程放在对立的两极非常警惕,但我认为从函数式编程进入面向对象编程的另一极端是很有趣的:OOP 试图封装事物,包括无法封装的根本复杂性,而纯函数式编程往往会使事情变得明确,包括一些偶发复杂性。在大多数时候,这没什么问题,但有时候(比如在纯函数式语言中构建自我指称的数据结构)设计更多的是为了函数编程,而不是为了简便(这就是为什么 Haskell 包含了一些“ 逃生出口 escape hatches )。我之前写过一篇所谓“ 弱纯性 weak purity ”的中间立场

Brian 发现封装对更大规模有效,原因有几个。一个是,由于大小的原因,较大的组件更可能包含偶发状态。另一个是“偶发”与你要解决的问题有关。从聊天程序用户的角度来看,“偶发的复杂性”是与消息、频道和用户等无关的任何事物。但是,当你将问题分解为子问题时,更多的事情就变得“根本”。例如,在解决“构建聊天应用”问题时,可以说频道名称和频道 ID 之间的映射是偶发的复杂性,而在解决“实现 getChannelIdByName() 函数”子问题时,这是根本复杂性。因此,封装对于子组件的作用比对父组件的作用要小。

顺便说一句,在视频的结尾,Brian Will 想知道是否有任何语言支持无法访问它们所作用的范围的匿名函数。D 语言可以。 D 中的匿名 Lambda 通常是闭包,但是如果你想要的话,也可以声明匿名无状态函数:

import std.stdio;

void main()
{
    int x = 41;

    // Value from immediately executed lambda
    auto v1 = () {
        return x + 1;
    }();
    writeln(v1);

    // Same thing
    auto v2 = delegate() {
        return x + 1;
    }();
    writeln(v2);

    // Plain functions aren't closures
    auto v3 = function() {
        // Can't access x
        // Can't access any mutable global state either if also marked pure
        return 42;
    }();
    writeln(v3);
}

via: https://theartofmachinery.com/2019/10/13/oop_and_essential_state.html

作者:Simon Arneaud 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

了解如何使用 Ansible 在容器中执行命令。

容器和 Ansible 可以很好地融合在一起:从管理和编排到供应和构建。在本文中,我们将重点介绍构建部分。

如果你熟悉 Ansible,就会知道你可以编写一系列任务,ansible-playbook 命令将为你执行这些任务。你知道吗,如果你编写 Dockerfile 并运行 podman build,你还可以在容器环境中执行此类命令,并获得相同​​的结果。

这是一个例子:

- name: Serve our file using httpd
  hosts: all
  tasks:
  - name: Install httpd
    package:
      name: httpd
      state: installed
  - name: Copy our file to httpd’s webroot
    copy:
      src: our-file.txt
      dest: /var/www/html/

你可以在 Web 服务器本地或容器中执行这个剧本,并且只要你记得先创建 our-file.txt,它就可以工作。

但是这里缺少了一些东西。你需要启动(并配置)httpd 以便提供文件。这是容器构建和基础架构供应之间的区别:构建镜像时,你只需准备内容;而运行容器是另一项任务。另一方面,你可以将元数据附加到容器镜像,它会默认运行命令。

这有个工具可以帮助。试试看 ansible-bender 怎么样?

$ ansible-bender build the-playbook.yaml fedora:30 our-httpd

该脚本使用 ansible-bender 对 Fedora 30 容器镜像执行该剧本,并将生成的容器镜像命名为 our-httpd

但是,当你运行该容器时,它不会启动 httpd,因为它不知道如何操作。你可以通过向该剧本添加一些元数据来解决此问题:

- name: Serve our file using httpd
  hosts: all
  vars:
    ansible_bender:
      base_image: fedora:30
      target_image:
        name: our-httpd
        cmd: httpd -DFOREGROUND
  tasks:
  - name: Install httpd
    package:
      name: httpd
      state: installed
  - name: Listen on all network interfaces.
    lineinfile:    
      path: /etc/httpd/conf/httpd.conf  
      regexp: '^Listen '
      line: Listen 0.0.0.0:80  
  - name: Copy our file to httpd’s webroot
    copy:
      src: our-file.txt
      dest: /var/www/html

现在你可以构建镜像(从这里开始,请以 root 用户身份运行所有命令。目前,Buildah 和 Podman 不会为无 root 容器创建专用网络):

# ansible-bender build the-playbook.yaml
PLAY [Serve our file using httpd] ****************************************************
                                                                                                                                                                             
TASK [Gathering Facts] ***************************************************************    
ok: [our-httpd-20191004-131941266141-cont]

TASK [Install httpd] *****************************************************************
loaded from cache: 'f053578ed2d47581307e9ba3f64f4b4da945579a082c6f99bd797635e62befd0'
skipping: [our-httpd-20191004-131941266141-cont]

TASK [Listen on all network interfaces.] *********************************************
changed: [our-httpd-20191004-131941266141-cont]

TASK [Copy our file to httpd’s webroot] **********************************************
changed: [our-httpd-20191004-131941266141-cont]

PLAY RECAP ***************************************************************************
our-httpd-20191004-131941266141-cont : ok=3    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

Getting image source signatures
Copying blob sha256:4650c04b851c62897e9c02c6041a0e3127f8253fafa3a09642552a8e77c044c8
Copying blob sha256:87b740bba596291af8e9d6d91e30a01d5eba9dd815b55895b8705a2acc3a825e
Copying blob sha256:82c21252bd87532e93e77498e3767ac2617aa9e578e32e4de09e87156b9189a0
Copying config sha256:44c6dc6dda1afe28892400c825de1c987c4641fd44fa5919a44cf0a94f58949f
Writing manifest to image destination
Storing signatures
44c6dc6dda1afe28892400c825de1c987c4641fd44fa5919a44cf0a94f58949f
Image 'our-httpd' was built successfully \o/

镜像构建完毕,可以运行容器了:

# podman run our-httpd
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.88.2.106. Set the 'ServerName' directive globally to suppress this message

是否提供文件了?首先,找出你容器的 IP:

# podman inspect -f '{{ .NetworkSettings.IPAddress }}' 7418570ba5a0
10.88.2.106

你现在可以检查了:

$ curl http://10.88.2.106/our-file.txt
Ansible is ❤

你文件内容是什么?

这只是使用 Ansible 构建容器镜像的介绍。如果你想了解有关 ansible-bender 可以做什么的更多信息,请查看它的 GitHub 页面。构建快乐!


via: https://opensource.com/article/19/10/building-container-images-ansible

作者:Tomas Tomecek 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出