2018年9月

了解如何验证某人所声称的身份。

上一篇文章中,我们概述了密码学并讨论了密码学的核心概念: 保密性 confidentiality (让数据保密)、 完整性 integrity (防止数据被篡改)和 身份认证 authentication (确认数据源的 身份 identity )。由于要在存在各种身份混乱的现实世界中完成身份认证,人们逐渐建立起一个复杂的 技术生态体系 technological ecosystem ,用于证明某人就是其声称的那个人。在本文中,我们将大致介绍这些体系是如何工作的。

快速回顾公钥密码学及数字签名

互联网世界中的身份认证依赖于公钥密码学,其中密钥分为两部分:拥有者需要保密的私钥和可以对外公开的公钥。经过公钥加密过的数据,只能用对应的私钥解密。举个例子,对于希望与记者建立联系的举报人来说,这个特性非常有用。但就本文介绍的内容而言,私钥更重要的用途是与一个消息一起创建一个 数字签名 digital signature ,用于提供完整性和身份认证。

在实际应用中,我们签名的并不是真实消息,而是经过 密码学哈希函数 cryptographic hash function 处理过的消息 摘要 digest 。要发送一个包含源代码的压缩文件,发送者会对该压缩文件的 256 比特长度的 SHA-256 摘要进行签名,而不是文件本身进行签名,然后用明文发送该压缩包(和签名)。接收者会独立计算收到文件的 SHA-256 摘要,然后结合该摘要、收到的签名及发送者的公钥,使用签名验证算法进行验证。验证过程取决于加密算法,加密算法不同,验证过程也相应不同;而且,很微妙的是签名验证漏洞依然层出不穷。如果签名验证通过,说明文件在传输过程中没有被篡改而且来自于发送者,这是因为只有发送者拥有创建签名所需的私钥。

方案中缺失的环节

上述方案中缺失了一个重要的环节:我们从哪里获得发送者的公钥?发送者可以将公钥与消息一起发送,但除了发送者的自我宣称,我们无法核验其身份。假设你是一名银行柜员,一名顾客走过来向你说,“你好,我是 Jane Doe,我要取一笔钱”。当你要求其证明身份时,她指着衬衫上贴着的姓名标签说道,“看,Jane Doe!”。如果我是这个柜员,我会礼貌的拒绝她的请求。

如果你认识发送者,你们可以私下见面并彼此交换公钥。如果你并不认识发送者,你们可以私下见面,检查对方的证件,确认真实性后接受对方的公钥。为提高流程效率,你可以举办聚会并邀请一堆人,检查他们的证件,然后接受他们的公钥。此外,如果你认识并信任 Jane Doe(尽管她在银行的表现比较反常),Jane 可以参加聚会,收集大家的公钥然后交给你。事实上,Jane 可以使用她自己的私钥对这些公钥(及对应的身份信息)进行签名,进而你可以从一个线上密钥库)获取公钥(及对应的身份信息)并信任已被 Jane 签名的那部分。如果一个人的公钥被很多你信任的人(即使你并不认识他们)签名,你也可能选择信任这个人。按照这种方式,你可以建立一个 信任网络 Web of Trust

但事情也变得更加复杂:我们需要建立一种标准的编码机制,可以将公钥和其对应的身份信息编码成一个 数字捆绑 digital bundle ,以便我们进一步进行签名。更准确的说,这类数字捆绑被称为 证书 cerificate 。我们还需要可以创建、使用和管理这些证书的工具链。满足诸如此类的各种需求的方案构成了 公钥基础设施 public key infrastructure (PKI)。

比信任网络更进一步

你可以用人际关系网类比信任网络。如果人们之间广泛互信,可以很容易找到(两个人之间的)一条 短信任链 short path of trust :就像一个社交圈。基于 GPG 加密的邮件依赖于信任网络,(理论上)只适用于与少量朋友、家庭或同事进行联系的情形。

(LCTT 译注:作者提到的“短信任链”应该是暗示“六度空间理论”,即任意两个陌生人之间所间隔的人一般不会超过 6 个。对 GPG 的唱衰,一方面是因为密钥管理的复杂性没有改善,另一方面 Yahoo 和 Google 都提出了更便利的端到端加密方案。)

在实际应用中,信任网络有一些“ 硬伤 significant problems ”,主要是在可扩展性方面。当网络规模逐渐增大或者人们之间的连接较少时,信任网络就会慢慢失效。如果信任链逐渐变长,信任链中某人有意或无意误签证书的几率也会逐渐增大。如果信任链不存在,你不得不自己创建一条信任链,与其它组织建立联系,验证它们的密钥以符合你的要求。考虑下面的场景,你和你的朋友要访问一个从未使用过的在线商店。你首先需要核验网站所用的公钥属于其对应的公司而不是伪造者,进而建立安全通信信道,最后完成下订单操作。核验公钥的方法包括去实体店、打电话等,都比较麻烦。这样会导致在线购物变得不那么便利(或者说不那么安全,毕竟很多人会图省事,不去核验密钥)。

如果世界上有那么几个格外值得信任的人,他们专门负责核验和签发网站证书,情况会怎样呢?你可以只信任他们,那么浏览互联网也会变得更加容易。整体来看,这就是当今互联网的工作方式。那些“格外值得信任的人”就是被称为 证书颁发机构 cerificate authoritie (CA)的公司。当网站希望获得公钥签名时,只需向 CA 提交 证书签名请求 certificate signing request (CSR)。

CSR 类似于包括公钥和身份信息(在本例中,即服务器的主机名)的 存根 stub 证书,但 CA 并不会直接对 CSR 本身进行签名。CA 在签名之前会进行一些验证。对于一些证书类型(LCTT 译注: 域名证实 Domain Validated (DV) 类型),CA 只验证申请者的确是 CSR 中列出主机名对应域名的控制者(例如通过邮件验证,让申请者完成指定的域名解析)。对于另一些证书类型 (LCTT 译注:链接中提到 扩展证实 Extended Validated (EV)类型,其实还有 OV Organization Validated 类型),CA 还会检查相关法律文书,例如公司营业执照等。一旦验证完成,CA(一般在申请者付费后)会从 CSR 中取出数据(即公钥和身份信息),使用 CA 自己的私钥进行签名,创建一个(签名)证书并发送给申请者。申请者将该证书部署在网站服务器上,当用户使用 HTTPS (或其它基于 TLS 加密的协议)与服务器通信时,该证书被分发给用户。

当用户访问该网站时,浏览器获取该证书,接着检查证书中的主机名是否与当前正在连接的网站一致(下文会详细说明),核验 CA 签名有效性。如果其中一步验证不通过,浏览器会给出安全警告并切断与网站的连接。反之,如果验证通过,浏览器会使用证书中的公钥来核验该服务器发送的签名信息,确认该服务器持有该证书的私钥。有几种算法用于协商后续通信用到的 共享密钥 shared secret key ,其中一种也用到了服务器发送的签名信息。 密钥交换 key exchange 算法不在本文的讨论范围,可以参考这个视频,其中仔细说明了一种密钥交换算法。

建立信任

你可能会问,“如果 CA 使用其私钥对证书进行签名,也就意味着我们需要使用 CA 的公钥验证证书。那么 CA 的公钥从何而来,谁对其进行签名呢?” 答案是 CA 对自己签名!可以使用证书公钥对应的私钥,对证书本身进行签名!这类签名证书被称为是 自签名的 self-signed ;在 PKI 体系下,这意味着对你说“相信我”。(为了表达方便,人们通常说用证书进行了签名,虽然真正用于签名的私钥并不在证书中。)

通过遵守浏览器操作系统供应商建立的规则,CA 表明自己足够可靠并寻求加入到浏览器或操作系统预装的一组自签名证书中。这些证书被称为“ 信任锚 trust anchor ”或 CA 根证书 root CA certificate ,被存储在根证书区,我们 约定 implicitly 信任该区域内的证书。

CA 也可以签发一种特殊的证书,该证书自身可以作为 CA。在这种情况下,它们可以生成一个证书链。要核验证书链,需要从“信任锚”(也就是 CA 根证书)开始,使用当前证书的公钥核验下一层证书的签名(或其它一些信息)。按照这个方式依次核验下一层证书,直到证书链底部。如果整个核验过程没有问题,信任链也建立完成。当向 CA 付费为网站签发证书时,实际购买的是将证书放置在证书链下的权利。CA 将卖出的证书标记为“不可签发子证书”,这样它们可以在适当的长度终止信任链(防止其继续向下扩展)。

为何要使用长度超过 2 的信任链呢?毕竟网站的证书可以直接被 CA 根证书签名。在实际应用中,很多因素促使 CA 创建 中间 CA 证书 intermediate CA certificate ,最主要是为了方便。由于价值连城,CA 根证书对应的私钥通常被存放在特定的设备中,一种需要多人解锁的 硬件安全模块 hardware security module (HSM),该模块完全离线并被保管在配备监控和报警设备的地下室中。

CA/浏览器论坛 CAB Forum, CA/Browser Forum 负责管理 CA,要求任何与 CA 根证书(LCTT 译注:就像前文提到的那样,这里是指对应的私钥)相关的操作必须由人工完成。设想一下,如果每个证书请求都需要员工将请求内容拷贝到保密介质中、进入地下室、与同事一起解锁 HSM、(使用 CA 根证书对应的私钥)签名证书,最后将签名证书从保密介质中拷贝出来;那么每天为大量网站签发证书是相当繁重乏味的工作。因此,CA 创建内部使用的中间 CA,用于证书签发自动化。

如果想查看证书链,可以在 Firefox 中点击地址栏的锁型图标,接着打开页面信息,然后点击“安全”面板中的“查看证书”按钮。在本文写作时,opensource.com 使用的证书链如下:

DigiCert High Assurance EV Root CA
    DigiCert SHA2 High Assurance Server CA
        opensource.com

中间人

我之前提到,浏览器需要核验证书中的主机名与已经建立连接的主机名一致。为什么需要这一步呢?要回答这个问题,需要了解所谓的 中间人攻击 man-in-the-middle, MIMT 。有一类网络攻击可以让攻击者将自己置身于客户端和服务端中间,冒充客户端与服务端连接,同时冒充服务端与客户端连接。如果网络流量是通过 HTTPS 传输的,加密的流量无法被窃听。此时,攻击者会创建一个代理,接收来自受害者的 HTTPS 连接,解密信息后构建一个新的 HTTPS 连接到原始目的地(即服务端)。为了建立假冒的 HTTPS 连接,代理必须返回一个攻击者具有对应私钥的证书。攻击者可以生成自签名证书,但受害者的浏览器并不会信任该证书,因为它并不是根证书库中的 CA 根证书签发的。换一个方法,攻击者使用一个受信任 CA 签发但主机名对应其自有域名的证书,结果会怎样呢?

再回到银行的那个例子,我们是银行柜员,一位男性顾客进入银行要求从 Jane Doe 的账户上取钱。当被要求提供身份证明时,他给出了 Joe Smith 的有效驾驶执照。如果这个交易可以完成,我们无疑会被银行开除。类似的,如果检测到证书中的主机名与连接对应的主机名不一致,浏览器会给出类似“连接不安全”的警告和查看更多内容的选项。在 Firefox 中,这类错误被标记为 SSL_ERROR_BAD_CERT_DOMAIN

我希望你阅读完本文起码记住这一点:如果看到这类警告,不要无视它们!它们出现意味着,或者该网站配置存在严重问题(不推荐访问),或者你已经是中间人攻击的潜在受害者。

总结

虽然本文只触及了 PKI 世界的一些皮毛,我希望我已经为你展示了便于后续探索的大致蓝图。密码学和 PKI 是美与复杂性的结合体。越深入研究,越能发现更多的美和复杂性,就像分形那样。


via: https://opensource.com/article/18/7/private-keys

作者:Alex Wood 选题:lujun9972 译者:pinewall 校对:wxy

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

了解 Hugo 如何使构建网站变得有趣。

你是不是强烈地想搭建博客来将自己对软件框架等的探索学习成果分享呢?你是不是面对缺乏指导文档而一团糟的项目就有一种想去改变它的冲动呢?或者换个角度,你是不是十分期待能创建一个属于自己的个人博客网站呢?

很多人在想搭建博客之前都有一些严重的迟疑顾虑:感觉自己缺乏内容管理系统(CMS)的相关知识,更缺乏时间去学习这些知识。现在,如果我说不用花费大把的时间去学习 CMS 系统、学习如何创建一个静态网站、更不用操心如何去强化网站以防止它受到黑客攻击的问题,你就可以在 30 分钟之内创建一个博客?你信不信?利用 Hugo 工具,就可以实现这一切。

Hugo 是一个基于 Go 语言开发的静态站点生成工具。也许你会问,为什么选择它?

  • 无需数据库、无需需要各种权限的插件、无需跑在服务器上的底层平台,更没有额外的安全问题。
  • 都是静态站点,因此拥有轻量级、快速响应的服务性能。此外,所有的网页都是在部署的时候生成,所以服务器负载很小。
  • 极易操作的版本控制。一些 CMS 平台使用它们自己的版本控制软件(VCS)或者在网页上集成 Git 工具。而 Hugo,所有的源文件都可以用你所选的 VCS 软件来管理。

0-5 分钟:下载 Hugo,生成一个网站

直白的说,Hugo 使得写一个网站又一次变得有趣起来。让我们来个 30 分钟计时,搭建一个网站。

为了简化 Hugo 安装流程,这里直接使用 Hugo 可执行安装文件。

  1. 下载和你操作系统匹配的 Hugo 版本
  2. 压缩包解压到指定路径,例如 windows 系统的 C:\hugo_dir 或者 Linux 系统的 ~/hugo_dir 目录;下文中的变量 ${HUGO_HOME} 所指的路径就是这个安装目录;
  3. 打开命令行终端,进入安装目录:cd ${HUGO_HOME}
  4. 确认 Hugo 已经启动:
* Unix 系统:`${HUGO_HOME}/[hugo version]`;
* Windows 系统:`${HUGO_HOME}\[hugo.exe version]`,例如:cmd 命令行中输入:`c:\hugo_dir\hugo version`。为了书写上的简化,下文中的 `hugo` 就是指 hugo 可执行文件所在的路径(包括可执行文件),例如命令 `hugo version` 就是指命令 `c:\hugo_dir\hugo version` 。(LCTT 译注:可以把 hugo 可执行文件所在的路径添加到系统环境变量下,这样就可以直接在终端中输入 `hugo version`)

如果命令 hugo version 报错,你可能下载了错误的版本。当然,有很多种方法安装 Hugo,更多详细信息请查阅 官方文档。最稳妥的方法就是把 Hugo 可执行文件放在某个路径下,然后执行的时候带上路径名

  1. 创建一个新的站点来作为你的博客,输入命令:hugo new site awesome-blog
  2. 进入新创建的路径下: cd awesome-blog

恭喜你!你已经创建了自己的新博客。

5-10 分钟:为博客设置主题

Hugo 中你可以自己构建博客的主题或者使用网上已经有的一些主题。这里选择 Kiera 主题,因为它简洁漂亮。按以下步骤来安装该主题:

  1. 进入主题所在目录:cd themes
  2. 克隆主题:git clone https://github.com/avianto/hugo-kiera kiera。如果你没有安装 Git 工具:

    • Github 上下载 hugo 的 .zip 格式的文件;
    • 解压该 .zip 文件到你的博客主题 theme 路径;
    • 重命名 hugo-kiera-masterkiera
  3. 返回博客主路径:cd awesome-blog
  4. 激活主题;通常来说,主题(包括 Kiera)都自带文件夹 exampleSite,里面存放了内容配置的示例文件。激活 Kiera 主题需要拷贝它提供的 config.toml 到你的博客下:
* Unix 系统:`cp themes/kiera/exampleSite/config.toml .`;
* Windows 系统:`copy themes\kiera\exampleSite\config.toml .`;
* 选择 `Yes` 来覆盖原有的 `config.toml`;
  1. ( 可选操作 )你可以选择可视化的方式启动服务器来验证主题是否生效:hugo server -D 然后在浏览器中输入 http://localhost:1313。可用通过在终端中输入 Crtl+C 来停止服务器运行。现在你的博客还是空的,但这也给你留了写作的空间。它看起来如下所示:

你已经成功的给博客设置了主题!你可以在官方 Hugo 主题 网站上找到上百种漂亮的主题供你使用。

10-20 分钟:给博客添加内容

对于碗来说,它是空的时候用处最大,可以用来盛放东西;但对于博客来说不是这样,空博客几乎毫无用处。在这一步,你将会给博客添加内容。Hugo 和 Kiera 主题都为这个工作提供了方便性。按以下步骤来进行你的第一次提交:

  1. archetypes 将会是你的内容模板。
  2. 添加主题中的 archtypes 至你的博客:
* Unix 系统: `cp themes/kiera/archetypes/* archetypes/`
* Windows 系统:`copy themes\kiera\archetypes\* archetypes\`
* 选择 `Yes` 来覆盖原来的 `default.md` 内容架构类型
  1. 创建博客 posts 目录:
* Unix 系统: `mkdir content/posts`
* Windows 系统: `mkdir content\posts`
  1. 利用 Hugo 生成你的 post:
* Unix 系统:`hugo nes posts/first-post.md`;
* Windows 系统:`hugo new posts\first-post.md`;
  1. 在文本编辑器中打开这个新建的 post 文件:
* Unix 系统:`gedit content/posts/first-post.md`;
* Windows 系统:`notepadd content\posts\first-post.md`;

此刻,你可以疯狂起来了。注意到你的提交文件中包括两个部分。第一部分是以 +++ 符号分隔开的。它包括了提交文档的主要数据,例如名称、时间等。在 Hugo 中,这叫做前缀。在前缀之后,才是正文。下面编辑第一个提交文件内容:

+++
title = "First Post"
date = 2018-03-03T13:23:10+01:00
draft = false
tags = ["Getting started"]
categories = []
+++

Hello Hugo world! No more excuses for having no blog or documentation now!

现在你要做的就是启动你的服务器:hugo server -D;然后打开浏览器,输入 http://localhost:1313/

20-30 分钟:调整网站

前面的工作很完美,但还有一些问题需要解决。例如,简单地命名你的站点:

  1. 终端中按下 Ctrl+C 以停止服务器。
  2. 打开 config.toml,编辑博客的名称,版权,你的姓名,社交网站等等。

当你再次启动服务器后,你会发现博客私人订制味道更浓了。不过,还少一个重要的基础内容:主菜单。快速的解决这个问题。返回 config.toml 文件,在末尾插入如下一段:

[[menu.main]]
    name = "Home" #Name in the navigation bar
    weight = 10 #The larger the weight, the more on the right this item will be
    url = "/" #URL address
[[menu.main]]
    name = "Posts"
    weight = 20
    url = "/posts/"

上面这段代码添加了 HomePosts 到主菜单中。你还需要一个 About 页面。这次是创建一个 .md 文件,而不是编辑 config.toml 文件:

  1. 创建 about.md 文件:hugo new about.md 。注意它是 about.md,不是 posts/about.md。该页面不是博客提交内容,所以你不想它显示到博客内容提交当中吧。
  2. 用文本编辑器打开该文件,输入如下一段:
+++
title = "About"
date = 2018-03-03T13:50:49+01:00
menu = "main" #Display this page on the nav menu
weight = "30" #Right-most nav item
meta = "false" #Do not display tags or categories
+++

> Waves are the practice of the water. Shunryu Suzuki

当你启动你的服务器并输入:http://localhost:1313/,你将会看到你的博客。(访问我 Gihub 主页上的 例子 )如果你想让文章的菜单栏和 Github 相似,给 themes/kiera/static/css/styles.css 打上这个 补丁


via: https://opensource.com/article/18/3/start-blog-30-minutes-hugo

作者:Marek Czernek 
译者:jrg 
校对:wxy

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

zsh 提供了数之不尽的功能和特性,这里有五个可以让你在命令行效率暴增的方法。

Z shell(zsh)是 Linux 和类 Unix 系统中的一个命令解析器)。 它跟 sh (Bourne shell) 家族的其它解析器(如 bash 和 ksh)有着相似的特点,但它还提供了大量的高级特性以及强大的命令行编辑功能,如增强版 Tab 补全。

在这里不可能涉及到 zsh 的所有功能,描述它的特性需要好几百页。在本文中,我会列出 5 个技巧,让你通过在命令行使用 zsh 来提高你的生产力。

1、主题和插件

多年来,开源社区已经为 zsh 开发了数不清的主题和插件。主题是一个预定义提示符的配置,而插件则是一组常用的别名命令和函数,可以让你更方便的使用一种特定的命令或者编程语言。

如果你现在想开始用 zsh 的主题和插件,那么使用一种 zsh 的配置框架是你最快的入门方式。在众多的配置框架中,最受欢迎的则是 Oh My Zsh。在默认配置中,它就已经为 zsh 启用了一些合理的配置,同时它也自带上百个主题和插件。

主题会在你的命令行提示符之前添加一些有用的信息,比如你 Git 仓库的状态,或者是当前使用的 Python 虚拟环境,所以它会让你的工作更高效。只需要看到这些信息,你就不用再敲命令去重新获取它们,而且这些提示也相当酷炫。下图就是我选用的主题 Powerlevel9k

 title=

zsh 主题 Powerlevel9k

除了主题,Oh my Zsh 还自带了大量常用的 zsh 插件。比如,通过启用 Git 插件,你可以用一组简便的命令别名操作 Git, 比如

$ alias | grep -i git | sort -R | head -10
g=git
ga='git add'
gapa='git add --patch'
gap='git apply'
gdt='git diff-tree --no-commit-id --name-only -r'
gau='git add --update'
gstp='git stash pop'
gbda='git branch --no-color --merged | command grep -vE "^(\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d'
gcs='git commit -S'
glg='git log --stat'

zsh 还有许多插件可以用于许多编程语言、打包系统和一些平时在命令行中常用的工具。以下是我 Ferdora 工作站中用到的插件表:

git golang fedora docker oc sudo vi-mode virtualenvwrapper

2、智能的命令别名

命令别名在 zsh 中十分有用。为你常用的命令定义别名可以节省你的打字时间。Oh My Zsh 默认配置了一些常用的命令别名,包括目录导航命令别名,为常用的命令添加额外的选项,比如:

ls='ls --color=tty'
grep='grep  --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}'

除了命令别名以外, zsh 还自带两种额外常用的别名类型:后缀别名和全局别名。

后缀别名可以让你基于文件后缀,在命令行中利用指定程序打开这个文件。比如,要用 vim 打开 YAML 文件,可以定义以下命令行别名:

alias -s {yml,yaml}=vim

现在,如果你在命令行中输入任何后缀名为 ymlyaml 文件, zsh 都会用 vim 打开这个文件。

$ playbook.yml
# Opens file playbook.yml using vim

全局别名可以让你创建一个可在命令行的任何地方展开的别名,而不仅仅是在命令开始的时候。这个在你想替换常用文件名或者管道命令的时候就显得非常有用了。比如:

alias -g G='| grep -i'

要使用这个别名,只要你在想用管道命令的时候输入 G 就好了:

$ ls -l G do
drwxr-xr-x.  5 rgerardi rgerardi 4096 Aug  7 14:08 Documents
drwxr-xr-x.  6 rgerardi rgerardi 4096 Aug 24 14:51 Downloads

接着,我们就来看看 zsh 是如何导航文件系统的。

3、便捷的目录导航

当你使用命令行的时候,在不同的目录之间切换访问是最常见的工作了。 zsh 提供了一些十分有用的目录导航功能来简化这个操作。这些功能已经集成到 Oh My Zsh 中了, 而你可以用以下命令来启用它

setopt  autocd autopushd \ pushdignoredups

使用了上面的配置后,你就不用输入 cd 来切换目录了,只需要输入目录名称,zsh 就会自动切换到这个目录中:

$ pwd
/home/rgerardi
$ /tmp
$ pwd
/tmp

如果想要回退,只要输入 -:

zsh 会记录你访问过的目录,这样下次你就可以快速切换到这些目录中。如果想要看这个目录列表,只要输入 dirs -v

$ dirs -v
0       ~
1       /var/log
2       /var/opt
3       /usr/bin
4       /usr/local
5       /usr/lib
6       /tmp
7       ~/Projects/Opensource.com/zsh-5tips
8       ~/Projects
9       ~/Projects/ansible
10      ~/Documents

如果想要切换到这个列表中的其中一个目录,只需输入 ~## 代表目录在列表中的序号)就可以了。比如

$ pwd
/home/rgerardi
$ ~4
$ pwd
/usr/local

你甚至可以用别名组合这些命令,这样切换起来就变得更简单:

d='dirs -v | head -10'
1='cd -'
2='cd -2'
3='cd -3'
4='cd -4'
5='cd -5'
6='cd -6'
7='cd -7'
8='cd -8'
9='cd -9'

现在你可以通过输入 d 来查看这个目录列表的前10个,然后用目录的序号来进行切换:

$ d
0       /usr/local
1       ~
2       /var/log
3       /var/opt
4       /usr/bin
5       /usr/lib
6       /tmp
7       ~/Projects/Opensource.com/zsh-5tips
8       ~/Projects
9       ~/Projects/ansible
$ pwd
/usr/local
$ 6
/tmp
$ pwd
/tmp

最后,你可以在 zsh 中利用 Tab 来自动补全目录名称。你可以先输入目录的首字母,然后按 TAB 键来补全它们:

$ pwd
/home/rgerardi
$ p/o/z (TAB)
$ Projects/Opensource.com/zsh-5tips/

以上仅仅是 zsh 强大的 Tab 补全系统中的一个功能。接来下我们来探索它更多的功能。

4、先进的 Tab 补全

zsh 强大的补全系统是它的卖点之一。为了简便起见,我称它为 Tab 补全,然而在系统底层,它起到了几个作用。这里通常包括展开以及命令补全,我会在这里用讨论它们。如果想了解更多,详见 用户手册

在 Oh My Zsh 中,命令补全是默认启用的。要启用它,你只要在 .zshrc 文件中添加以下命令:

autoload -U compinit
compinit

zsh 的补全系统非常智能。它会尝试唯一提示可用在当前上下文环境中的项目 —— 比如,你输入了 cdTAB,zsh 只会为你提示目录名,因为它知道其它的项目放在 cd 后面没用。

反之,如果你使用与用户相关的命令便会提示用户名,而 ssh 或者 ping 这类则会提示主机名。

zsh 拥有一个巨大而又完整的库,因此它能识别许多不同的命令。比如,如果你使用 tar 命令, 你可以按 TAB 键,它会为你展示一个可以用于解压的文件列表:

$ tar -xzvf test1.tar.gz test1/file1 (TAB)
file1 file2

如果使用 git 的话,这里有个更高级的示例。在这个示例中,当你按 TAB 键, zsh 会自动补全当前库可以操作的文件:

$ ls
original  plan.txt  zsh-5tips.md  zsh_theme_small.png
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   zsh-5tips.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git add (TAB)
$ git add zsh-5tips.md

zsh 还能识别命令行选项,同时它只会提示与选中子命令相关的命令列表:

$ git commit - (TAB)
--all                  -a       -- stage all modified and deleted paths
--allow-empty                   -- allow recording an empty commit
--allow-empty-message           -- allow recording a commit with an empty message
--amend                         -- amend the tip of the current branch
--author                        -- override the author name used in the commit
--branch                        -- show branch information
--cleanup                       -- specify how the commit message should be cleaned up
--date                          -- override the author date used in the commit
--dry-run                       -- only show the list of paths that are to be committed or not, and any untracked
--edit                 -e       -- edit the commit message before committing
--file                 -F       -- read commit message from given file
--gpg-sign             -S       -- GPG-sign the commit
--include              -i       -- update the given files and commit the whole index
--interactive                   -- interactively update paths in the index file
--message              -m       -- use the given message as the commit message
... TRUNCATED ...

在按 TAB 键之后,你可以使用方向键来选择你想用的命令。现在你就不用记住所有的 git 命令项了。

zsh 还有很多有用的功能。当你用它的时候,你就知道哪些对你才是最有用的。

5、命令行编辑与历史记录

zsh 的命令行编辑功能也十分有用。默认条件下,它是模拟 emacs 编辑器的。如果你是跟我一样更喜欢用 vi/vim,你可以用以下命令启用 vi 的键绑定。

$ bindkey -v

如果你使用 Oh My Zsh,vi-mode 插件可以启用额外的绑定,同时会在你的命令提示符上增加 vi 的模式提示 —— 这个非常有用。

当启用 vi 的绑定后,你可以在命令行中使用 vi 命令进行编辑。比如,输入 ESC+/ 来查找命令行记录。在查找的时候,输入 n 来找下一个匹配行,输入 N 来找上一个。输入 ESC 后,常用的 vi 命令都可以使用,如输入 0 跳转到行首,输入 $ 跳转到行尾,输入 i 来插入文本,输入 a 来追加文本等等,即使是跟随的命令也同样有效,比如输入 cw 来修改单词。

除了命令行编辑,如果你想修改或重新执行之前使用过的命令,zsh 还提供几个常用的命令行历史功能。比如,你打错了一个命令,输入 fc,你可以在你偏好的编辑器中修复最后一条命令。使用哪个编辑是参照 $EDITOR 变量的,而默认是使用 vi。

另外一个有用的命令是 r, 它会重新执行上一条命令;而 r <WORD> 则会执行上一条包含 WORD 的命令。

最后,输入两个感叹号(!!),可以在命令行中回溯最后一条命令。这个十分有用,比如,当你忘记使用 sudo 去执行需要权限的命令时:

$ less /var/log/dnf.log
/var/log/dnf.log: Permission denied
$ sudo !!
$ sudo less /var/log/dnf.log

这个功能让查找并且重新执行之前命令的操作更加方便。

下一步呢?

这里仅仅介绍了几个可以让你提高生产率的 zsh 特性;其实还有更多功能有待你的发掘;想知道更多的信息,你可以访问以下的资源:

你有使用 zsh 提高生产力的技巧可以分享吗?我很乐意在下方评论中看到它们。


via: https://opensource.com/article/18/9/tips-productivity-zsh

作者:Ricardo Gerardi 选题:lujun9972 译者:tnuoccalanosrep 校对:wxy

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

创建照片幻灯片只需点击几下。以下是如何在 Ubuntu 18.04 和其他 Linux 发行版中制作照片幻灯片。

How to create slideshow of photos in Ubuntu Linux

想象一下,你的朋友和亲戚正在拜访你,并请你展示最近的活动/旅行照片。

你将照片保存在计算机上,并整齐地放在单独的文件夹中。你邀请计算机附近的所有人。你进入该文件夹​​,单击其中一张图片,然后按箭头键逐个显示照片。

但那太累了!如果这些图片每隔几秒自动更改一次,那将会好很多。

这称之为幻灯片,我将向你展示如何在 Ubuntu 中创建照片幻灯片。这能让你在文件夹中循环播放图片并以全屏模式显示它们。

在 Ubuntu 18.04 和其他 Linux 发行版中创建照片幻灯片

虽然有几种图像浏览器可以做到,但我将向你展示大多数发行版中应该提供的两种最常用的工具。

方法 1:使用 GNOME 默认图像浏览器浏览照片幻灯片

如果你在 Ubuntu 18.04 或任何其他发行版中使用 GNOME,那么你很幸运。Gnome 的默认图像浏览器,Eye of GNOME,能够在当前文件夹中显示图片的幻灯片。

只需单击其中一张图片,你将在程序的右上角菜单中看到设置选项。它看起来像堆叠在一起的三条横栏。

你会在这里看到几个选项。勾选幻灯片选项,它将全屏显示图像。

How to create slideshow of photos in Ubuntu Linux

默认情况下,图像以 5 秒的间隔变化。你可以进入 “Preferences -> Slideshow” 来更改幻灯片放映间隔。

change slideshow interval in Ubuntu

方法 2:使用 Shotwell Photo Manager 进行照片幻灯片放映

Shotwell 是一款流行的 Linux 照片管理程序。适用于所有主要的 Linux 发行版。

如果尚未安装,请在你的发行版软件中心中搜索 Shotwell 并安装。

Shotwell 的运行略有不同。如果你在 Shotwell Viewer 中直接打开照片,则不会看到首选项或者幻灯片的选项。

对于幻灯片放映和其他选项,你必须打开 Shotwell 并导入包含这些图片的文件夹。导入文件夹后,从左侧窗格中选择该文件夹,然后单击菜单中的 “View”。你应该在此处看到幻灯片选项。只需单击它即可创建所选文件夹中所有图像的幻灯片。

How to create slideshow of photos in Ubuntu Linux

你还可以更改幻灯片设置。当图像以全屏显示时,将显示此选项。只需将鼠标悬停在底部,你就会看到一个设置选项。

创建照片幻灯片很容易

如你所见,在 Linux 中创建照片幻灯片非常简单。我希望你觉得这个简单的提示有用。如果你有任何问题或建议,请在下面的评论栏告诉我们。


via: https://itsfoss.com/photo-slideshow-ubuntu/

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

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

开源就是丢一些代码到 GitHub 上。了解一下它是什么,以及不是什么?

最简单的来说,开源编程就是编写一些大家可以随意取用、修改的代码。但你肯定听过关于 Go 语言的那个老笑话,说 Go 语言“简单到看一眼就可以明白规则,但需要一辈子去学会运用它”。其实写开源代码也是这样的。往 GitHub、Bitbucket、SourceForge 等网站或者是你自己的博客或网站上丢几行代码不是难事,但想要卓有成效,还需要个人的努力付出和高瞻远瞩。

我们对开源编程的误解

首先我要说清楚一点:把你的代码放在 GitHub 的公开仓库中并不意味着把你的代码开源了。在几乎全世界,根本不用创作者做什么,只要作品形成,版权就随之而生了。在创作者进行授权之前,只有作者可以行使版权相关的权力。未经创作者授权的代码,不论有多少人在使用,都是一颗定时炸弹,只有愚蠢的人才会去用它。

有些创作者很善良,认为“很明显我的代码是免费提供给大家使用的。”,他也并不想起诉那些用了他的代码的人,但这并不意味着这些代码可以放心使用。不论在你眼中创作者们多么善良,他们都 有权力 起诉任何使用、修改代码,或未经明确授权就将代码嵌入的人。

很明显,你不应该在没有指定开源许可证的情况下将你的源代码发布到网上然后期望别人使用它并为其做出贡献。我建议你也尽量避免使用这种代码,甚至疑似未授权的也不要使用。如果你开发了一个函数和例程,它和之前一个疑似未授权代码很像,源代码作者就可以对你就侵权提起诉讼。

举个例子,Jill Schmill 写了 AwesomeLib 然后未明确授权就把它放到了 GitHub 上,就算 Jill Schmill 不起诉任何人,只要她把 AwesomeLib 的完整版权都卖给 EvilCorp,EvilCorp 就会起诉之前违规使用这段代码的人。这种行为就好像是埋下了计算机安全隐患,总有一天会为人所用。

没有许可证的代码的危险的,切记。

选择恰当的开源许可证

假设你正要写一个新程序,而且打算让人们以开源的方式使用它,你需要做的就是选择最贴合你需求的许可证。和宣传中说的一样,你可以从 GitHub 所支持的 choosealicense.com 开始。这个网站设计得像个简单的问卷,特别方便快捷,点几下就能找到合适的许可证。

警示:在选择许可证时不要过于自负,如果你选的是 Apache 许可证或者 GPLv3 这种广为使用的许可证,人们很容易理解他们和你都有什么权利,你也不需要请律师来排查其中的漏洞。你选择的许可证使用的人越少,带来的麻烦就越多。

最重要的一点是: 千万不要试图自己制造许可证! 自己制造许可证会给大家带来更多的困惑和困扰,不要这样做。如果在现有的许可证中确实找不到你需要的条款,你可以在现有的许可证中附加上你的要求,并且重点标注出来,提醒使用者们注意。

我知道有些人会站出来说:“我才懒得管什么许可证,我已经把代码发到 公开领域 public domain 了。”但问题是,公开领域的法律效力并不是受全世界认可的。在不同的国家,公开领域的效力和表现形式不同。在有些国家的政府管控下,你甚至不可以把自己的源代码发到公开领域。万幸,Unlicense 可以弥补这些漏洞,它语言简洁,使用几个词清楚地描述了“就把它放到公开领域”,但其效力为全世界认可。

怎样引入许可证

确定使用哪个许可证之后,你需要清晰而无疑义地指定它。如果你是在 GitHub、GitLab 或 BitBucket 这几个网站发布,你需要构建很多个文件夹,在根文件夹中,你应把许可证创建为一个以 LICENSE.txt 命名的明文文件。

创建 LICENSE.txt 这个文件之后还有其它事要做。你需要在每个重要文件的头部添加注释块来申明许可证。如果你使用的是一个现有的许可证,这一步对你来说十分简便。一个 # 项目名 (c)2018 作者名,GPLv3 许可证,详情见 https://www.gnu.org/licenses/gpl-3.0.en.html 这样的注释块比隐约指代的许可证的效力要强得多。

如果你是要发布在自己的网站上,步骤也差不多。先创建 LICENSE.txt 文件,放入许可证,再表明许可证出处。

开源代码的不同之处

开源代码和专有代码的一个主要区别是开源代码写出来就是为了给别人看的。我是个 40 多岁的系统管理员,已经写过许许多多的代码。最开始我写代码是为了工作,为了解决公司的问题,所以其中大部分代码都是专有代码。这种代码的目的很简单,只要能在特定场合通过特定方式发挥作用就行。

开源代码则大不相同。在写开源代码时,你知道它可能会被用于各种各样的环境中。也许你的用例的环境条件很局限,但你仍旧希望它能在各种环境下发挥理想的效果。不同的人使用这些代码时会出现各种用例,你会看到各类冲突,还有你没有考虑过的思路。虽然代码不一定要满足所有人,但至少应该得体地处理他们遇到的问题,就算解决不了,也可以转换回常见的逻辑,不会给使用者添麻烦。(例如“第 583 行出现零除错误”就不能作为错误地提供命令行参数的响应结果)

你的源代码也可能逼疯你,尤其是在你一遍又一遍地修改错误的函数或是子过程后,终于出现了你希望的结果,这时你不会叹口气就继续下一个任务,你会把过程清理干净,因为你不会愿意别人看出你一遍遍尝试的痕迹。比如你会把 $variable$lol 全都换成有意义的 $iterationcounter$modelname。这意味着你要认真专业地进行注释(尽管对于你所处的背景知识热度来说它并不难懂),因为你期望有更多的人可以使用你的代码。

这个过程难免有些痛苦沮丧,毕竟这不是你常做的事,会有些不习惯。但它会使你成为一位更好的程序员,也会让你的代码升华。即使你的项目只有你一位贡献者,清理代码也会节约你后期的很多工作,相信我一年后你再看你的 app 代码时,你会庆幸自己写下的是 $modelname,还有清晰的注释,而不是什么不知名的数列,甚至连 $lol 也不是。

你并不是为你一人而写

开源的真正核心并不是那些代码,而是社区。更大的社区的项目维持时间更长,也更容易为人们所接受。因此不仅要加入社区,还要多多为社区发展贡献思路,让自己的项目能够为社区所用。

蝙蝠侠为了完成目标暗中独自花了很大功夫,你用不着这样,你可以登录 Twitter、Reddit,或者给你项目的相关人士发邮件,发布你正在筹备新项目的消息,仔细聊聊项目的设计初衷和你的计划,让大家一起帮忙,向大家征集数据输入,类似的使用案例,把这些信息整合起来,用在你的代码里。你不用接受所有的建议和请求,但你要对它有个大概把握,这样在你之后完善时可以躲过一些陷阱。

发布了首次通告这个过程还不算完整。如果你希望大家能够接受你的作品并且使用它,你就要以此为初衷来设计。公众说不定可以帮到你,你不必对公开这件事如临大敌。所以不要闭门造车,既然你是为大家而写,那就开设一个真实、公开的项目,想象你在社区的帮助和监督下,认真地一步步完成它。

建立项目的方式

你可以在 GitHub、GitLab 或 BitBucket 上免费注册账号来管理你的项目。注册之后,创建知识库,建立 README 文件,分配一个许可证,一步步写入代码。这样可以帮你建立好习惯,让你之后和现实中的团队一起工作时,也能目的清晰地朝着目标稳妥地开展工作。这样你做得越久,就越有兴趣 —— 通常会有用户先对你的项目产生兴趣。

用户会开始提一些问题,这会让你开心也会让你不爽,你应该亲切礼貌地对待他们,就算他们很多人对项目有很多误解甚至根本不知道你的项目做的是什么,你也应该礼貌专业地对待。一方面,你可以引导他们,让他们了解你在干什么。另一方面,他们也会慢慢地将你带入更大的社区。

如果你的项目很受用户青睐,总会有高级开发者出现,并表示出兴趣。这也许是好事,也可能激怒你。最开始你可能只会做简单的问题修复,但总有一天你会收到拉取请求,有可能是硬编码或特殊用例(可能会让项目变得难以维护),它可能改变你项目的作用域,甚至改变你项目的初衷。你需要学会分辨哪个有贡献,根据这个决定合并哪个,婉拒哪个。

我们为什么要开源?

开源听起来任务繁重,它也确实是这样。但它对你也有很多好处。它可以在无形之中磨练你,让你写出纯净持久的代码,也教会你与人沟通,团队协作。对于一个志向远大的专业开发者来说,它是最好的简历素材。你的未来雇主很有可能点开你的仓库,了解你的能力范围;而社区项目的开发者也有可能给你带来工作。

最后,为开源工作,意味着个人的提升,因为你在做的事不是为了你一个人,这比养活自己重要得多。


via: https://opensource.com/article/18/3/what-open-source-programming

作者:Jim Salter 译者:Valoniakim 校对:wxypityonline

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

图表及其它可视化方式让传递数据的信息变得更简单。

对于数据可视化和制作精美网站来说,图表和图形很重要。视觉上的展示让分析大块数据及传递信息变得更简单。JavaScript 图表库能让数据以极好的、易于理解的和交互的方式进行可视化,还能够优化你的网站设计。

本文会带你学习最好的 3 个开源 JavaScript 图表库。

1、 Chart.js

Chart.js 是一个开源的 JavaScript 库,你可以在自己的应用中用它创建生动美丽和交互式的图表。使用它需要遵循 MIT 协议。

使用 Chart.js,你可以创建各种各样令人印象深刻的图表和图形,包括条形图、折线图、范围图、线性标度和散点图。它可以响应各种设备,使用 HTML5 Canvas 元素进行绘制。

示例代码如下,它使用该库绘制了一个条形图。本例中我们使用 Chart.js 的内容分发网络(CDN)来包含这个库。注意这里使用的数据仅用于展示。

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
</head>

<body>
   
    <canvas id="bar-chart" width=300" height="150"></canvas>
  
    <script>
     
new Chart(document.getElementById("bar-chart"), {
    type: 'bar',
    data: {
      labels: ["North America", "Latin America", "Europe", "Asia", "Africa"],
      datasets: [
        {
          label: "Number of developers (millions)",
          backgroundColor: ["red", "blue","yellow","green","pink"],
          data: [7,4,6,9,3]
        }
      ]
    },
    options: {
      legend: { display: false },
      title: {
        display: true,
        text: 'Number of Developers in Every Continent'
      },

      scales: {
            yAxes: [{
                ticks: {
                    beginAtZero:true
                }
            }]
        }

    }

});
    </script>
   
</body>
</html>

如你所见,通过设置 typebar 来构造条形图。你可以把条形体的方向改成其他类型 —— 比如把 type 设置成 horizontalBar

backgroundColor 数组参数中提供颜色类型,就可以设置条形图的颜色。

颜色被分配给关联数组中相同索引的标签和数据。例如,第二个标签 “Latin American”,颜色会是 “蓝色(blue)”(第二个颜色),数值是 4(data 中的第二个数字)。

代码的执行结果如下。

2、 Chartist.js

Chartist.js 是一个简单的 JavaScript 动画库,你能够自制美丽的响应式图表,或者进行其他创作。使用它需要遵循 WTFPL 或者 MIT 协议。

这个库是由一些对现有图表工具不满的开发者进行开发的,它可以为设计师或程序员提供美妙的功能。

在项目中包含 Chartist.js 库后,你可以使用它们来创建各式各样的图表,包括动画,条形图和折线图。它使用 SVG 来动态渲染图表。

这里是使用该库绘制一个饼图的例子。

<!DOCTYPE html>
<html>
<head>
   
    <link href="https//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css" rel="stylesheet" type="text/css" />
   
    <style>
        .ct-series-a .ct-slice-pie {
            fill: hsl(100, 20%, 50%); /* filling pie slices */
            stroke: white; /*giving pie slices outline */          
            stroke-width: 5px;  /* outline width */
          }

          .ct-series-b .ct-slice-pie {
            fill: hsl(10, 40%, 60%);
            stroke: white;
            stroke-width: 5px;
          }

          .ct-series-c .ct-slice-pie {
            fill: hsl(120, 30%, 80%);
            stroke: white;
            stroke-width: 5px;
          }

          .ct-series-d .ct-slice-pie {
            fill: hsl(90, 70%, 30%);
            stroke: white;
            stroke-width: 5px;
          }
          .ct-series-e .ct-slice-pie {
            fill: hsl(60, 140%, 20%);
            stroke: white;
            stroke-width: 5px;
          }

    </style>
     </head>

<body>

    <div class="ct-chart ct-golden-section"></div>

    <script src="https://cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>

    <script>
       
      var data = {
            series: [45, 35, 20]
            };

      var sum = function(a, b) { return a + b };

      new Chartist.Pie('.ct-chart', data, {
        labelInterpolationFnc: function(value) {
          return Math.round(value / data.series.reduce(sum) * 100) + '%';
            }
              });
     </script>
</body>
</html>

使用 Chartist JavaScript 库,你可以使用各种预先构建好的 CSS 样式,而不是在项目中指定各种与样式相关的部分。你可以使用这些样式来设置已创建的图表的外观。

比如,预创建的 CSS 类 .ct-chart 是用来构建饼状图的容器。还有 .ct-golden-section 类可用于获取纵横比,它基于响应式设计进行缩放,帮你解决了计算固定尺寸的麻烦。Chartist 还提供了其它类别的比例容器,你可以在自己的项目中使用它们。

为了给各个扇形设置样式,可以使用默认的 .ct-serials-a 类。字母 a 是根据系列的数量变化的(a、b、c,等等),因此它与每个要设置样式的扇形相对应。

Chartist.Pie 方法用来创建一个饼状图。要创建另一种类型的图表,比如折线图,请使用 Chartist.Line

代码的执行结果如下。

3、 D3.js

D3.js 是另一个好用的开源 JavaScript 图表库。使用它需要遵循 BSD 许可证。D3 的主要用途是,根据提供的数据,处理和添加文档的交互功能,。

借助这个 3D 动画库,你可以通过 HTML5、SVG 和 CSS 来可视化你的数据,并且让你的网站变得更精美。更重要的是,使用 D3,你可以把数据绑定到文档对象模型(DOM)上,然后使用基于数据的函数改变文档。

示例代码如下,它使用该库绘制了一个简单的条形图。

<!DOCTYPE html>
<html>
<head>
     
    <style>
    .chart div {
      font: 15px sans-serif;
      background-color: lightblue;
      text-align: right;
      padding:5px;
      margin:5px;
      color: white;
      font-weight: bold;
    }
       
    </style>
     </head>

<body>

    <div class="chart"></div>
   
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.5.0/d3.min.js"></script>

    <script>

      var data = [342,222,169,259,173];

      d3.select(".chart")
        .selectAll("div")
        .data(data)
          .enter()
          .append("div")
          .style("width", function(d){ return d + "px"; })
          .text(function(d) { return d; });
       
 
    </script>
</body>
</html>

使用 D3 库的主要概念是应用 CSS 样式选择器来定位 DOM 节点,然后对其执行操作,就像其它的 DOM 框架,比如 JQuery。

将数据绑定到文档上后,.enter() 函数会被调用,为即将到来的数据构建新的节点。所有在 .enter() 之后调用的方法会为数据中的每一个项目调用一次。

代码的执行结果如下。

总结

JavaScript 图表库提供了强大的工具,你可以将自己的网络资源进行数据可视化。通过这三个开源库,你可以把自己的网站变得更好看,更容易使用。

你知道其它强大的用于创造 JavaScript 动画效果的前端库吗?请在下方的评论区留言分享。


via: https://opensource.com/article/18/9/open-source-javascript-chart-libraries

作者:Dr.Michael J.Garbade 选题:lujun9972 译者:BriFuture 校对:wxy

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