Duc：一个能够可视化洞察硬盘使用情况的工具包

Cool!

Cool!

Cool!

感觉 du 命令就够用了

![Duc：一个能够洞察并可视化硬盘使用情况的工具包](/data/attachment/album/201905/24/122746i737b3g393879b67.png)

Duc 是一个在类 Unix 操作系统上可以用来索引、洞察及可视化硬盘使用情况的工具包。别把它当成一个仅能用漂亮图表展现硬盘使用情况的 CLI 工具。它对巨大的文件系统也支持的很好。Duc 已在由超过五亿个文件和几 PB 的存储组成的系统上测试过，没有任何问题。

Duc 是一个快速而且灵活的工具。它将你的硬盘使用情况存在一个优化过的数据库里，这样你就可以在索引完成后迅速找到你的数据。此外，它自带不同的用户交互界面与后端以访问数据库并绘制图表。

以下列出的是目前支持的用户界面（UI）：

1. 命令行界面（`duc ls`）
2. Ncurses 控制台界面（`duc ui`）
3. X11 GUI（`duc gui`）
4. OpenGL GUI（`duc gui`）

支持的后端数据库：

* Tokyocabinet
* Leveldb
* Sqlite3

Duc 默认使用 Tokyocabinet 作为后端数据库。

### 安装 Duc

Duc 可以从 Debian 以及其衍生品例如 Ubuntu 的默认仓库中获取。因此在基于 DEB 的系统上安装 Duc 是小菜一碟。

```
$ sudo apt-get install duc
```

在其它 Linux 发行版上你需要像以下所展示的那样手动从源代码编译安装 Duc。

可以从 Github 上的[发行](https://github.com/zevv/duc/releases)页面下载最新的 Duc 源代码的 .tgz 文件。在写这篇教程的时候，最新的版本是1.4.4。

```
$ wget https://github.com/zevv/duc/releases/download/1.4.4/duc-1.4.4.tar.gz
```

然后一个接一个地运行以下命令来安装 DUC。

```
$ tar -xzf duc-1.4.4.tar.gz
$ cd duc-1.4.4
$ ./configure
$ make
$ sudo make install
```

### 使用 Duc

`duc` 的典型用法是：

```
$ duc <subcommand> <options>
```

你可以通过运行以下命令来浏览总的选项列表以及子命令：

```
$ duc help
```

你也可以像下面这样了解一个特定子命令的用法。

```
$ duc help <subcommand>
```

要查看所有命令与其选项的列表，仅需运行：

```
$ duc help --all
```

让我们看看一些 `duc` 工具的特定用法。

### 创建索引（数据库）

首先，你需要创建一个你文件系统的索引文件（数据库）。使用 `duc index` 命令以创建索引文件。

比如说，要创建你的 `/home` 目录的索引，仅需运行：

```
$ duc index /home
```

上述命令将会创建你的 `/home` 目录的索引，并将其保存在 `$HOME/.duc.db` 文件中。如果你以后需要往 `/home` 目录添加新的文件或目录，只要在之后重新运行一下上面的命令来重建索引。

### 查询索引

Duc 有不同的子命令来查询并探索索引。

要查看可访问的索引列表，运行：

```
$ duc info
```

示例输出：

```
Date Time Files Dirs Size Path
2019-04-09 15:45:55 3.5K 305 654.6M /home
```

如你在上述输出所见，我已经索引好了 `/home` 目录。

要列出当前工作目录中所有的文件和目录，你可以这样做：

```
$ duc ls
```

要列出指定的目录，例如 `/home/sk/Downloads` 中的文件/目录，仅需像下面这样将路径作为参数传过去。

```
$ duc ls /home/sk/Downloads
```

类似的，运行 `duc ui` 命令来打开基于 ncurses 的控制台用户界面以探索文件系统使用情况，运行`duc gui` 以打开图形界面（X11）来探索文件系统。

要了解更多子命令的用法，仅需参考帮助部分。

```
$ duc help ls
```

上述命令将会展现 `ls` 子命令的帮助部分。

### 可视化硬盘使用状况

在之前的部分我们以及看到如何用 duc 子命令列出文件和目录。在此之外，你甚至可以用一张漂亮的图表展示文件大小。

要展示所提供目录的图表，像以下这样使用 `ls` 子命令。

```
$ duc ls -Fg /home/sk
```

示例输出：

![使用 “duc ls” 命令可视化硬盘使用情况](/data/attachment/album/201905/24/122747zu4p4a1la17lpllp.png)

如你在上述输出所见，`ls` 子命令查询 duc 数据库并列出了所提供目录包含的文件与目录的大小，在这里就是 `/home/sk/`。

这里 `-F` 选项是往条目中用来添加文件类型指示符（`/`），`-g` 选项是用来绘制每个条目相对大小的图表。

请注意如果未提供任何路径，就会使用当前工作目录。

你可以使用 `-R` 选项来用[树状结构](https://www.ostechnix.com/view-directory-tree-structure-linux/)浏览硬盘使用情况。

```
$ duc ls -R /home/sk
```

![用树状结构可视化硬盘使用情况](/data/attachment/album/201905/24/122749k94q0l9jftxqzufr.png)

要查询 duc 数据库并打开基于 ncurses 的控制台以探索所提供的目录，像以下这样使用 `ui` 子命令。

```
$ duc ui /home/sk
```

![](/data/attachment/album/201905/24/122750p6irgz99ddhdfdyo.png)

类似的，我们使用 `gui *` 子命令来查询 duc 数据库以及打开一个图形界面（X11）来了解指定路径的硬盘使用情况。

```
$ duc gui /home/sk
```

![](/data/attachment/album/201905/24/122752cyyhozjhoyoxjo6y.png)

像我之前所提到的，我们可以像下面这样了解更多关于特定子命令的用法。

```
$ duc help <子命令名字>
```

我仅仅覆盖了基本用法的部分，参考 man 页面了解关于 `duc` 工具的更多细节。

```
$ man duc
```

相关阅读：

* [Filelight – 在你的 Linux 系统上可视化硬盘使用情况](https://www.ostechnix.com/filelight-visualize-disk-usage-on-your-linux-system/)
* [一些好的 du 命令的替代品](https://www.ostechnix.com/some-good-alternatives-to-du-command/)
* [如何在 Linux 中用 Ncdu 检查硬盘使用情况](https://www.ostechnix.com/check-disk-space-usage-linux-using-ncdu/)
* [Agedu——发现 Linux 中被浪费的硬盘空间](https://www.ostechnix.com/agedu-find-out-wasted-disk-space-in-linux/)
* [如何在 Linux 中找到目录大小](https://www.ostechnix.com/find-size-directory-linux/)
* [为初学者打造的带有示例的 df 命令教程](https://www.ostechnix.com/the-df-command-tutorial-with-examples-for-beginners/)

### 总结

Duc 是一款简单却有用的硬盘用量查看器。如果你想要快速简便地知道哪个文件/目录占用你的硬盘空间，Duc 可能是一个好的选择。你还等什么呢？获取这个工具，扫描你的文件系统，摆脱无用的文件/目录。

现在就到此为止了。希望这篇文章有用处。更多好东西马上就到。保持关注！

欢呼吧！

资源：

* [Duc 网站](https://duc.zevv.nl/)

---

via: <https://www.ostechnix.com/duc-a-collection-of-tools-to-inspect-and-visualize-disk-usage/>

作者：[sk](https://www.ostechnix.com/author/sk/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[tomjlw](https://github.com/tomjlw) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


PiShrink：使树莓派镜像更小

Summary: PiShrink 将自动缩小镜像，然后在启动时将其调整为 SD 卡的最大大小。

赞呀，这个帖子帮我解决了问题！

PiShrink 将自动缩小镜像，然后在启动时将其调整为 SD 卡的最大大小。

![Make Raspberry Pi Images Smaller With PiShrink In Linux](/data/attachment/album/201905/24/124416eti958m5tt55vtbv.png)

树莓派不需要过多介绍。它是一款小巧、价格实惠，只有信用卡大小的电脑，它可以连接到显示器或电视。我们可以连接一个标准的键盘和鼠标，并将其用作一台成熟的台式计算机来完成日常任务，如互联网浏览、播放视频/玩游戏、文字处理和电子表格制作等。它主要是为学校的计算机科学教学而开发的。如今，树莓派被广泛用于大学、中小型组织和研究所来教授编码。

如果你有一台树莓派，你可能需要了解一个名为 PiShrink 的 bash 脚本，该脚本可使树莓派镜像更小。PiShrink 将自动缩小镜像，然后在启动时将其调整为 SD 卡的最大大小。这能更快地将镜像复制到 SD 卡中，同时缩小的镜像将更好地压缩。这对于将大容量镜像放入 SD 卡非常有用。在这个简短的指南中，我们将学习如何在类 Unix 系统中将树莓派镜像缩小到更小。

### 安装 PiShrink

要在 Linux 机器上安装 PiShrink，请先使用以下命令下载最新版本：

```
$ wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
```

接下来，将下载的 PiShrink 变成二进制可执行文件：

```
$ chmod +x pishrink.sh
```

最后，移动到目录：

```
$ sudo mv pishrink.sh /usr/local/bin/
```

### 使树莓派镜像更小

你可能已经知道，Raspbian 是所有树莓派型号的官方操作系统。树莓派基金会为 PC 和 Mac 开发了树莓派桌面版本。你可以创建一个 live CD，并在虚拟机中运行它，甚至也可以将其安装在桌面上。树莓派也有少量非官方操作系统镜像。为了测试，我从[官方下载页面](https://www.raspberrypi.org/downloads/)下载了官方的 Raspbian 系统。

解压下载的系统镜像：

```
$ unzip 2019-04-08-raspbian-stretch-lite.zip
```

上面的命令将提取当前目录中 `2019-04-08-raspbian-stretch-lite.zip` 文件的内容。

让我们看下提取文件的实际大小：

```
$ du -h 2019-04-08-raspbian-stretch-lite.img
1.7G 2019-04-08-raspbian-stretch-lite.img
```

如你所见，提取的树莓派系统镜像大小为 1.7G。

现在，使用 PiShrink 缩小此文件的大小，如下所示：

```
$ sudo pishrink.sh 2019-04-08-raspbian-stretch-lite.img
```

示例输出：

```
Creating new /etc/rc.local
rootfs: 39795/107072 files (0.1% non-contiguous), 239386/428032 blocks
resize2fs 1.45.0 (6-Mar-2019)
resize2fs 1.45.0 (6-Mar-2019)
Resizing the filesystem on /dev/loop1 to 280763 (4k) blocks.
Begin pass 3 (max = 14)
Scanning inode table XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Begin pass 4 (max = 3728)
Updating inode references XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
The filesystem on /dev/loop1 is now 280763 (4k) blocks long.

Shrunk 2019-04-08-raspbian-stretch-lite.img from 1.7G to 1.2G
```

正如你在上面的输出中看到的，树莓派镜像的大小已减少到 1.2G。

你还可以使用 `-s` 标志跳过该过程的自动扩展部分。

```
$ sudo pishrink.sh -s 2019-04-08-raspbian-stretch-lite.img newpi.img
```

这将创建一个源镜像文件（即 `2019-04-08-raspbian-stretch-lite.img`）的副本到一个新镜像文件（`newpi.img`）并进行处理。有关更多详细信息，请查看最后给出的官方 GitHub 页面。

就是这些了。希望本文有用。还有更多好东西，敬请期待！

资源：

* [PiShrink 的 GitHub 仓库](https://github.com/Drewsif/PiShrink)
* [树莓派网站](https://www.raspberrypi.org/)

---

via: <https://www.ostechnix.com/pishrink-make-raspberry-pi-images-smaller/>

作者：[sk](https://www.ostechnix.com/author/sk/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


使用 Ispell 在命令行中检查拼写

Summary: Ispell 可以帮助你在纯文本中消除超过 50 种语言的拼写错误。

Ispell 可以帮助你在纯文本中消除超过 50 种语言的拼写错误。

>
> Ispell 可以帮助你在纯文本中消除超过 50 种语言的拼写错误。
>
>
>

![](/data/attachment/album/201905/24/192644wqqv6d0lztmqoqyl.jpg)

好的拼写是一种技巧。它是一项需要时间学习和掌握的技能。也就是说，有些人从来没有完全掌握这种技能，我知道有两三个出色的作家就无法完全掌握拼写。

即使你拼写得很好，偶尔也会输入错字。特别是在最后期限前如果你快速敲击键盘，那就更是如此。无论你的拼写的是什么，通过拼写检查器检查你所写的内容总是一个好主意。

我用[纯文本](https://plaintextproject.online)完成了我的大部分写作，并经常使用名为 [Aspell](https://opensource.com/article/18/2/how-check-spelling-linux-command-line-aspell) 的命令行拼写检查器来完成这项工作。Aspell 不是唯一的工具。你可能还想要看下不错的 [Ispell](https://www.cs.hmc.edu/%7Egeoff/ispell.html)。

### 入门

自 1971 年以来，Ispell 就以各种形式出现过。不要被它的年龄欺骗。Ispell 仍然是一个可以在 21 世纪高效使用的应用。

在开始之前，请打开终端窗口并输入 `which ispell` 来检查计算机上是否安装了 Ispell。如果未安装，请打开发行版的软件包管理器并从那里安装 Ispell。

不要忘记为你使用的语言安装词典。我唯一使用的语言是英语，所以我只需下载美国和英国英语字典。你可以不局限于我的（也是唯一的）母语。Ispell 有[超过 50 种语言的词典](https://www.cs.hmc.edu/%7Egeoff/ispell-dictionaries.html)。

![Installing Ispell dictionaries](/data/attachment/album/201905/24/192706zznjmeyv1yyg42qn.png "Installing Ispell dictionaries")

### 使用 Ispell

如果你还没有猜到，Ispell 只能用在文本文件。这包括用 HTML、LaTeX 和 [nroff 或 troff](https://opensource.com/article/18/2/how-format-academic-papers-linux-groff-me) 标记的文档。之后会有更多相关内容。

要开始使用，请打开终端窗口并进入包含要运行拼写检查的文件的目录。输入 `ispell` 后跟文件名，然后按回车键。

![Checking spelling with Ispell](/data/attachment/album/201905/24/192707pkc089bjj0mjcabk.png "Checking spelling with Ispell")

Ispell 高亮了它无法识别的第一个词。如果单词拼写错误，Ispell 通常会提供一个或多个备选方案。按下 `R`，然后按下正确选择旁边的数字。在上面的截图中，我按了 `R` 和 `0` 来修复错误。

另一方面，如果单词拼写正确，请按下 `A` 然后移动到下一个拼写错误的单词。

继续这样做直到到达文件的末尾。Ispell 会保存你的更改，创建你刚检查的文件的备份（扩展名为 `.bak`），然后关闭。

### 其他几个选项

此示例说明了 Ispell 的基本用法。这个程序有[很多选项](https://www.cs.hmc.edu/%7Egeoff/ispell-man.html)，有些你*可能*会用到，而另一些你*可能永远*不会使用。让我们快速看下我经常使用的一些。

之前我提到过 Ispell 可以用于某些标记语言。你需要告诉它文件的格式。启动 Ispell 时，为 TeX 或 LaTeX 文件添加 `-t`，为 HTML 文件添加 `-H`，对于 groff 或 troff 文件添加 `-n`。例如，如果输入 `ispell -t myReport.tex`，Ispell 将忽略所有标记。

如果你不想在检查文件后创建备份文件，请将 `-x` 添加到命令行。例如，`ispell -x myFile.txt`。

如果 Ispell 遇到拼写正确但不在其字典中的单词，比如名字，会发生什么？你可以按 `I` 将该单词添加到个人单词列表中。这会将单词保存到 `/home` 目录下的 `.ispell_default` 的文件中。

这些是我在使用 Ispell 时最有用的选项，但请查看 [Ispell 的手册页](https://www.cs.hmc.edu/%7Egeoff/ispell-man.html)以了解其所有选项。

Ispell 比 Aspell 或其他命令行拼写检查器更好或者更快么？我会说它不比其他的差或者慢。Ispell 不是适合所有人。它也许也不适合你。但有更多选择也不错，不是么？

---

via: <https://opensource.com/article/19/5/spelling-command-line-ispell>

作者：[Scott Nesbitt](https://opensource.com/users/scottnesbitt) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


“文章助手”的助手

Summary: 半个月前，我们推出了一个“文章助手”的小程序，用于解决在微信公众号文章中无法放置可点击的链接的问题。

![](/data/attachment/album/201905/25/093229caa8kga3aiaylzw3.jpg)

半个月前，我们推出了一个“文章助手”的小程序，用于解决在微信公众号文章中**无法放置可点击的链接**的问题。

可能对于我们这种技术向的文章来说，很多时候都需要插入链接，而一个认真的读者也经常希望可以点击链接看看。在我们从 2013 年开始运营公众号以来，我们对此问题有过几种解决方案：

* 直接将链接以文字的方式显示在**正文**中，读者需要手工选择并复制链接，然后另外在浏览器中打开
* 类似于文章脚注一样，将链接放置在**文末**，读者需要翻到文末，根据所要找的链接标号来复制并在浏览器中打开
* 将链接整理后放在另外一个 web 页面中，然后通过“**阅读原文**”的方式引导读者去点击，在其中完成链接功能

但是这几种方法都不太尽如人意，读者经常还是会下意识去点击文章内该出现链接的地方。所以，我们最近又有了新的解决方案。我们利用微信小程序的能力，在微信公众号文章内，采用小程序链接来替代外部链接；点击小程序链接后会打开该小程序，自动复制外部链接到剪贴板；打开浏览器（自动）贴入剪贴板中的链接来访问。

应该说，效果还是达到了我们的预期，虽然有些功能限于小程序本身支持无法做到，比如无法得到来源公众号的信息、无法主动唤起浏览器等。

不过，在使用过程中，公众号编辑们发现对链接一个个进行替换非常繁琐，所以，我决定给这个“文章助手”提供一个“助手”。我做了一个静态页面，在此页面内，只要将你编辑的公众号文章内容贴入其中的输入框，**一键点击**即可将全部链接转换为“文章助手”链接。

此外，经常还有认识或不认识的朋友，对我们的公众号排版表示好奇，比如这种<ruby> 注释性的标签 <rp>  （ </rp> <rt>  Ruby tag </rt> <rp>  ） </rp></ruby>是怎么回事？也有人希望采用 PingFang 字体，这个是微信编辑器默认不提供的。这次我们就一便提供好了。

“文章助手”的助手地址如下： <https://linux.cn/static/tools/a.html>

![](/data/attachment/album/201905/25/091843exyg54kawayweyys.jpg)

* 这个页面是纯静态页面，你可以连着其中使用的 jQuery 保存下来自行使用。
* 该小程序永久免费，并永久不添加第三方广告。
* 除不可抗力（如被微信官方封杀，但目前我们判断并未触犯微信使用规则），该小程序会一直运营下去。
* 贴入输入框的文章内容可能暂时不能显示来自微信已发送文章内的图片，但是并不影响转换和转换后再贴入微信编辑器内使用。
* 在输入框中选定格式“中文（English）”这样的内容时，点击下方的“转换 RUBY”的按钮，会将该字符串转换为<ruby> 中文 <rp>  （ </rp> <rt>  English </rt> <rp>  ） </rp></ruby>样式。
* 如果要全文使用“PingFang”字体，转换前勾选即可（最显眼的区别是，逗号和句号是垂直居中而不是底线对齐的）。

好了，老王的这些家底都给你们了，祝你们的公众号文章看起来越来越专业、越来越漂亮。


在 Ubuntu 中使用 Slimbook Battery Optimizer 切换电源模式

>
> Slimbook Battery Optimizer 是一个美观实用的指示器小程序，它可以让你在安装了 Linux 的笔记本上快速切换电源模式来延长续航时间。
>
>
>

[Slimbook](https://slimbook.es/en/) 是一个销售 [预装 Linux 的笔记本电脑](https://itsfoss.com/get-linux-laptops/) 的西班牙电脑制造商，他们发布了一款好用的小程序，用来在基于 Ubuntu 的 Linux 发行版下调整电池性能。

因为 Slimbook 销售他们自己的 Linux 系统，所以他们制作了一些在 Linux 上用于调整他们自己硬件性能的小工具。Battery Optimizer 就是这样一个工具。

要使用这个实用小程序，你不必购买 Slimbook 的产品，因为 Slimbook 已经将它在 [他们的官方 PPA 源](https://launchpad.net/%7Eslimbook/+archive/ubuntu/slimbook) 发行了。

### Slimbook Battery Optimizer 简介

这个程序叫 Slimbook Battery。它是一个常驻顶栏的指示器小程序，使得你可以快速切换电源模式。

![Slimbook Battery Mode Ubuntu](/data/attachment/album/201905/25/101349katxob9x5oobb5tb.jpg)

你可能在 Windows 中见过类似的程序。Slimbook Battery 和它们一样，提供了类似的电源计划：

* 节能：最大程度延长电池续航时间
* 平衡：性能与节能间的最佳平衡
* 高性能：最大程度提高性能

你可以在高级模式中配置这些模式：

![配置多种多样的电源模式](/data/attachment/album/201905/25/101350rlkelgsjji1491nl.jpg)

如果你觉得你把设置调乱了，你可以用“恢复默认设置”的按钮还原它。

你也可以修改像程序自启或默认电源模式这样的通用设置。

![Slimbook Battery 通用设置](/data/attachment/album/201905/25/101351o9hj3j6ezm6nl0y9.jpg)

Slimbook 有专门为多种电源管理参数提供的页面。如果你希望自己配置，请参照 [此页](https://slimbook.es/en/tutoriales/aplicaciones-slimbook/398-slimbook-battery-3-application-for-optimize-battery-of-your-laptop)。

不过，我认为 Slimbook 的界面需要一些改进。例如，某些页面上的“问题标记”的图标应该改为可点击的，以此提供更多信息。然而，在我写这篇文章时，那个标记仍然无法点击。

总的来说，Slimbook Battery 是一个小巧精美的软件，你可以用它来快速切换电源模式。如果你决定在 Ubuntu 及其衍生发行版上（比如 Linux Mint 或 elementary OS 等），你可以使用官方 [PPA 源](https://itsfoss.com/ppa-guide/)。

#### 在基于 Ubuntu 的发行版上安装 Slimbook Battery

打开终端，一步一步地使用以下命令：

```
sudo add-apt-repository ppa:slimbook/slimbook
sudo apt update
sudo apt install slimbookbattery
```

安装好之后，在菜单中搜索 Slimbook Battery：

![启动 Slimbook Battery Optimizer](/data/attachment/album/201905/25/101351tb7mbqfmsju7zusb.jpg)

在你点击它之后，你会发现它出现在了顶栏。你可以在这里选择你希望使用的电源模式。

![Slimbook Battery 电源模式](/data/attachment/album/201905/25/101349katxob9x5oobb5tb.jpg)

#### 卸载 Slimbook Battery

如果你不再使用它，你可以通过以下命令来卸载它：

```
sudo apt remove slimbookbattery
sudo add-apt-repository -r ppa:slimbook/slimbook
```

在我看来，这样的应用程序为某些特定的目的服务，这是值得鼓励的。这个工具给了你一条调整电源模式的捷径，和调整性能的更多选项。

你用过 Slimbook Battery 吗？你觉得它如何？

---

via: <https://itsfoss.com/slimbook-battry-optimizer-ubuntu/>

作者：[Abhishek Prakash](https://itsfoss.com/author/abhishek/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[zhs852](https://github.com/zhs852) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


PHP PDO 简单教程

Summary: PHP 5.5 版本之前，我们有用于访问 MySQL 数据库的 mysql_ 命令，但由于安全性不足，它们最终被弃用。mysql_ 的弃用带来了访问数据库的两种主要方法：mysqli 和 PDO 库。

![](/data/attachment/album/201905/26/104631lriqriqrrusyyy22.jpg)

大约 80% 的 Web 应用程序由 PHP 提供支持。类似地，SQL 也是如此。PHP 5.5 版本之前，我们有用于访问 MySQL 数据库的 mysql\_ 命令，但由于安全性不足，它们最终被弃用。

弃用这件事是发生在 2013 年的 PHP 5.5 上，我写这篇文章的时间是 2018 年，PHP 版本为 7.2。mysql\_ 的弃用带来了访问数据库的两种主要方法：mysqli 和 PDO 库。

虽然 mysqli 库是官方指定的，但由于 mysqli 只能支持 mysql 数据库，而 PDO 可以支持 12 种不同类型的数据库驱动程序，因此 PDO 获得了更多的赞誉。此外，PDO 还有其它一些特性，使其成为大多数开发人员的更好选择。你可以在下表中看到一些特性比较：

|  | PDO | MySQLi |
| --- | --- | --- |
| 数据库支持 | 12 种驱动 | 只有 MySQL |
| 范例 | OOP | 过程 + OOP |
| 预处理语句（客户端侧） | Yes | No |
| 1命名参数 | Yes | No |

现在我想对于大多数开发人员来说，PDO 是首选的原因已经很清楚了。所以让我们深入研究它，并希望在本文中尽量涵盖关于 PDO 你需要的了解的。

### 连接

第一步是连接到数据库，由于 PDO 是完全面向对象的，所以我们将使用 PDO 类的实例。

我们要做的第一件事是定义主机、数据库名称、用户名、密码和数据库字符集。

```
$host = 'localhost';
$db   = 'theitstuff';
$user = 'root';
$pass = 'root';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$conn = new PDO($dsn, $user, $pass);
```

之后，正如你在上面的代码中看到的，我们创建了 DSN 变量，DSN 变量只是一个保存数据库信息的变量。对于一些在外部服务器上运行 MySQL 的人，你还可以通过提供一个 `port=$port_number` 来调整端口号。

最后，你可以创建一个 PDO 类的实例，我使用了 `$conn` 变量，并提供了 `$dsn`、`$user`、`$pass` 参数。如果你遵循这些步骤，你现在应该有一个名为 `$conn` 的对象，它是 PDO 连接类的一个实例。现在是时候进入数据库并运行一些查询。

### 一个简单的 SQL 查询

现在让我们运行一个简单的 SQL 查询。

```
$tis = $conn->query('SELECT name, age FROM students');
while ($row = $tis->fetch())
{
  echo $row['name']."\t";
  echo $row['age'];
  echo "<br>";
}
```

这是使用 PDO 运行查询的最简单形式。我们首先创建了一个名为 `tis`（TheITStuff 的缩写 ）的变量，然后你可以看到我们使用了创建的 `$conn` 对象中的查询函数。

然后我们运行一个 `while` 循环并创建了一个 `$row` 变量来从 `$tis` 对象中获取内容，最后通过调用列名来显示每一行。

很简单，不是吗？现在让我们来看看预处理语句。

### 预处理语句

预处理语句是人们开始使用 PDO 的主要原因之一，因为它提供了可以阻止 SQL 注入的语句。

有两种基本方法可供使用，你可以使用位置参数或命名参数。

#### 位置参数

让我们看一个使用位置参数的查询示例。

```
$tis = $conn->prepare("INSERT INTO STUDENTS(name, age) values(?, ?)");
$tis->bindValue(1,'mike');
$tis->bindValue(2,22);
$tis->execute();
```

在上面的例子中，我们放置了两个问号，然后使用 `bindValue()` 函数将值映射到查询中。这些值绑定到语句问号中的位置。

我还可以使用变量而不是直接提供值，通过使用 `bindParam()` 函数相同例子如下：

```
$name='Rishabh'; $age=20;
$tis = $conn->prepare("INSERT INTO STUDENTS(name, age) values(?, ?)");
$tis->bindParam(1,$name);
$tis->bindParam(2,$age);
$tis->execute();
```

### 命名参数

命名参数也是预处理语句，它将值/变量映射到查询中的命名位置。由于没有位置绑定，因此在多次使用相同变量的查询中非常有效。

```
$name='Rishabh'; $age=20;
$tis = $conn->prepare("INSERT INTO STUDENTS(name, age) values(:name, :age)");
$tis->bindParam(':name', $name);
$tis->bindParam(':age', $age);
$tis->execute();
```

你可以注意到，唯一的变化是我使用 `:name` 和 `:age` 作为占位符，然后将变量映射到它们。冒号在参数之前使用，让 PDO 知道该位置是一个变量，这非常重要。

你也可以类似地使用 `bindValue()` 来使用命名参数直接映射值。

### 获取数据

PDO 在获取数据时非常丰富，它实际上提供了许多格式来从数据库中获取数据。

你可以使用 `PDO::FETCH_ASSOC` 来获取关联数组，`PDO::FETCH_NUM` 来获取数字数组，使用 `PDO::FETCH_OBJ` 来获取对象数组。

```
$tis = $conn->prepare("SELECT * FROM STUDENTS");
$tis->execute();
$result = $tis->fetchAll(PDO::FETCH_ASSOC);
```

你可以看到我使用了 `fetchAll`，因为我想要所有匹配的记录。如果只需要一行，你可以简单地使用 `fetch`。

现在我们已经获取了数据，现在是时候循环它了，这非常简单。

```
foreach ($result as $lnu){
  echo $lnu['name'];
  echo $lnu['age']."<br>";
}
```

你可以看到，因为我请求了关联数组，所以我正在按名称访问各个成员。

虽然在定义希望如何传输递数据方面没有要求，但在定义 `$conn` 变量本身时，实际上可以将其设置为默认值。

你需要做的就是创建一个 `$options` 数组，你可以在其中放入所有默认配置，只需在 `$conn` 变量中传递数组即可。

```
$options = [
  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
$conn = new PDO($dsn, $user, $pass, $options);
```

这是一个非常简短和快速的 PDO 介绍，我们很快就会制作一个高级教程。如果你在理解本教程的任何部分时遇到任何困难，请在评论部分告诉我，我会在那你为你解答。

---

via: <http://www.theitstuff.com/easiest-pdo-tutorial-basics>

作者：[Rishabh Kandari](http://www.theitstuff.com/author/reevkandari) 选题：[lujun9972](https://github.com/lujun9972) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


API 演进的正确方式

Summary: 负责任的库作者与其用户的十个约定。

您在百度可以搜到的 果敢老街位于云南省 临沧市 镇康县 南伞镇 出个国门 10公里就到了

我们公司的实体 就在 缅甸果敢老街 双凤塔前面 80米左右 随时你可以视频验证呢

公司是经过这边政府合法营业，腾龙公司实力强大资金雄厚  官网的顾客朋友，都可以放心的娱乐，不会像那些假平台黑你的钱，其一：一个投资上亿的公司每天拥有上万个网投客人，这样的公司是不会骗你那点小钱的，因为这么大的一个公司怎么可能会因为你的那点钱自毁名声呢。其二：缅甸的公司都是经过政府的审核以后才允许开的，政府把公司视为最大的经济核心，就算有心玩假，

翻译的真好，新译者学习了。

负责任的库作者与其用户的十个约定。

>
> 负责任的库作者与其用户的十个约定。
>
>
>

![](/data/attachment/album/201905/26/134131jnymeg7t7gmo6qcy.jpg)

想象一下你是一个造物主，为一个生物设计一个身体。出于仁慈，你希望它能随着时间进化：首先，因为它必须对环境的变化作出反应；其次，因为你的智慧在增长，你对这个小东西想到了更好的设计，它不应该永远保持一个样子。

![Serpents](/data/attachment/album/201905/26/134220rxfsxpef7pmk1wzy.jpg "Serpents")

然而，这个生物可能有赖于其目前解剖学的特征。你不能无所顾忌地添加翅膀或改变它的身材比例。它需要一个有序的过程来适应新的身体。作为一个负责任的设计者，你如何才能温柔地引导这种生物走向更大的进步呢？

对于负责任的库维护者也是如此。我们向依赖我们代码的人保证我们的承诺：我们会发布 bug 修复和有用的新特性。如果对库的未来有利，我们有时会删除某些特性。我们会不断创新，但我们不会破坏使用我们库的人的代码。我们怎样才能一次实现所有这些目标呢？

### 添加有用的特性

你的库不应该永远保持不变：你应该添加一些特性，使你的库更适合用户。例如，如果你有一个爬行动物类，并且如果有个可以飞行的翅膀是有用的，那就去添加吧。

```
class Reptile:
    @property
    def teeth(self):
        return 'sharp fangs'

    # 如果 wings 是有用的，那就添加它!
    @property
    def wings(self):
        return 'majestic wings'
```

但要注意，特性是有风险的。考虑 Python 标准库中以下功能，看看它出了什么问题。

```
bool(datetime.time(9, 30)) == True
bool(datetime.time(0, 0)) == False
```

这很奇怪：将任何时间对象转换为布尔值都会得到 True，但午夜时间除外。（更糟糕的是，时区感知时间的规则更加奇怪。）

我已经写了十多年的 Python 了，但直到上周才发现这条规则。这种奇怪的行为会在用户代码中引起什么样的 bug？

比如说一个日历应用程序，它带有一个创建事件的函数。如果一个事件有一个结束时间，那么函数也应该要求它有一个开始时间。

```
def create_event(day,
                 start_time=None,
                 end_time=None):
    if end_time and not start_time:
        raise ValueError("Can't pass end_time without start_time")

    # 女巫集会从午夜一直开到凌晨 4 点
create_event(datetime.date.today(),
             datetime.time(0, 0),
             datetime.time(4, 0))
```

不幸的是，对于女巫来说，从午夜开始的事件无法通过校验。当然，一个了解午夜怪癖的细心程序员可以正确地编写这个函数。

```
def create_event(day,
                 start_time=None,
                 end_time=None):
    if end_time is not None and start_time is None:
        raise ValueError("Can't pass end_time without start_time")
```

但这种微妙之处令人担忧。如果一个库作者想要创建一个伤害用户的 API，那么像午夜的布尔转换这样的“特性”很有效。

![Man being chased by an alligator](/data/attachment/album/201905/26/134226k5f1mglbupccp6px.jpg "Man being chased by an alligator")

但是，负责任的创建者的目标是使你的库易于正确使用。

这个功能是由 Tim Peters 在 2002 年首次编写 datetime 模块时造成的。即时是像 Tim 这样的奠基 Python 的高手也会犯错误。[这个怪异之处后来被消除了](https://bugs.python.org/issue13936)，现在所有时间的布尔值都是 True。

```
# Python 3.5 以后

bool(datetime.time(9, 30)) == True
bool(datetime.time(0, 0)) == True
```

不知道午夜怪癖的古怪之处的程序员现在可以从这种晦涩的 bug 中解脱出来，但是一想到任何依赖于古怪的旧行为的代码现在没有注意变化，我就会感到紧张。如果从来没有实现这个糟糕的特性，情况会更好。这就引出了库维护者的第一个承诺：

#### 第一个约定：避免糟糕的特性

最痛苦的变化是你必须删除一个特性。一般来说，避免糟糕特性的一种方法是少添加特性！没有充分的理由，不要使用公共方法、类、功能或属性。因此：

#### 第二个约定：最小化特性

特性就像孩子：在充满激情的瞬间孕育，但是它们必须要支持多年（LCTT 译注：我怀疑作者在开车，可是我没有证据）。不要因为你能做傻事就去做傻事。不要画蛇添足！

![Serpents with and without feathers](/data/attachment/album/201905/26/134229yys47cug7acisiyy.jpg "Serpents with and without feathers")

但是，当然，在很多情况下，用户需要你的库中尚未提供的东西，你如何选择合适的功能给他们？以下另一个警示故事。

### 一个来自 asyncio 的警示故事

你可能知道，当你调用一个协程函数，它会返回一个协程对象：

```
async def my_coroutine():
    pass

print(my_coroutine())
```

```
<coroutine object my_coroutine at 0x10bfcbac8>
```

你的代码必须 “<ruby> 等待 <rt>  await </rt></ruby>” 这个对象以此来运行协程。人们很容易忘记这一点，所以 asyncio 的开发人员想要一个“调试模式”来捕捉这个错误。当协程在没有等待的情况下被销毁时，调试模式将打印一个警告，并在其创建的行上进行回溯。

当 Yury Selivanov 实现调试模式时，他添加了一个“协程装饰器”的基础特性。装饰器是一个函数，它接收一个协程并返回任何内容。Yury 使用它在每个协程上接入警告逻辑，但是其他人可以使用它将协程转换为字符串 “hi!”。

```
import sys

def my_wrapper(coro):
    return 'hi!'

sys.set_coroutine_wrapper(my_wrapper)

async def my_coroutine():
    pass

print(my_coroutine())
```

```
hi!
```

这是一个地狱般的定制。它改变了 “<ruby> 异步 <rt>  async </rt></ruby>“ 的含义。调用一次 `set_coroutine_wrapper` 将在全局永久改变所有的协程函数。正如 [Nathaniel Smith 所说](https://bugs.python.org/issue32591)：“一个有问题的 API” 很容易被误用，必须被删除。如果 asyncio 开发人员能够更好地按照其目标来设计该特性，他们就可以避免删除该特性的痛苦。负责任的创建者必须牢记这一点：

#### 第三个约定：保持特性单一

幸运的是，Yury 有良好的判断力，他将该特性标记为临时，所以 asyncio 用户知道不能依赖它。Nathaniel 可以用更单一的功能替换 `set_coroutine_wrapper`，该特性只定制回溯深度。

```
import sys

sys.set_coroutine_origin_tracking_depth(2)

async def my_coroutine():
    pass

print(my_coroutine())

```

```
<coroutine object my_coroutine at 0x10bfcbac8>

RuntimeWarning:'my_coroutine' was never awaited

Coroutine created at (most recent call last)
    File "script.py", line 8, in <module>
        print(my_coroutine())
```

这样好多了。没有可以更改协程的类型的其他全局设置，因此 asyncio 用户无需编写防御代码。造物主应该像 Yury 一样有远见。

#### 第四个约定：标记实验特征“临时”

如果你只是预感你的生物需要犄角和四叉舌，那就引入这些特性，但将它们标记为“临时”。

![Serpent with horns](/data/attachment/album/201905/26/134230k6rhvv5o9aov9aw5.jpg "Serpent with horns")

你可能会发现犄角是无关紧要的，但是四叉舌是有用的。在库的下一个版本中，你可以删除前者并标记后者为正式的。

### 删除特性

无论我们如何明智地指导我们的生物进化，总会有一天想要删除一个正式特征。例如，你可能已经创建了一只蜥蜴，现在你选择删除它的腿。也许你想把这个笨拙的家伙变成一条时尚而现代的蟒蛇。

![Lizard transformed to snake](/data/attachment/album/201905/26/134235hhu4kk9984kknk9k.jpg "Lizard transformed to snake")

删除特性主要有两个原因。首先，通过用户反馈或者你自己不断增长的智慧，你可能会发现某个特性是个坏主意。午夜怪癖的古怪行为就是这种情况。或者，最初该特性可能已经很好地适应了你的库环境，但现在生态环境发生了变化，也许另一个神发明了哺乳动物，你的生物想要挤进哺乳动物的小洞穴里，吃掉里面美味的哺乳动物，所以它不得不失去双腿。

![A mouse](/data/attachment/album/201905/26/134240iiixxm7p7pdp8xzw.jpg "A mouse")

同样，Python 标准库会根据语言本身的变化删除特性。考虑 asyncio 的 Lock 功能，在把 `await` 作为一个关键字添加进来之前，它一直在等待：

```
lock = asyncio.Lock()

async def critical_section():
    await lock
    try:
        print('holding lock')
    finally:
        lock.release()
```

但是现在，我们可以做“异步锁”：

```
lock = asyncio.Lock()

async def critical_section():
    async with lock:
        print('holding lock')
```

新方法好多了！很短，并且在一个大函数中使用其他 try-except 块时不容易出错。因为“尽量找一种，最好是唯一一种明显的解决方案”，[旧语法在 Python 3.7 中被弃用](https://bugs.python.org/issue32253)，并且很快就会被禁止。

不可避免的是，生态变化会对你的代码产生影响，因此要学会温柔地删除特性。在此之前，请考虑删除它的成本或好处。负责任的维护者不会愿意让用户更改大量代码或逻辑。（还记得 Python 3 在重新添加会 `u` 字符串前缀之前删除它是多么痛苦吗？）如果代码删除是机械性的动作，就像一个简单的搜索和替换，或者如果该特性是危险的，那么它可能值得删除。

#### 是否删除特性

![Balance scales](/data/attachment/album/201905/26/134242nn31guu3bx4gqe4t.jpg "Balance scales")

| 反对 | 支持 |
| --- | --- |
| 代码必须改变 | 改变是机械性的 |
| 逻辑必须改变 | 特性是危险的 |

就我们饥饿的蜥蜴而言，我们决定删除它的腿，这样它就可以滑进老鼠洞里吃掉它。我们该怎么做呢？我们可以删除 `walk` 方法，像下面一样修改代码：

```
class Reptile:
    def walk(self):
        print('step step step')
```

变成这样：

```
class Reptile:
    def slither(self):
        print('slide slide slide')
```

这不是一个好主意，这个生物习惯于走路！或者，就库而言，你的用户拥有依赖于现有方法的代码。当他们升级到最新库版本时，他们的代码将会崩溃。

```
# 用户的代码，哦，不!
Reptile.walk()
```

因此，负责任的创建者承诺：

#### 第五条预定：温柔地删除

温柔地删除一个特性需要几个步骤。从用腿走路的蜥蜴开始，首先添加新方法 `slither`。接下来，弃用旧方法。

```
import warnings

class Reptile:
    def walk(self):
        warnings.warn(
                "walk is deprecated, use slither",
                 DeprecationWarning, stacklevel=2)
        print('step step step')

    def slither(self):
        print('slide slide slide')
```

Python 的 warnings 模块非常强大。默认情况下，它会将警告输出到 stderr，每个代码位置只显示一次，但你可以禁用警告或将其转换为异常，以及其它选项。

一旦将这个警告添加到库中，PyCharm 和其他 IDE 就会使用删除线呈现这个被弃用的方法。用户马上就知道该删除这个方法。

>
> Reptile().~~walk()~~
>
>
>

当他们使用升级后的库运行代码时会发生什么？

```
$ python3 script.py

DeprecationWarning: walk is deprecated, use slither
    script.py:14: Reptile().walk()

step step step
```

默认情况下，他们会在 stderr 上看到警告，但脚本会成功并打印 “step step step”。警告的回溯显示必须修复用户代码的哪一行。（这就是 `stacklevel` 参数的作用：它显示了用户需要更改的调用，而不是库中生成警告的行。）请注意，错误消息有指导意义，它描述了库用户迁移到新版本必须做的事情。

你的用户可能会希望测试他们的代码，并证明他们没有调用弃用的库方法。仅警告不会使单元测试失败，但异常会失败。Python 有一个命令行选项，可以将弃用警告转换为异常。

```
> python3 -Werror::DeprecationWarning script.py

Traceback (most recent call last):
    File "script.py", line 14, in <module>
        Reptile().walk()
    File "script.py", line 8, in walk
        DeprecationWarning, stacklevel=2)
DeprecationWarning: walk is deprecated, use slither
```

现在，“step step step” 没有输出出来，因为脚本以一个错误终止。

因此，一旦你发布了库的一个版本，该版本会警告已启用的 `walk` 方法，你就可以在下一个版本中安全地删除它。对吧？

考虑一下你的库用户在他们项目的 `requirements` 中可能有什么。

```
# 用户的 requirements.txt 显示 reptile 包的依赖关系
reptile
```

下次他们部署代码时，他们将安装最新版本的库。如果他们尚未处理所有的弃用，那么他们的代码将会崩溃，因为代码仍然依赖 `walk`。你需要温柔一点，你必须向用户做出三个承诺：维护更改日志，选择版本化方案和编写升级指南。

#### 第六个约定：维护变更日志

你的库必须有更改日志，其主要目的是宣布用户所依赖的功能何时被弃用或删除。

>
> **版本 1.1 中的更改**
>
>
> **新特性**
>
>
> * 新功能 Reptile.slither()
>
>
> **弃用**
>
>
> * Reptile.walk() 已弃用，将在 2.0 版本中删除，请使用 slither()
>
>
>

负责任的创建者会使用版本号来表示库发生了怎样的变化，以便用户能够对升级做出明智的决定。“版本化方案”是一种用于交流变化速度的语言。

#### 第七个约定：选择一个版本化方案

有两种广泛使用的方案，[语义版本控制](https://semver.org)和基于时间的版本控制。我推荐任何库都进行语义版本控制。Python 的风格在 [PEP 440](https://www.python.org/dev/peps/pep-0440/) 中定义，像 `pip` 这样的工具可以理解语义版本号。

如果你为库选择语义版本控制，你可以使用版本号温柔地删除腿，例如：

>
> 1.0: 第一个“稳定”版，带有 `walk()` 1.1: 添加 `slither()`，废弃 `walk()` 2.0: 删除 `walk()`
>
>
>

你的用户依赖于你的库的版本应该有一个范围，例如：

```
# 用户的 requirements.txt
reptile>=1,<2
```

这允许他们在主要版本中自动升级，接收错误修正并可能引发一些弃用警告，但不会升级到**下**个主要版本并冒着更改破坏其代码的风险。

如果你遵循基于时间的版本控制，则你的版本可能会编号：

>
> 2017.06.0: 2017 年 6 月的版本 2018.11.0: 添加 `slither()`，废弃 `walk()` 2019.04.0: 删除 `walk()`
>
>
>

用户可以这样依赖于你的库：

```
# 用户的 requirements.txt，基于时间控制的版本
reptile==2018.11.*
```

这非常棒，但你的用户如何知道你的版本方案，以及如何测试代码来进行弃用呢？你必须告诉他们如何升级。

#### 第八个约定：写一个升级指南

下面是一个负责任的库创建者如何指导用户：

>
> **升级到 2.0**
>
>
> **从弃用的 API 迁移**
>
>
> 请参阅更改日志以了解已弃用的特性。
>
>
> **启用弃用警告**
>
>
> 升级到 1.1 并使用以下代码测试代码：
>
>
> `python -Werror::DeprecationWarning`
>
>
>  现在可以安全地升级了。
>
>
>

你必须通过向用户显示命令行选项来教会用户如何处理弃用警告。并非所有 Python 程序员都知道这一点 —— 我自己就每次都得查找这个语法。注意，你必须*发布*一个版本，它输出来自每个弃用的 API 的警告，以便用户可以在再次升级之前使用该版本进行测试。在本例中，1.1 版本是小版本。它允许你的用户逐步重写代码，分别修复每个弃用警告，直到他们完全迁移到最新的 API。他们可以彼此独立地测试代码和库的更改，并隔离 bug 的原因。

如果你选择语义版本控制，则此过渡期将持续到下一个主要版本，从 1.x 到 2.0，或从 2.x 到 3.0 以此类推。删除生物腿部的温柔方法是至少给它一个版本来调整其生活方式。不要一次性把腿删掉！

![A skink](/data/attachment/album/201905/26/134245ypfefay2pwy4hfly.jpg "A skink")

版本号、弃用警告、更改日志和升级指南可以协同工作，在不违背与用户约定的情况下温柔地改进你的库。[Twisted 项目的兼容性政策](https://twistedmatrix.com/documents/current/core/development/policy/compatibility-policy.html) 解释的很漂亮：

>
> “先行者总是自由的”
>
>
> 运行的应用程序在没有任何警告的情况下都可以升级为 Twisted 的一个次要版本。
>
>
> 换句话说，任何运行其测试而不触发 Twisted 警告的应用程序应该能够将其 Twisted 版本升级至少一次，除了可能产生新警告之外没有任何不良影响。
>
>
>

现在，我们的造物主已经获得了智慧和力量，可以通过添加方法来添加特性，并温柔地删除它们。我们还可以通过添加参数来添加特性，但这带来了新的难度。你准备好了吗？

### 添加参数

想象一下，你只是给了你的蛇形生物一对翅膀。现在你必须允许它选择是滑行还是飞行。目前它的 `move` 功能只接受一个参数。

```
# 你的库代码
def move(direction):
    print(f'slither {direction}')

# 用户的应用
move('north')
```

你想要添加一个 `mode` 参数，但如果用户升级库，这会破坏他们的代码，因为他们只传递了一个参数。

```
# 你的库代码
def move(direction, mode):
    assert mode in ('slither', 'fly')
    print(f'{mode} {direction}')

# 一个用户的代码，出现错误!
move('north')
```

一个真正聪明的创建者者会承诺不会以这种方式破坏用户的代码。

#### 第九条约定：兼容地添加参数

要保持这个约定，请使用保留原始行为的默认值添加每个新参数。

```
# 你的库代码
def move(direction, mode='slither'):
    assert mode in ('slither', 'fly')
    print(f'{mode} {direction}')

# 用户的应用
move('north')
```

随着时间推移，参数是函数演化的自然历史。它们首先列出最老的参数，每个都有默认值。库用户可以传递关键字参数以选择特定的新行为，并接受所有其他行为的默认值。

```
# 你的库代码
def move(direction,
         mode='slither',
         turbo=False,
         extra_sinuous=False,
         hail_lyft=False):
    # ...

# 用户应用
move('north', extra_sinuous=True)
```

但是有一个危险，用户可能会编写如下代码：

```
# 用户应用，简写
move('north', 'slither', False, True)
```

如果在你在库的下一个主要版本中去掉其中一个参数，例如 `turbo`，会发生什么？

```
# 你的库代码，下一个主要版本中 "turbo" 被删除
def move(direction,
         mode='slither',
         extra_sinuous=False,
         hail_lyft=False):
    # ...

# 用户应用，简写
move('north', 'slither', False, True)
```

用户的代码仍然能编译，这是一件坏事。代码停止了曲折的移动并开始招呼 Lyft，这不是它的本意。我相信你可以预测我接下来要说的内容：删除参数需要几个步骤。当然，首先弃用 `trubo` 参数。我喜欢这种技术，它可以检测任何用户的代码是否依赖于这个参数。

```
# 你的库代码
_turbo_default = object()

def move(direction,
         mode='slither',
         turbo=_turbo_default,
         extra_sinuous=False,
         hail_lyft=False):
    if turbo is not _turbo_default:
        warnings.warn(
                "'turbo' is deprecated",
                DeprecationWarning,
                stacklevel=2)
    else:
        # The old default.
        turbo = False
```

但是你的用户可能不会注意到警告。警告声音不是很大：它们可以在日志文件中被抑制或丢失。用户可能会漫不经心地升级到库的下一个主要版本——那个删除 `turbo` 的版本。他们的代码运行时将没有错误、默默做错误的事情！正如 Python 之禅所说：“错误绝不应该被默默 pass”。实际上，爬行动物的听力很差，所有当它们犯错误时，你必须非常大声地纠正它们。

![Woman riding an alligator](/data/attachment/album/201905/26/134247pcaszfvmwcqmmwhh.jpg "Woman riding an alligator")

保护用户的最佳方法是使用 Python 3 的星型语法，它要求调用者传递关键字参数。

```
# 你的库代码
# 所有 “*” 后的参数必须以关键字方式传输。
def move(direction,
         *,
         mode='slither',
         turbo=False,
         extra_sinuous=False,
         hail_lyft=False):
    # ...

# 用户代码，简写
# 错误！不能使用位置参数，关键字参数是必须的
move('north', 'slither', False, True)
```

有了这个星，以下是唯一允许的语法：

```
# 用户代码
move('north', extra_sinuous=True)
```

现在，当你删除 `turbo` 时，你可以确定任何依赖于它的用户代码都会明显地提示失败。如果你的库也支持 Python2，这没有什么大不了。你可以模拟星型语法（[归功于 Brett Slatkin](http://www.informit.com/articles/article.aspx?p=2314818)）：

```
# 你的库代码，兼容 Python 2
def move(direction, **kwargs):
    mode = kwargs.pop('mode', 'slither')
    turbo = kwargs.pop('turbo', False)
    sinuous = kwargs.pop('extra_sinuous', False)
    lyft = kwargs.pop('hail_lyft', False)

    if kwargs:
        raise TypeError('Unexpected kwargs: %r'
                        % kwargs)

# ...
```

要求关键字参数是一个明智的选择，但它需要远见。如果允许按位置传递参数，则不能仅在以后的版本中将其转换为仅关键字。所以，现在加上星号。你可以在 asyncio API 中观察到，它在构造函数、方法和函数中普遍使用星号。尽管到目前为止，`Lock` 只接受一个可选参数，但 asyncio 开发人员立即添加了星号。这是幸运的。

```
# In asyncio.
class Lock:
    def __init__(self, *, loop=None):
        # ...
```

现在，我们已经获得了改变方法和参数的智慧，同时保持与用户的约定。现在是时候尝试最具挑战性的进化了：在不改变方法或参数的情况下改变行为。

### 改变行为

假设你创造的生物是一条响尾蛇，你想教它一种新行为。

![Rattlesnake](/data/attachment/album/201905/26/134255yhc6lxmz6m6jjkkz.jpg "Rattlesnake")

横向移动！这个生物的身体看起来是一样的，但它的行为会发生变化。我们如何为这一进化步骤做好准备？

![](/data/attachment/album/201905/26/134313i7wd5lln5rdlciji.png)

*Image by HCA [[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0)], [via Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Neonate_sidewinder_sidewinding_with_tracks_unlabeled.jpg), 由 Opensource.com 修改*

当行为在没有新函数或新参数的情况下发生更改时，负责任的创建者可以从 Python 标准库中学习。很久以前，os 模块引入了 `stat` 函数来获取文件统计信息，比如创建时间。起初，这个时间总是整数。

```
>>> os.stat('file.txt').st_ctime
1540817862
```

有一天，核心开发人员决定在 `os.stat` 中使用浮点数来提供亚秒级精度。但他们担心现有的用户代码还没有做好准备更改。于是他们在 Python 2.3 中创建了一个设置 `stat_float_times`，默认情况下是 `False` 。用户可以将其设置为 True 来选择浮点时间戳。

```
>>> # Python 2.3.
>>> os.stat_float_times(True)
>>> os.stat('file.txt').st_ctime
1540817862.598021
```

从 Python 2.5 开始，浮点时间成为默认值，因此 2.5 及之后版本编写的任何新代码都可以忽略该设置并期望得到浮点数。当然，你可以将其设置为 `False` 以保持旧行为，或将其设置为 `True` 以确保所有 Python 版本都得到浮点数，并为删除 `stat_float_times` 的那一天准备代码。

多年过去了，在 Python 3.1 中，该设置已被弃用，以便为人们为遥远的未来做好准备，最后，经过数十年的旅程，[这个设置被删除](https://bugs.python.org/issue31827)。浮点时间现在是唯一的选择。这是一个漫长的过程，但负责任的神灵是有耐心的，因为我们知道这个渐进的过程很有可能于意外的行为变化拯救用户。

#### 第十个约定：逐渐改变行为

以下是步骤：

* 添加一个标志来选择新行为，默认为 `False`，如果为 `False` 则发出警告
* 将默认值更改为 `True`，表示完全弃用标记
* 删除该标志

如果你遵循语义版本控制，版本可能如下：

| 库版本 | 库 API | 用户代码 |
| --- | --- | --- |
| 1.0 | 没有标志 | 预期的旧行为 |
| 1.1 | 添加标志，默认为 `False`，如果是 `False`，则警告 | 设置标志为 `True`，处理新行为 |
| 2.0 | 改变默认为 `True`，完全弃用标志 | 处理新行为 |
| 3.0 | 移除标志 | 处理新行为 |

你需要**两**个主要版本来完成该操作。如果你直接从“添加标志，默认为 `False`，如果是 `False` 则发出警告”变到“删除标志”，而没有中间版本，那么用户的代码将无法升级。为 1.1 正确编写的用户代码必须能够升级到下一个版本，除了新警告之外，没有任何不良影响，但如果在下一个版本中删除了该标志，那么该代码将崩溃。一个负责任的神明从不违反扭曲的政策：“先行者总是自由的”。

### 负责任的创建者

![Demeter](/data/attachment/album/201905/26/134315sk08fu8tfh7t7007.jpg "Demeter")

我们的 10 个约定大致可以分为三类：

**谨慎发展**

1. 避免不良功能
2. 最小化特性
3. 保持功能单一
4. 标记实验特征“临时”
5. 温柔删除功能

**严格记录历史**

1. 维护更改日志
2. 选择版本方案
3. 编写升级指南

**缓慢而明显地改变**

1. 兼容添加参数
2. 逐渐改变行为

如果你对你所创造的物种保持这些约定，你将成为一个负责任的造物主。你的生物的身体可以随着时间的推移而进化，一直在改善和适应环境的变化，而不是在生物没有准备好就突然改变。如果你维护一个库，请向用户保留这些承诺，这样你就可以在不破坏依赖该库的代码的情况下对库进行更新。

---

这篇文章最初是在 [A. Jesse Jiryu Davis 的博客上'](https://emptysqua.re/blog/api-evolution-the-right-way/)出现的，经允许转载。

插图参考：

* [《世界进步》, Delphian Society, 1913](https://www.gutenberg.org/files/42224/42224-h/42224-h.htm)
* [《走进蛇的历史》, Charles Owen, 1742](https://publicdomainreview.org/product-att/artist/charles-owen/)
* [关于哥斯达黎加的 batrachia 和爬行动物，关于尼加拉瓜和秘鲁的爬行动物和鱼类学的记录, Edward Drinker Cope, 1875](https://archive.org/details/onbatrachiarepti00cope/page/n3)
* [《自然史》, Richard Lydekker et. al., 1897](https://www.flickr.com/photos/internetarchivebookimages/20556001490)
* [Mes Prisons, Silvio Pellico, 1843](https://www.oldbookillustrations.com/illustrations/stationery/)
* [Tierfotoagentur / m.blue-shadow](https://www.alamy.com/mediacomp/ImageDetails.aspx?ref=D7Y61W)
* [洛杉矶公共图书馆, 1930](https://www.vintag.es/2013/06/riding-alligator-c-1930s.html)

---

via: <https://opensource.com/article/19/5/api-evolution-right-way>

作者：[A. Jesse](https://opensource.com/users/emptysquare) 选题：[lujun9972](https://github.com/lujun9972) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


在 Pygame 游戏中放置平台

>
> 在这个从零构建一个 Python 游戏系列的第六部分中，为你的角色创建一些平台来旅行。
>
>
>

![](/data/attachment/album/201905/26/203703vkddhwhdthy5p1pw.png)

这是仍在进行中的关于使用 Pygame 模块来在 Python 3 中创建电脑游戏的系列文章的第六部分。先前的文章是：

* [通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程](/article-9071-1.html)
* [使用 Python 和 Pygame 模块构建一个游戏框架](/article-10850-1.html)
* [如何在你的 Python 游戏中添加一个玩家](/article-10858-1.html)
* [用 Pygame 使你的游戏角色移动起来](/article-10874-1.html)
* [如何向你的 Python 游戏中添加一个敌人](/article-10883-1.html)

一个平台类游戏需要平台。

在 [Pygame](https://www.pygame.org/news) 中，平台本身也是个妖精，正像你那个可玩的妖精。这一点是重要的，因为有个是对象的平台，可以使你的玩家妖精更容易与之互动。

创建平台有两个主要步骤。首先，你必须给该对象编写代码，然后，你必须映射出你希望该对象出现的位置。

### 编码平台对象

要构建一个平台对象，你要创建一个名为 `Platform` 的类。它是一个妖精，正像你的 `Player` [妖精](https://opensource.com/article/17/12/game-python-add-a-player) 一样，带有很多相同的属性。

你的 `Platform` 类需要知道很多平台类型的信息，它应该出现在游戏世界的哪里、它应该包含的什么图片等等。这其中很多信息可能还尚不存在，这要看你为你的游戏计划了多少，但是没有关系。正如直到[移动你的游戏角色](https://opensource.com/article/17/12/game-python-moving-player)那篇文章结束时，你都没有告诉你的玩家妖精移动速度有多快，你不必事先告诉 `Platform` 每一件事。

在这系列中你所写的脚本的开头附近，创建一个新的类。在这个代码示例中前三行是用于说明上下文，因此在注释的下面添加代码：

```
import pygame
import sys
import os
## 新代码如下：

class Platform(pygame.sprite.Sprite):
# x location, y location, img width, img height, img file
def __init__(self,xloc,yloc,imgw,imgh,img):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.image.load(os.path.join('images',img)).convert()
    self.image.convert_alpha()
    self.image.set_colorkey(ALPHA)
    self.rect = self.image.get_rect()
    self.rect.y = yloc
    self.rect.x = xloc
```

当被调用时，这个类在某个 X 和 Y 位置上创建一个屏上对象，具有某种宽度和高度，并使用某种图像作为纹理。这与如何在屏上绘制出玩家或敌人非常类似。

### 平台的类型

下一步是绘制出你的平台需要出现的地方。

#### 瓷砖方式

实现平台类游戏世界有几种不同的方法。在最初的横向滚轴游戏中，例如，马里奥超级兄弟和刺猬索尼克，这个技巧是使用“瓷砖”方式，也就是说有几个代表地面和各种平台的块，并且这些块被重复使用来制作一个关卡。你只能有 8 或 12 种不同的块，你可以将它们排列在屏幕上来创建地面、浮动的平台，以及你游戏中需要的一切其它的事物。有人发现这是制作游戏最容易的方法了，因为你只需要制作（或下载）一小组关卡素材就能创建很多不同的关卡。然而，这里的代码需要一点数学知识。

![Supertux, a tile-based video game](/data/attachment/album/201905/26/203707q14rerqrsjsaruuz.png "Supertux, a tile-based video game")

*[SuperTux](https://www.supertux.org/) ，一个基于瓷砖的电脑游戏。*

#### 手工绘制方式

另一种方法是将每个素材作为一个整体图像。如果你喜欢为游戏世界创建素材，那你会在用图形应用程序构建游戏世界的每个部分上花费很多时间。这种方法不需要太多的数学知识，因为所有的平台都是整体的、完整的对象，你只需要告诉 [Python](https://www.python.org/) 将它们放在屏幕上的什么位置。

每种方法都有优势和劣势，并且根据于你选择使用的方式，代码稍有不同。我将覆盖这两方面，所以你可以在你的工程中使用一种或另一种，甚至两者的混合。

### 关卡绘制

总的来说，绘制你的游戏世界是关卡设计和游戏编程中的一个重要的部分。这需要数学知识，但是没有什么太难的，而且 Python 擅长数学，它会有所帮助。

你也许发现先在纸张上设计是有用的。拿一张表格纸，并绘制一个方框来代表你的游戏窗体。在方框中绘制平台，并标记其每一个平台的 X 和 Y 坐标，以及它的宽度和高度。在方框中的实际位置没有必要是精确的，你只要保持数字合理即可。譬如，假设你的屏幕是 720 像素宽，那么你不能在一个屏幕上放 8 块 100 像素的平台。

当然，不是你游戏中的所有平台都必须容纳在一个屏幕大小的方框里，因为你的游戏将随着你的玩家行走而滚动。所以，可以继续绘制你的游戏世界到第一屏幕的右侧，直到关卡结束。

如果你更喜欢精确一点，你可以使用方格纸。当设计一个瓷砖类的游戏时，这是特别有用的，因为每个方格可以代表一个瓷砖。

![Example of a level map](/data/attachment/album/201905/26/203708r1rke761pdkp7dpd.png "Example of a level map")

*一个关卡地图示例。*

#### 坐标系

你可能已经在学校中学习过[笛卡尔坐标系](https://en.wikipedia.org/wiki/Cartesian_coordinate_system)。你学习的东西也适用于 Pygame，除了在 Pygame 中你的游戏世界的坐标系的原点 `0,0` 是放置在你的屏幕的左上角而不是在中间，是你在地理课上用过的坐标是在中间的。

![Example of coordinates in Pygame](/data/attachment/album/201905/26/203709tkfkbkonzvevsv1f.png "Example of coordinates in Pygame")

*在 Pygame 中的坐标示例。*

X 轴起始于最左边的 0，向右无限增加。Y 轴起始于屏幕顶部的 0，向下延伸。

#### 图片大小

如果你不知道你的玩家、敌人、平台是多大的，绘制出一个游戏世界是毫无意义的。你可以在图形程序中找到你的平台或瓷砖的尺寸。例如在 [Krita](https://krita.org/en/) 中，单击“图像”菜单，并选择“属性”。你可以在“属性”窗口的最顶部处找到它的尺寸。

另外，你也可以创建一个简单的 Python 脚本来告诉你的一个图像的尺寸。打开一个新的文本文件，并输入这些代码到其中：

```
#!/usr/bin/env python3

from PIL import Image
import os.path
import sys

if len(sys.argv) > 1:
    print(sys.argv[1])
else:
    sys.exit('Syntax: identify.py [filename]')

pic = sys.argv[1]
dim = Image.open(pic)
X   = dim.size[0]
Y   = dim.size[1]

print(X,Y)
```

保存该文本文件为 `identify.py`。

要使用这个脚本，你必须安装一些额外的 Python 模块，它们包含了这个脚本中新使用的关键字：

```
$ pip3 install Pillow --user
```

一旦安装好，在你游戏工程目录中运行这个脚本：

```
$ python3 ./identify.py images/ground.png
(1080, 97)
```

在这个示例中，地面平台的图形的大小是 1080 像素宽和 97 像素高。

### 平台块

如果你选择单独地绘制每个素材，你必须创建想要插入到你的游戏世界中的几个平台和其它元素，每个素材都放在它自己的文件中。换句话说，你应该让每个素材都有一个文件，像这样：

![One image file per object](/data/attachment/album/201905/26/203710p2xtt26t664t6a42.png "One image file per object")

*每个对象一个图形文件。*

你可以按照你希望的次数重复使用每个平台，只要确保每个文件仅包含一个平台。你不能使用一个文件包含全部素材，像这样：

![Your level cannot be one image file](/data/attachment/album/201905/26/203710ymiai7a6i6lugmab.png "Your level cannot be one image file")

*你的关卡不能是一个图形文件。*

当你完成时，你可能希望你的游戏看起来像这样，但是如果你在一个大文件中创建你的关卡，你就没有方法从背景中区分出一个平台，因此，要么把对象绘制在它们自己的文件中，要么从一个更大的文件中裁剪出它们，并保存为单独的副本。

**注意：** 如同你的其它素材，你可以使用 [GIMP](https://www.gimp.org/)、Krita、[MyPaint](http://mypaint.org/about/)，或 [Inkscape](https://inkscape.org/en/) 来创建你的游戏素材。

平台出现在每个关卡开始的屏幕上，因此你必须在你的 `Level` 类中添加一个 `platform` 函数。在这里特例是地面平台，它重要到应该拥有它自己的一个组。通过把地面看作一组特殊类型的平台，你可以选择它是否滚动，或它上面是否可以站立，而其它平台可以漂浮在它上面。这取决于你。

添加这两个函数到你的 `Level` 类：

```
def ground(lvl,x,y,w,h):
    ground_list = pygame.sprite.Group()
    if lvl == 1:
        ground = Platform(x,y,w,h,'block-ground.png')
        ground_list.add(ground)

    if lvl == 2:
        print("Level " + str(lvl) )

    return ground_list

def platform( lvl ):
    plat_list = pygame.sprite.Group()
    if lvl == 1:
        plat = Platform(200, worldy-97-128, 285,67,'block-big.png')
        plat_list.add(plat)
        plat = Platform(500, worldy-97-320, 197,54,'block-small.png')
        plat_list.add(plat)
    if lvl == 2:
        print("Level " + str(lvl) )

    return plat_list
```

`ground` 函数需要一个 X 和 Y 位置，以便 Pygame 知道在哪里放置地面平台。它也需要知道平台的宽度和高度，这样 Pygame 知道地面延伸到每个方向有多远。该函数使用你的 `Platform` 类来生成一个屏上对象，然后将这个对象添加到 `ground_list` 组。

`platform` 函数本质上是相同的，除了其有更多的平台。在这个示例中，仅有两个平台，但是你可以想有多少就有多少。在进入一个平台后，在列出另一个前你必须添加它到 `plat_list` 中。如果你不添加平台到组中，那么它将不出现在你的游戏中。

>
> **提示：** 很难想象你的游戏世界的 0 是在顶部，因为在真实世界中发生的情况是相反的；当估计你有多高时，你不会从上往下测量你自己，而是从脚到头顶来测量。
>
>
> 如果对你来说从“地面”上来构建你的游戏世界更容易，将 Y 轴值表示为负数可能有帮助。例如，你知道你的游戏世界的底部是 `worldy` 的值。因此 `worldy` 减去地面的高度（在这个示例中是 97）是你的玩家正常站立的位置。如果你的角色是 64 像素高，那么地面减去 128 正好是你的玩家的两倍高。事实上，一个放置在 128 像素处平台大约是相对于你的玩家的两层楼高度。一个平台在 -320 处比三层楼更高。等等。
>
>
>

正像你现在可能所知的，如果你不使用它们，你的类和函数是没有价值的。添加这些代码到你的设置部分（第一行只是上下文，所以添加最后两行）：

```
enemy_list  = Level.bad( 1, eloc )
ground_list = Level.ground( 1,0,worldy-97,1080,97 )
plat_list   = Level.platform( 1 )
```

并把这些行加到你的主循环（再一次，第一行仅用于上下文）：

```
enemy_list.draw(world)  # 刷新敌人
ground_list.draw(world)  # 刷新地面
plat_list.draw(world)  # 刷新平台
```

### 瓷砖平台

瓷砖类游戏世界更容易制作，因为你只需要在前面绘制一些块，就能在游戏中一再使用它们创建每个平台。在像 [OpenGameArt.org](https://opengameart.org/content/simplified-platformer-pack) 这样的网站上甚至有一套瓷砖供你来使用。

`Platform` 类与在前面部分中的类是相同的。

`ground` 和 `platform` 在 `Level` 类中，然而，必须使用循环来计算使用多少块来创建每个平台。

如果你打算在你的游戏世界中有一个坚固的地面，这种地面是很简单的。你只需要从整个窗口的一边到另一边“克隆”你的地面瓷砖。例如，你可以创建一个 X 和 Y 值的列表来规定每个瓷砖应该放置的位置，然后使用一个循环来获取每个值并绘制每一个瓷砖。这仅是一个示例，所以不要添加这到你的代码：

```
# Do not add this to your code
gloc = [0,656,64,656,128,656,192,656,256,656,320,656,384,656]
```

不过，如果你仔细看，你可以看到所有的 Y 值是相同的，X 值以 64 的增量不断地增加 —— 这就是瓷砖的大小。这种重复是精确地，是计算机擅长的，因此你可以使用一点数学逻辑来让计算机为你做所有的计算：

添加这些到你的脚本的设置部分：

```
gloc = []
tx   = 64
ty   = 64

i=0
while i <= (worldx/tx)+tx:
    gloc.append(i*tx)
    i=i+1

ground_list = Level.ground( 1,gloc,tx,ty )
```

现在，不管你的窗口的大小，Python 会通过瓷砖的宽度分割游戏世界的宽度，并创建一个数组列表列出每个 X 值。这里不计算 Y 值，因为在平的地面上这个从不会变化。

为了在一个函数中使用数组，使用一个 `while` 循环，查看每个条目并在适当的位置添加一个地面瓷砖：

```
def ground(lvl,gloc,tx,ty):
    ground_list = pygame.sprite.Group()
    i=0
    if lvl == 1:
        while i < len(gloc):
            ground = Platform(gloc[i],worldy-ty,tx,ty,'tile-ground.png')
            ground_list.add(ground)
            i=i+1

    if lvl == 2:
        print("Level " + str(lvl) )

    return ground_list
```

除了 `while` 循环，这几乎与在上面一部分中提供的瓷砖类平台的 `ground` 函数的代码相同。

对于移动的平台，原理是相似的，但是这里有一些技巧可以使它简单。

你可以通过它的起始像素（它的 X 值）、距地面的高度（它的 Y 值）、绘制多少瓷砖来定义一个平台，而不是通过像素绘制每个平台。这样，你不必操心每个平台的宽度和高度。

这个技巧的逻辑有一点复杂，因此请仔细复制这些代码。有一个 `while` 循环嵌套在另一个 `while` 循环的内部，因为这个函数必须考虑每个数组项的三个值来成功地建造一个完整的平台。在这个示例中，这里仅有三个平台以 `ploc.append` 语句定义，但是你的游戏可能需要更多，因此你需要多少就定义多少。当然，有一些不会出现，因为它们远在屏幕外，但是一旦当你进行滚动时，它们将呈现在眼前。

```
def platform(lvl,tx,ty):
    plat_list = pygame.sprite.Group()
    ploc = []
    i=0
    if lvl == 1:
        ploc.append((200,worldy-ty-128,3))
        ploc.append((300,worldy-ty-256,3))
        ploc.append((500,worldy-ty-128,4))
        while i < len(ploc):
            j=0
            while j <= ploc[i][2]:
                plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'tile.png')
                plat_list.add(plat)
                j=j+1
            print('run' + str(i) + str(ploc[i]))
            i=i+1

    if lvl == 2:
        print("Level " + str(lvl) )

    return plat_list
```

要让这些平台出现在你的游戏世界，它们必须出现在你的主循环中。如果你还没有这样做，添加这些行到你的主循环（再一次，第一行仅被用于上下文）中：

```
        enemy_list.draw(world)  # 刷新敌人
        ground_list.draw(world) # 刷新地面
        plat_list.draw(world)   # 刷新平台
```

启动你的游戏，根据需要调整你的平台的放置位置。如果你看不见屏幕外产生的平台，不要担心；你不久后就可以修复它。

到目前为止，这是游戏的图片和代码：

![Pygame game](/data/attachment/album/201905/26/203711c58sbz95ybpnxpdg.jpg "Pygame game")

*到目前为止，我们的 Pygame 平台。*

```
#!/usr/bin/env python3
# draw a world
# add a player and player control
# add player movement
# add enemy and basic collision
# add platform

# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.  This file is offered as-is,
# without any warranty.

import pygame
import sys
import os

'''
Objects
'''

class Platform(pygame.sprite.Sprite):
    # x location, y location, img width, img height, img file
    def __init__(self,xloc,yloc,imgw,imgh,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img)).convert()
        self.image.convert_alpha()
        self.rect = self.image.get_rect()
        self.rect.y = yloc
        self.rect.x = xloc

class Player(pygame.sprite.Sprite):
    '''
    Spawn a player
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.health = 10
        self.score = 1
        self.images = []
        for i in range(1,9):
            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            img.convert_alpha()
            img.set_colorkey(ALPHA)
            self.images.append(img)
            self.image = self.images[0]
            self.rect  = self.image.get_rect()

    def control(self,x,y):
        '''
        control player movement
        '''
        self.movex += x
        self.movey += y

    def update(self):
        '''
        Update sprite position
        '''

        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # moving left
        if self.movex < 0:
            self.frame += 1
            if self.frame > ani*3:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # moving right
        if self.movex > 0:
            self.frame += 1
            if self.frame > ani*3:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]

        # collisions
        enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        for enemy in enemy_hit_list:
            self.health -= 1
            print(self.health)

        ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
        for g in ground_hit_list:
            self.health -= 1
            print(self.health)

class Enemy(pygame.sprite.Sprite):
    '''
    Spawn an enemy
    '''
    def __init__(self,x,y,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img))
        #self.image.convert_alpha()
        #self.image.set_colorkey(ALPHA)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.counter = 0

    def move(self):
        '''
        enemy movement
        '''
        distance = 80
        speed = 8

        if self.counter >= 0 and self.counter <= distance:
            self.rect.x += speed
        elif self.counter >= distance and self.counter <= distance*2:
            self.rect.x -= speed
        else:
            self.counter = 0

        self.counter += 1

class Level():
    def bad(lvl,eloc):
        if lvl == 1:
            enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy
            enemy_list = pygame.sprite.Group() # create enemy group
            enemy_list.add(enemy)              # add enemy to group

        if lvl == 2:
            print("Level " + str(lvl) )

        return enemy_list

    def loot(lvl,lloc):
        print(lvl)

    def ground(lvl,gloc,tx,ty):
        ground_list = pygame.sprite.Group()
        i=0
        if lvl == 1:
            while i < len(gloc):
                print("blockgen:" + str(i))
                ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png')
                ground_list.add(ground)
                i=i+1

        if lvl == 2:
            print("Level " + str(lvl) )

        return ground_list

'''
Setup
'''
worldx = 960
worldy = 720

fps = 40 # frame rate
ani = 4  # animation cycles
clock = pygame.time.Clock()
pygame.init()
main = True

BLUE  = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
ALPHA = (0,255,0)

world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player() # spawn player
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10 # how fast to move

eloc = []
eloc = [200,20]
gloc = []
#gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630]
tx = 64 #tile size
ty = 64 #tile size

i=0
while i <= (worldx/tx)+tx:
    gloc.append(i*tx)
    i=i+1
    print("block: " + str(i))

enemy_list = Level.bad( 1, eloc )
ground_list = Level.ground( 1,gloc,tx,ty )

'''
Main loop
'''
while main == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

#    world.fill(BLACK)
    world.blit(backdrop, backdropbox)
    player.update()
    player_list.draw(world) #refresh player position
    enemy_list.draw(world)  # refresh enemies
    ground_list.draw(world)  # refresh enemies
    for e in enemy_list:
        e.move()
    pygame.display.flip()
    clock.tick(fps)
```

（LCTT 译注：到本文翻译完为止，该系列已经近一年没有继续更新了~）

---

via: <https://opensource.com/article/18/7/put-platforms-python-game>

作者：[Seth Kenlon](https://opensource.com/users/seth) 选题：[lujun9972](https://github.com/lujun9972) 译者：[robsean](https://github.com/robsean) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


3 款在 Fedora 中管理个人财务的应用

![](/data/attachment/album/201905/26/233631jcwvzmo8covq10i4.jpg)

网上有很多可以用来管理你个人财务的服务。虽然它们可能很方便，但这通常也意味着将你最宝贵的个人数据放在你无法监控的公司。也有些人对这些不太在意。

无论你是否在意，你可能会对你自己系统上的应用感兴趣。这意味着如果你不想，你的数据永远不会离开自己的计算机。这三款之一可能就是你想找的。

### HomeBank

HomeBank 是一款可以管理多个账户的全功能软件。它很容易设置并保持更新。它有多种方式画出你的分类和负债，以便你可以看到资金流向何处。它可以通过官方 Fedora 仓库下载。

![A simple account set up in HomeBank with a few transactions.](/data/attachment/album/201905/26/233632v7uf3qqr53f4fu4e.png)

要安装 HomeBank，请打开“软件中心”，搜索 “HomeBank”，然后选择该应用。单击“安装”将其添加到你的系统中。HomeBank 也可以通过 Flatpak 安装。

### KMyMoney

KMyMoney 是一个成熟的应用，它已经存在了很长一段时间。它有一系列稳定的功能，可帮助你管理多个帐户，包括资产、负债、税收等。KMyMoney 包含一整套用于管理投资和进行预测的工具。它还提供大量报告，以了解你的资金运作方式。

![A subset of the many reports available in KMyMoney.](/data/attachment/album/201905/26/233633fgnzh4fhntnigrts.png)

要安装它，请使用软件中心，或使用命令行：

```
$ sudo dnf install kmymoney
```

### GnuCash

用于个人财务的最受欢迎的免费 GUI 应用之一是 GnuCash。GnuCash 不仅可以用于个人财务。它还有管理企业收入、资产和负债的功能。这并不意味着你不能用它来管理自己的账户。从查看[在线教程和指南](https://www.gnucash.org/viewdoc.phtml?rev=3&lang=C&doc=guide)开始了解。

![Checking account records shown in GnuCash.](/data/attachment/album/201905/26/233634ao6mubdkbx9abo8c.png)

打开“软件中心”，搜索 “GnuCash”，然后选择应用。单击“安装”将其添加到你的系统中。或者如上所述使用 `dnf install` 来安装 “gnucash” 包。

它现在可以通过 Flathub 安装，这使得安装变得简单。如果你没有安装 Flathub，请查看 [Fedora Magazine 上的这篇文章](https://fedoramagazine.org/install-flathub-apps-fedora/)了解如何使用它。这样你也可以在终端使用 `flatpak install gnucash` 命令。

---

照片由 [Fabian Blank](https://unsplash.com/photos/pElSkGRA2NU?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 拍摄，发布在 [Unsplash](https://unsplash.com/search/photos/money?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 上。

---

via: <https://fedoramagazine.org/3-apps-to-manage-personal-finances-in-fedora/>

作者：[Paul W. Frields](https://fedoramagazine.org/author/pfrields/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


用 GHTorrent 和 Libraries.io 查询 10 年的 GitHub 数据

>
> 有一种方法可以在没有任何本地基础设施的情况下使用开源数据集探索 GitHub 数据。
>
>
>

![](/data/attachment/album/201905/27/220200jlzrlz333vkfl8ok.jpg)

我一直在寻找新的数据集，以用它们来展示我们团队工作的力量。[CHAOSSEARCH](https://chaossearch.io/) 可以将你的 [Amazon S3](https://aws.amazon.com/s3/) 对象存储数据转换为完全可搜索的 [Elasticsearch](https://www.elastic.co/) 式集群。使用 Elasticsearch API 或 [Kibana](https://www.elastic.co/products/kibana) 等工具，你可以查询你所要找的任何数据。

当我找到 [GHTorrent](http://ghtorrent.org) 项目进行探索时，我很兴奋。GHTorrent 旨在通过 GitHub API 构建所有可用数据的离线版本。如果你喜欢数据集，这是一个值得一看的项目，甚至你可以考虑[捐赠一个 GitHub API 密钥](http://ghtorrent.org/services.html)。

### 访问 GHTorrent 数据

有许多方法可以访问和使用 [GHTorrent 的数据](http://ghtorrent.org/downloads.html)，它以 [NDJSON](http://ndjson.org) 格式提供。这个项目可以以多种形式提供数据，包括用于恢复到 [MySQL](https://en.wikipedia.org/wiki/MySQL) 数据库的 [CSV](https://en.wikipedia.org/wiki/Comma-separated_values)，可以转储所有对象的 [MongoDB](https://www.mongodb.com/)，以及用于将数据直接导出到 Google 对象存储中的 Google Big Query（免费）。 有一点需要注意：这个数据集有从 2008 年到 2017 年几乎完整的数据集，但从 2017 年到现在的数据还不完整。这将影响我们确定性查询的能力，但它仍然是一个令人兴奋的信息量。

我选择 Google Big Query 来避免自己运行任何数据库，那么我就可以很快下载包括用户和项目在内的完整数据库。CHAOSSEARCH 可以原生分析 NDJSON 格式，因此在将数据上传到 Amazon S3 之后，我能够在几分钟内对其进行索引。CHAOSSEARCH 平台不要求用户设置索引模式或定义其数据的映射，它可以发现所有字段本身（字符串、整数等）。

随着我的数据完全索引并准备好进行搜索和聚合，我想深入了解看看我们可以发现什么，比如哪些软件语言是 GitHub 项目最受欢迎的。

（关于格式化的说明：下面这是一个有效的 JSON 查询，我们不会在这里正确格式化以避免滚动疲劳。要正确格式化它，你可以在本地复制它并发送到命令行实用程序，如 [jq](https://stedolan.github.io/jq/)。）

```
{"aggs":{"2":{"date_histogram":{"field":"root.created_at","interval":"1M","time_zone":"America/New_York","min_doc_count":1}}},"size":0,"_source":{"excludes":[]},"stored_fields":["*"],"script_fields":{},"docvalue_fields":["root.created_at","root.updated_at"],"query":{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[{"match_phrase":{"root.language":{"query":""}}}]}}}
```

对于那些近年来跟踪开源语言状态的人来说，这个结果并不令人惊讶。

![Which software languages are the most popular on GitHub.](/data/attachment/album/201905/27/220219dyiqsuiu9yo7kkyi.png "Which software languages are the most popular on GitHub.")

[JavaScript](https://en.wikipedia.org/wiki/JavaScript) 仍然是卫冕冠军，虽然有些人认为 JavaScript 正在逐渐消失，但它仍然是 800 磅重的大猩猩，很可能会保持这种状态一段时间。[Java](/resources/java) 面临类似的谣言，但这些数据表明它是开源生态系统的重要组成部分。

考虑到像 [Docker](/resources/what-docker) 和 [Kubernetes](/resources/what-is-kubernetes) 这样的项目的流行，你可能会想，“Go（[Golang](https://golang.org/)）怎么样？”这是一个提醒的好时机，这里讨论的 GitHub 数据集包含一些空缺，最明显的是在 2017 年之后我看到 Golang 项目随处可见，而这里并没有显示。我希望用完整的 GitHub 数据集重复此搜索，看看它是否会改变排名。

现在让我们来探讨项目创建的速度。 （提醒：这是为了便于阅读而合并的有效 JSON。）

```
{"aggs":{"2":{"date_histogram":{"field":"root.created_at","interval":"1M","time_zone":"America/New_York","min_doc_count":1}}},"size":0,"_source":{"excludes":[]},"stored_fields":["*"],"script_fields":{},"docvalue_fields":["root.created_at","root.updated_at"],"query":{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[{"match_phrase":{"root.language":{"query":""}}}]}}}
```

我们可以看到创建新项目的速度，也会给人留下深刻的印象，从 2012 年左右开始大幅增长：

![The rate at which new projects are created on GitHub.](/data/attachment/album/201905/27/220222k7kvplrm8ph6824u.png "The rate at which new projects are created on GitHub.")

既然我知道了创建项目的速度以及用于创建这些项目的最流行的语言，我还想知道这些项目选择的开源许可证。遗憾的是，这个 GitHub 项目数据集中并不存在这些数据，但是 [Tidelift](https://tidelift.com) 的精彩团队在 [Libraries.io](http://libraries.io/) [数据](https://libraries.io/data) 里发布了一个 GitHub 项目的详细列表，包括使用的许可证以及其中有关开源软件状态的其他详细信息。将此数据集导入 CHAOSSEARCH 只花了几分钟，让我看看哪些开源软件许可证在 GitHub 上最受欢迎：

（提醒：这是为了便于阅读而合并的有效 JSON。）

```
{"aggs":{"2":{"terms":{"field":"Repository License","size":10,"order":{"_count":"desc"}}}},"size":0,"_source":{"excludes":[]},"stored_fields":["*"],"script_fields":{},"docvalue_fields":["Created Timestamp","Last synced Timestamp","Latest Release Publish Timestamp","Updated Timestamp"],"query":{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[{"match_phrase":{"Repository License":{"query":""}}}]}}}
```

结果显示了一些重要的异常值：

![Which open source software licenses are the most popular on GitHub.](/data/attachment/album/201905/27/220223p04n9o4y84hdw9x4.png "Which open source software licenses are the most popular on GitHub.")

如你所见，[MIT 许可证](https://opensource.org/licenses/MIT) 和 [Apache 2.0 许可证](https://opensource.org/licenses/Apache-2.0) 的开源项目远远超过了其他大多数开源许可证，而 [各种 BSD 和 GPL 许可证](https://opensource.org/licenses) 则差得很远。鉴于 GitHub 的开放模式，我不能说我对这些结果感到惊讶。我猜想是用户（而不是公司）创建了大多数项目，并且他们使用 MIT 许可证可以使其他人轻松地使用、共享和贡献。而鉴于有不少公司希望确保其商标得到尊重并为其业务提供开源组件，那么 Apache 2.0 许可证数量高企的背后也是有道理的。

现在我确定了最受欢迎的许可证，我很想看看最少使用的许可证。通过调整我的上一个查询，我将前 10 名逆转为最后 10 名，并且只找到了两个使用 [伊利诺伊大学 - NCSA 开源许可证](https://tldrlegal.com/license/university-of-illinois---ncsa-open-source-license-(ncsa)) 的项目。我之前从未听说过这个许可证，但它与 Apache 2.0 非常接近。看到所有 GitHub 项目中使用了多少个不同的软件许可证，这很有意思。

![The University of Illinois/NCSA open source license.](/data/attachment/album/201905/27/220225egj3p4etptevv4je.png "The University of Illinois/NCSA open source license.")

之后，我针对特定语言（JavaScript）来查看最常用的许可证。（提醒：这是为了便于阅读而合并的有效JSON。）

```
{"aggs":{"2":{"terms":{"field":"Repository License","size":10,"order":{"_count":"desc"}}}},"size":0,"_source":{"excludes":[]},"stored_fields":["*"],"script_fields":{},"docvalue_fields":["Created Timestamp","Last synced Timestamp","Latest Release Publish Timestamp","Updated Timestamp"],"query":{"bool":{"must":[{"match_phrase":{"Repository Language":{"query":"JavaScript"}}}],"filter":[{"match_all":{}}],"should":[],"must_not":[{"match_phrase":{"Repository License":{"query":""}}}]}}}
```

这个输出有一些意外。

![The most popular open source licenses used for GitHub JavaScript projects.](/data/attachment/album/201905/27/220228g0y90gnafsa8on03.png "The most popular open source licenses used for GitHub JavaScript projects.")

尽管使用 `npm init` 创建的 [NPM](https://www.npmjs.com/) 模块的默认许可证是来自 [Internet Systems Consortium（ISC）](https://en.wikipedia.org/wiki/ISC_license) 的许可证，但你可以看到相当多的这些项目使用 MIT 以及 Apache 2.0 的开源许可证。

由于 Libraries.io 数据集中包含丰富的开源项目内容，并且由于 GHTorrent 数据缺少最近几年的数据（因此缺少有关 Golang 项目的任何细节），因此我决定运行类似的查询来查看 Golang 项目是如何许可他们的代码的。

（提醒：这是为了便于阅读而合并的有效 JSON。）

```
{"aggs":{"2":{"terms":{"field":"Repository License","size":10,"order":{"_count":"desc"}}}},"size":0,"_source":{"excludes":[]},"stored_fields":["*"],"script_fields":{},"docvalue_fields":["Created Timestamp","Last synced Timestamp","Latest Release Publish Timestamp","Updated Timestamp"],"query":{"bool":{"must":[{"match_phrase":{"Repository Language":{"query":"Go"}}}],"filter":[{"match_all":{}}],"should":[],"must_not":[{"match_phrase":{"Repository License":{"query":""}}}]}}}
```

结果与 Javascript 完全不同。

![How Golang projects license their GitHub code.](/data/attachment/album/201905/27/220230wddcbywhw4ct4zch.png "How Golang projects license their GitHub code.")

Golang 项目与 JavaScript 项目惊人逆转 —— 使用 Apache 2.0 的 Golang 项目几乎是 MIT 许可证的三倍。虽然很难准确地解释为什么会出现这种情况，但在过去的几年中，Golang 已经出现了大规模的增长，特别是在开源和商业化的项目和软件产品公司中。

正如我们上面所了解的，这些公司中的许多公司都希望强制执行其商标策略，因此转向 Apache 2.0 许可证是有道理的。

#### 总结

最后，我通过深入了解 GitHub 用户和项目的数据找到了一些有趣的结果。其中一些我肯定会猜到，但是一些结果对我来说也是惊喜，特别是像很少使用的 NCSA 许可证这样的异常值。

总而言之，你可以看到 CHAOSSEARCH 平台能够快速轻松地找到有趣问题的复杂答案。我无需自己运行任何数据库就可以深入研究这个数据集，甚至可以在 Amazon S3 上以低成本的方式存储数据，因此无需维护。 现在，我可以随时查询有关这些数据的任何其他问题。

你对数据提出了哪些其他问题，以及你使用了哪些数据集？请在评论或推特上告诉我 [@petecheslock](https://twitter.com/petecheslock)。

本文的一个版本最初发布在 [CHAOSSEARCH](https://chaossearch.io/blog/where-are-the-github-users-part-1/)，有更多结果可供发现。

---

via: <https://opensource.com/article/19/5/chaossearch-github-ghtorrent>

作者：[Pete Cheslock](https://opensource.com/users/petecheslock/users/ghaff/users/payalsingh/users/davidmstokes) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


systemd 代码已超 120 万行！五年间翻了一番

Summary: 五年前， Phoronix 发现 systemd 源码树已经接近 55 万行，所以好奇之下，让我们来看看今天的 systemd Git 存储库有多大：现在已超过 120 万行了！

![](/data/attachment/album/201905/27/224013jnmi7m6n0gen2egd.jpg)

五年前， Phoronix 发现 [systemd 源码树已经接近 55 万行](https://www.phoronix.com/scan.php?page=news_item&px=MTY5NjM)，所以好奇之下，让我们来看看今天的 systemd Git 存储库有多大：现在已超过 120 万行了！

![](/data/attachment/album/201905/27/224054thtydrvilvvrr8ur.png)

*systemd 代码行数*

在 2017 年超过 [systemd 的代码超过了 100 万行](https://www.phoronix.com/scan.php?page=news_item&px=systemd-2017-Git-Activity)之后，如今在 systemd 的 Git 存储库上运行 GitStats 时，发现它已经有 1,207,302 行了。这 120 万行分布在 3,260 个文件中，来自近 1,400 个不同作者的 40,057 个提交。

![](/data/attachment/album/201905/27/224054ieag7ygsbe5ykykg.png)

*systemd 提交数*

去年，systemd 出现了创纪录的提交数量，但到目前为止，2019 年恐怕很难再次看到突破该记录。到目前为止，今年已有 2,145 个提交，而去年有 6,245 个提交，而 2016 年和 2017 年每年的提交总数不到 4 千个。

Lennart Poettering 依旧是 systemd 最多产的贡献者，今年迄今为止他贡献了超过 32％ 的提交。今年其他多产贡献者包括 Yu Watanabe、ZbigniewJędrzejewski-Szmek、Frantisek Sumsal、Susant Sahani 和 Evgeny Vereshchagin 等人。到目前为止，已有大约 142 人为 systemd 的源代码树做出了贡献。

对于那些对其他系统统计数据感兴趣的人，请参阅[最新的 GitStats 输出](https://phoronix.com/misc/systemd-201905/index.html)。


使用 k3s 在 Fedora IoT 上运行 K8S

Summary: 一个“正常”安装的 Kubernetes（如果有这么一说的话）对于物联网来说有点沉重。K8s 的推荐内存配置，是每台机器 2GB！不过，我们也有一些替代品，其中一个新人是 k3s —— 一个轻量级的 Kubernetes 发行版。

![](/data/attachment/album/201905/28/094048yrzlik9oek5rbs5s.jpg)

Fedora IoT 是一个即将发布的、面向物联网的 Fedora 版本。去年 Fedora Magazine 的《[如何使用 Fedora IoT 点亮 LED 灯](/article-10380-1.html)》一文第一次介绍了它。从那以后，它与 Fedora Silverblue 一起不断改进，以提供针对面向容器的工作流的不可变基础操作系统。

Kubernetes 是一个颇受欢迎的容器编排系统。它可能最常用在那些能够处理巨大负载的强劲硬件上。不过，它也能在像树莓派 3 这样轻量级的设备上运行。让我们继续阅读，来了解如何运行它。

### 为什么用 Kubernetes？

虽然 Kubernetes 在云计算领域风靡一时，但让它在小型单板机上运行可能并不是常见的。不过，我们有非常明确的理由来做这件事。首先，这是一个不需要昂贵硬件就可以学习并熟悉 Kubernetes 的好方法；其次，由于它的流行性，市面上有[大量应用](https://hub.helm.sh/)进行了预先打包，以用于在 Kubernetes 集群中运行。更不用说，当你遇到问题时，会有大规模的社区用户为你提供帮助。

最后但同样重要的是，即使是在家庭实验室这样的小规模环境中，容器编排也确实能够使事情变得更加简单。虽然在学习曲线方面，这一点并不明显，但这些技能在你将来与任何集群打交道的时候都会有帮助。不管你面对的是一个单节点树莓派集群，还是一个大规模的机器学习场，它们的操作方式都是类似的。

#### K3s - 轻量级的 Kubernetes

一个“正常”安装的 Kubernetes（如果有这么一说的话）对于物联网来说有点沉重。K8s 的推荐内存配置，是每台机器 2GB！不过，我们也有一些替代品，其中一个新人是 [k3s](https://k3s.io) —— 一个轻量级的 Kubernetes 发行版。

K3s 非常特殊，因为它将 etcd 替换成了 SQLite 以满足键值存储需求。还有一点，在于整个 k3s 将使用一个二进制文件分发，而不是每个组件一个。这减少了内存占用并简化了安装过程。基于上述原因，我们只需要 512MB 内存即可运行 k3s，极度适合小型单板电脑！

### 你需要的东西

1. Fedora IoT 运行在虚拟机或实体设备中运行的。在[这里](https://docs.fedoraproject.org/en-US/iot/getting-started/)可以看到优秀的入门指南。一台机器就足够了，不过两台可以用来测试向集群添加更多节点。
2. [配置防火墙](https://github.com/rancher/k3s#open-ports--network-security)，允许 6443 和 8372 端口的通信。或者，你也可以简单地运行 `systemctl stop firewalld` 来为这次实验关闭防火墙。

### 安装 k3s

安装 k3s 非常简单。直接运行安装脚本：

```
curl -sfL https://get.k3s.io | sh -
```

它会下载、安装并启动 k3s。安装完成后，运行以下命令来从服务器获取节点列表：

```
kubectl get nodes
```

需要注意的是，有几个选项可以通过环境变量传递给安装脚本。这些选项可以在[文档](https://github.com/rancher/k3s#systemd)中找到。当然，你也完全可以直接下载二进制文件来手动安装 k3s。

对于实验和学习来说，这样已经很棒了，不过单节点的集群也不能算一个集群。幸运的是，添加另一个节点并不比设置第一个节点要难。只需要向安装脚本传递两个环境变量，它就可以找到第一个节点，而不用运行 k3s 的服务器部分。

```
curl -sfL https://get.k3s.io | K3S_URL=https://example-url:6443 \
  K3S_TOKEN=XXX sh -
```

上面的 `example-url` 应被替换为第一个节点的 IP 地址，或一个完全限定域名。在该节点中，（用 XXX 表示的）令牌可以在 `/var/lib/rancher/k3s/server/node-token` 文件中找到。

### 部署一些容器

现在我们有了一个 Kubernetes 集群，我们可以真正做些什么呢？让我们从部署一个简单的 Web 服务器开始吧。

```
kubectl create deployment my-server --image nginx
```

这会从名为 `nginx` 的容器镜像中创建出一个名叫 `my-server` 的 [部署](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)（默认使用 docker hub 注册中心，以及 `latest` 标签）。

```
kubectl get pods
```

为了访问到 pod 中运行的 nginx 服务器，首先通过一个 [服务](https://kubernetes.io/docs/concepts/services-networking/service/) 来暴露该部署。以下命令将创建一个与该部署同名的服务。

```
kubectl expose deployment my-server --port 80
```

服务将作为一种负载均衡器和 Pod 的 DNS 记录来工作。比如，当运行第二个 Pod 时，我们只需指定 `my-server`（服务名称）就可以通过 `curl` 访问 nginx 服务器。有关如何操作，可以看下面的实例。

```
# 启动一个 pod，在里面以交互方式运行 bash
kubectl run debug --generator=run-pod/v1 --image=fedora -it -- bash
# 等待 bash 提示符出现
curl my-server
# 你可以看到“Welcome to nginx!”的输出页面
```

### Ingress 控制器及外部 IP

默认状态下，一个服务只能获得一个 ClusterIP（只能从集群内部访问），但你也可以通过把它的类型设置为 [LoadBalancer](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) 为该服务申请一个外部 IP。不过，并非所有应用都需要自己的 IP 地址。相反，通常可以通过基于 Host 请求头部或请求路径进行路由，从而使多个服务共享一个 IP 地址。你可以在 Kubernetes 使用 [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) 完成此操作，而这也是我们要做的。Ingress 也提供了额外的功能，比如无需配置应用即可对流量进行 TLS 加密。

Kubernetes 需要 Ingress 控制器来使 Ingress 资源工作，k3s 包含 [Traefik](https://traefik.io/) 正是出于此目的。它还包含了一个简单的服务负载均衡器，可以为集群中的服务提供外部 IP。这篇[文档](https://github.com/rancher/k3s/blob/master/README.md#service-load-balancer)描述了这种服务：

>
> k3s 包含一个使用可用主机端口的基础服务负载均衡器。比如，如果你尝试创建一个监听 80 端口的负载均衡器，它会尝试在集群中寻找一个 80 端口空闲的节点。如果没有可用端口，那么负载均衡器将保持在 Pending 状态。
>
>
> k3s README
>
>
>

Ingress 控制器已经通过这个负载均衡器暴露在外。你可以使用以下命令找到它正在使用的 IP 地址。

```
$ kubectl get svc --all-namespaces
NAMESPACE     NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
 default       kubernetes   ClusterIP      10.43.0.1               443/TCP                      33d
 default       my-server    ClusterIP      10.43.174.38            80/TCP                       30m
 kube-system   kube-dns     ClusterIP      10.43.0.10              53/UDP,53/TCP,9153/TCP       33d
 kube-system   traefik      LoadBalancer   10.43.145.104   10.0.0.8      80:31596/TCP,443:31539/TCP   33d
```

找到名为 `traefik` 的服务。在上面的例子中，我们感兴趣的 IP 是 10.0.0.8。

### 路由传入的请求

让我们创建一个 Ingress，使它通过基于 Host 头部的路由规则将请求路由至我们的服务器。这个例子中我们使用 [xip.io](http://xip.io/) 来避免必要的 DNS 记录配置工作。它的工作原理是将 IP 地址作为子域包含，以使用 `10.0.0.8.xip.io` 的任何子域来达到 IP `10.0.0.8`。换句话说，`my-server.10.0.0.8.xip.io` 被用于访问集群中的 Ingress 控制器。你现在就可以尝试（使用你自己的 IP，而不是 10.0.0.8）。如果没有 Ingress，你应该会访问到“默认后端”，只是一个写着“404 page not found”的页面。

我们可以使用以下 Ingress 让 Ingress 控制器将请求路由到我们的 Web 服务器的服务。

```
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-server
spec:
  rules:
    - host: my-server.10.0.0.8.xip.io
      http:
        paths:
          - path: /
            backend:
              serviceName: my-server
              servicePort: 80
```

将以上片段保存到 `my-ingress.yaml` 文件中，然后运行以下命令将其加入集群：

```
kubectl apply -f my-ingress.yaml
```

你现在应该能够在你选择的完全限定域名中访问到 nginx 的默认欢迎页面了。在我的例子中，它是 `my-server.10.0.0.8.xip.io`。Ingress 控制器会通过 Ingress 中包含的信息来路由请求。对 `my-server.10.0.0.8.xip.io` 的请求将被路由到 Ingress 中定义为 `backend` 的服务和端口（在本例中为 `my-server` 和 `80`）。

### 那么，物联网呢？

想象如下场景：你的家或农场周围有很多的设备。它是一个具有各种硬件功能、传感器和执行器的物联网设备的异构集合。也许某些设备拥有摄像头、天气或光线传感器。其它设备可能会被连接起来，用来控制通风、灯光、百叶窗或闪烁的 LED。

这种情况下，你想从所有传感器中收集数据，在最终使用它来制定决策和控制执行器之前，也可能会对其进行处理和分析。除此之外，你可能还想配置一个仪表盘来可视化那些正在发生的事情。那么 Kubernetes 如何帮助我们来管理这样的事情呢？我们怎么保证 Pod 在合适的设备上运行？

简单的答案就是“标签”。你可以根据功能来标记节点，如下所示：

```
kubectl label nodes <node-name> <label-key>=<label-value>
# 举例
kubectl label nodes node2 camera=available
```

一旦它们被打上标签，我们就可以轻松地使用 [nodeSelector](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/) 为你的工作负载选择合适的节点。拼图的最后一块：如果你想在*所有*合适的节点上运行 Pod，那应该使用 [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) 而不是部署。换句话说，应为每个使用唯一传感器的数据收集应用程序创建一个 DaemonSet，并使用 nodeSelector 确保它们仅在具有适当硬件的节点上运行。

服务发现功能允许 Pod 通过服务名称来寻找彼此，这项功能使得这类分布式系统的管理工作变得易如反掌。你不需要为应用配置 IP 地址或自定义端口，也不需要知道它们。相反，它们可以通过集群中的命名服务轻松找到彼此。

#### 充分利用空闲资源

随着集群的启动并运行，收集数据并控制灯光和气候，可能使你觉得你已经把它完成了。不过，集群中还有大量的计算资源可以用于其它项目。这才是 Kubernetes 真正出彩的地方。

你不必担心这些资源的确切位置，或者去计算是否有足够的内存来容纳额外的应用程序。这正是编排系统所解决的问题！你可以轻松地在集群中部署更多的应用，让 Kubernetes 来找出适合运行它们的位置（或是否适合运行它们）。

为什么不运行一个你自己的 [NextCloud](https://nextcloud.com/) 实例呢？或者运行 [gitea](https://gitea.io/en-us/)？你还可以为你所有的物联网容器设置一套 CI/CD 流水线。毕竟，如果你可以在集群中进行本地构建，为什么还要在主计算机上构建并交叉编译它们呢？

这里的要点是，Kubernetes 可以更容易地利用那些你可能浪费掉的“隐藏”资源。Kubernetes 根据可用资源和容错处理规则来调度 Pod，因此你也无需手动完成这些工作。但是，为了帮助 Kubernetes 做出合理的决定，你绝对应该为你的工作负载添加[资源请求](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/)配置。

### 总结

尽管 Kuberenetes 或一般的容器编排平台通常不会与物联网相关联，但在管理分布式系统时，使用一个编排系统肯定是有意义的。你不仅可以使用统一的方式来处理多样化和异构的设备，还可以简化它们的通信方式。此外，Kubernetes 还可以更好地对闲置资源加以利用。

容器技术使构建“随处运行”应用的想法成为可能。现在，Kubernetes 可以更轻松地来负责“随处”的部分。作为构建一切的不可变基础，我们使用 Fedora IoT。

---

via: <https://fedoramagazine.org/kubernetes-on-fedora-iot-with-k3s/>

作者：[Lennart Jern](https://fedoramagazine.org/author/lennartj/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[StdioA](https://github.com/StdioA) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


每日安全资讯：旧版高通 Wi-Fi 驱动会导致 Win10 2019 年五月更新包无法联网

微软最近承认了另一个有关 Windows 10 May 2019 更新的问题，但幸运的是，用户实际上可以很容易地解决它。具体表现在 Windows 10 2019 年 5 月更新或版本 1903 会因为旧的高通驱动程序而遭遇 Wi-Fi 连接丢失。微软没有提供问题驱动程序的详细信息，但表示已经解决问题并提供更新后的驱动程序，建议用户尽快获取它。

“由于过时的高通驱动程序，一些较旧的计算机可能会失去 Wi-Fi 连接。应该可以从您的设备制造商（OEM）获得更新的 Wi-Fi 驱动程序。”微软解释道。

“为了保护您的升级体验，我们已经禁止在设备上使用此高通驱动程序的用户升级到 Windows 10 版本1903，直到安装了更新的驱动程序。”

虽然用户可以选择使用 Media Creation Tool 手动安装May更新，但微软建议不要使用此方法，除非部署了新的驱动程序。

![](/data/attachment/album/201905/28/095857p5dso4olqsv3dvlo.jpg)

来源：cnBeta.COM

### 更多资讯

##### Android Q 设备重启不断：谷歌证实将尽快修复

如果你升级到 Android Q 第三个测试版的话，那么最近是不是被设备不断重启搞崩溃了，事实上也确实如此，因为有很多用户都遇到了类似的情况，大家吐槽谷歌的声音越来越大。不少用户发现，自己设备升级至 Android Q Beta 3 之后，设备市场会发生重启，而且重启的状态没有任何预警，让人猝不及防。

来源： 快科技

详情： <http://t.cn/AiKxGumo>

##### 人民日报追踪浏览器主页劫持：我的主页我做主

连日来，本报持续跟踪报道“浏览器主页劫持”现象，在网民中引发广泛共鸣。大家纷纷留言，吐露心声，揭露各种网络侵权行为，呼吁主管部门加强监管，督促相关企业严守底线，期盼进一步净化网络生态，实现“我的主页我做主”。

来源： 人民日报

详情： <http://t.cn/AiKxqj8W>

##### 欧盟当局查封比特币混币服务商 Bestmixer.io 网站

荷兰金融刑事调查局在涉及欧洲刑警组织和其他当局的联合行动中关闭了比特币混币服务商 Bestmixer.io 的网站。欧洲刑警组织在周三的一份声明中表示，继去年夏天开始相关调查之后，警方查封了 Bestmixer.io，称其为 “针对加密混币服务商的首次执法行动” 。

来源： cnBeta.COM

详情： <http://t.cn/AiKxqQWW>

##### Canva 1.39 亿用户数据泄露

自称 GnosticPlayers 的黑客声称窃取了澳大利亚网站 Canva 的 1.39 亿用户数据。Canva 是一个非常受欢迎的平面设计服务，Alexa 排名在 200 以内。黑客窃取的数据包括了用户名字、真名 、电邮地址、城市国家信息，其中 6100 万用户有哈希密码，其他用户的信息还有用于登录的 Google 令牌。

来源： solidot.org

详情： <http://t.cn/AiKxqrEp>

（信息来源于网络，安华金和搜集整理）


Adobe Lightroom 的三个开源替代品

谢谢了！Adobe 的Lightroom真的是养不起了！

来自云南昆明的 Microsoft Edge 18.19041|Windows 10 用户

>
> 摄影师们：在没有 Lightroom 套件的情况下，可以看看这些 RAW 图像处理器。
>
>
>

![](/data/attachment/album/201905/29/071049sbq3cu4ru6426qwc.jpg)

如今智能手机的摄像功能已经完备到多数人认为可以代替传统摄影了。虽然这在傻瓜相机的市场中是个事实，但是对于许多摄影爱好者和专业摄影师看来，一个高端单反相机所能带来的照片景深、清晰度以及真实质感是口袋中的智能手机无法与之相比的。

所有的这些功能在便利性上要付出一些很小的代价；就像传统的胶片相机中的反色负片，单反照相得到的 RAW 格式文件必须预先处理才能印刷或编辑；因此对于单反相机，照片的后期处理是无可替代的，并且 首选应用就是 Adobe Lightroom。但是由于 Adobe Lightroom 的昂贵价格、基于订阅的定价模式以及专有许可证都使更多人开始关注其开源替代品。

Lightroom 有两大主要功能：处理 RAW 格式的图片文件，以及数字资产管理系统（DAM） —— 通过标签、评星以及其他元数据信息来简单清晰地整理照片。

在这篇文章中，我们将介绍三个开源的图片处理软件：Darktable、LightZone 以及 RawTherapee。所有的软件都有 DAM 系统，但没有任何一个具有 Lightroom 基于机器学习的图像分类和标签功能。如果你想要知道更多关于开源的 DAM 系统的软件，可以看 Terry Hacock 的文章：“[开源项目的 DAM 管理](https://opensource.com/article/18/3/movie-open-source-software)”，他分享了他在自己的 [Lunatics!](http://lunatics.tv/) 电影项目研究过的开源多媒体软件。

### Darktable

![Darktable](/data/attachment/album/201905/29/071050vapmrz3cn6m4t6pg.jpg)

类似其他两个软件，Darktable 可以处理 RAW 格式的图像并将它们转换成可用的文件格式 —— JPEG、PNG、TIFF、PPM、PFM 和 EXR，它同时支持 Google 和 Facebook 的在线相册，上传至 Flikr，通过邮件附件发送以及创建在线相册。

它有 61 个图像处理模块，可以调整图像的对比度、色调、明暗、色彩、噪点；添加水印；切割以及旋转；等等。如同另外两个软件一样，不论你做出多少次修改，这些修改都是“无损的” —— 你的初始 RAW 图像文件始终会被保存。

Darktable 可以从 400 多种相机型号中直接导入照片，以及有 JPEG、CR2、DNG、OpenEXR 和 PFM 等格式的支持。图像在一个数据库中显示，因此你可以轻易地过滤并查询这些元数据，包括了文字标签、评星以及颜色标签。软件同时支持 21 种语言，支持 Linux、MacOS、BSD、Solaris 11/GNOME 以及 Windows（Windows 版本是最新发布的，Darktable 声明它比起其他版本可能还有一些不完备之处，有一些未实现的功能）。

Darktable 在开源许可证 [GPLv3](https://github.com/darktable-org/darktable/blob/master/LICENSE) 下发布，你可以了解更多它的 [特性](https://www.darktable.org/about/features/)，查阅它的 [用户手册](https://www.darktable.org/resources/)，或者直接去 Github 上看[源代码](https://github.com/darktable-org/darktable) 。

### LightZone

![LightZone's tool stack](/data/attachment/album/201905/29/071051zi6fz152z9p491i6.jpg)

[LightZone](http://www.lightzoneproject.org/) 和其他两个软件类似同样是无损的 RAW 格式图像处理工具：它是跨平台的，有 Windows、MacOS 和 Linux 版本，除 RAW 格式之外，它还支持 JPG 和 TIFF 格式的图像处理。接下来说说 LightZone 其他独特特性。

这个软件最初在 2005 年时，是以专有许可证发布的图像处理软件，后来在 BSD 证书下开源。此外，在你下载这个软件之前，你必须注册一个免费账号，以便 LightZone的 开发团队可以跟踪软件的下载数量以及建立相关社区。（许可很快，而且是自动的，因此这不是一个很大的使用障碍。）

除此之外的一个特性是这个软件的图像处理通常是通过很多可组合的工具实现的，而不是叠加滤镜（就像大多数图像处理软件），这些工具组可以被重新编排以及移除，以及被保存并且复制用到另一些图像上。如果想要编辑图片的部分区域，你还可以通过矢量工具或者根据色彩和亮度来选择像素。

想要了解更多，见 LightZone 的[论坛](http://www.lightzoneproject.org/Forum) 或者查看 Github上的 [源代码](https://github.com/ktgw0316/LightZone)。

### RawTherapee

![RawTherapee](/data/attachment/album/201905/29/071051m1lo6j020e12ez17.jpg)

[RawTherapee](http://rawtherapee.com/) 是另一个值得关注的开源（[GPL](https://github.com/Beep6581/RawTherapee/blob/dev/LICENSE.txt)）的 RAW 图像处理器。就像 Darktable 和 LightZone，它是跨平台的（支持 Windows、MacOS 和 Linux），一切修改都在无损条件下进行，因此不论你叠加多少滤镜做出多少改变，你都可以回到你最初的 RAW 文件。

RawTherapee 采用的是一个面板式的界面，包括一个历史记录面板来跟踪你做出的修改，以方便随时回到先前的图像；一个快照面板可以让你同时处理一张照片的不同版本；一个可滚动的工具面板可以方便准确地选择工具。这些工具包括了一系列的调整曝光、色彩、细节、图像变换以及去马赛克功能。

这个软件可以从多数相机直接导入 RAW 文件，并且支持超过 25 种语言，得到了广泛使用。批量处理以及 [SSE](https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions) 优化这类功能也进一步提高了图像处理的速度以及对 CPU 性能的利用。

RawTherapee 还提供了很多其他 [功能](http://rawpedia.rawtherapee.com/Features)；可以查看它的 [官方文档](http://rawpedia.rawtherapee.com/Main_Page) 以及 [源代码](https://github.com/Beep6581/RawTherapee) 了解更多细节。

你是否在摄影中使用另外的开源 RAW 图像处理工具？有任何建议和推荐都可以在评论中分享。

---

via: <https://opensource.com/alternatives/adobe-lightroom>

作者：[Opensource.com](https://opensource.com) 选题：[lujun9972](https://github.com/lujun9972) 译者：[scoutydren](https://github.com/scoutydren) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


区块链 2.0：房地产区块链（四）

![](/data/attachment/album/201905/29/215511bqkdqcffqdf6v8zq.png)

### 区块链 2.0：“更”智能的房地产

在本系列的[上一篇文章](/article-10689-1.html)中我们探讨了区块链的特征，这些区块链将使机构能够将**传统银行**和**融资系统**转换和交织在一起。这部分将探讨**房地产区块链**。房地产业正在走向革命。它是人类已知的交易最活跃、最重要的资产类别之一。然而，由于充满了监管障碍和欺诈、欺骗的无数可能性，它也是最难参与交易的之一。利用适当的共识算法的区块链的分布式分类账本功能被吹捧为这个行业的前进方向，而这个行业传统上被认为其面对变革是保守的。

就其无数的业务而言，房地产一直是一个非常保守的行业。这似乎也是理所当然的。2008 年金融危机或 20 世纪上半叶的大萧条等重大经济危机成功摧毁了该行业及其参与者。然而，与大多数具有经济价值的产品一样，房地产行业具有弹性，而这种弹性则源于其保守性。

全球房地产市场由价值 228 万亿    1  美元的资产类别组成，出入不大。其他投资资产，如股票、债券和股票合计价值仅为 170 万亿美元。显然，在这样一个行业中实施的交易在很大程度上都是精心策划和执行的。很多时候，房地产也因许多欺诈事件而臭名昭著，并且随之而来的是毁灭性的损失。由于其运营非常保守，该行业也难以驾驭。它受到了法律的严格监管，创造了一个交织在一起的细微差别网络，这对于普通人来说太难以完全理解，使得大多数人无法进入和参与。如果你曾参与过这样的交易，那么你就会知道纸质文件的重要性和长期性。

从一个微不足道的开始，虽然是一个重要的例子，以显示当前的记录管理实践在房地产行业有多糟糕，考虑一下[产权保险业务](https://www.forbes.com/sites/jordanlulich/2018/06/21/what-is-title-insurance-and-why-its-important/#1472022b12bb)    2 。产权保险用于对冲土地所有权和所有权记录不可接受且从而无法执行的可能性。诸如此类的保险产品也称为赔偿保险。在许多情况下，法律要求财产拥有产权保险，特别是在处理多年来多次易手的财产时。抵押贷款公司在支持房地产交易时也可能坚持同样的要求。事实上，这种产品自 19 世纪 50 年代就已存在，并且仅在美国每年至少有 1.5 万亿美元的商业价值这一事实证明了一开始的说法。在这种情况下，这些记录的维护方式必须进行改革，区块链提供了一个可持续解决方案。根据[美国土地产权协会](https://www.cbinsights.com/research/blockchain-real-estate-disruption/#financing)，平均每个案例的欺诈平均约为 10 万美元，并且涉及交易的所有产权中有 25％ 的文件存在问题。区块链允许设置一个不可变的永久数据库，该数据库将跟踪资产本身，记录已经进入的每个交易或投资。这样的分类帐本系统将使包括一次性购房者在内的房地产行业的每个人的生活更加轻松，并使诸如产权保险等金融产品基本上无关紧要。将诸如房地产之类的实物资产转换为这样的数字资产是非常规的，并且目前仅在理论上存在。然而，这种变化迫在眉睫，而不是迟到    3 。

区块链在房地产中影响最大的领域如上所述，在维护透明和安全的产权管理系统方面。基于区块链的财产记录可以包含有关财产、其所在地、所有权历史以及相关的公共记录的[信息](https://www2.deloitte.com/us/en/pages/financial-services/articles/blockchain-in-commercial-real-estate.html)。这将允许房地产交易快速完成，并且无需第三方监控和监督。房地产评估和税收计算等任务成为有形的、客观的参数问题，而不是主观测量和猜测，因为可靠的历史数据是可公开验证的。[UBITQUITY](https://www.ubitquity.io/) 就是这样一个平台，为企业客户提供定制的基于区块链的解决方案。该平台允许客户跟踪所有房产细节、付款记录、抵押记录，甚至允许运行智能合约，自动处理税收和租赁。

这为我们带来了房地产区块链的第二大机遇和用例。由于该行业受到众多第三方的高度监管，除了参与交易的交易对手外，尽职调查和财务评估可能非常耗时。这些流程主要通过离线渠道进行，文书工作需要在最终评估报告出来之前进行数天。对于公司房地产交易尤其如此，这构成了顾问所收取的总计费时间的大部分。如果交易由抵押背书，则这些过程的重复是不可避免的。一旦与所涉及的人员和机构的数字身份相结合，就可以完全避免当前的低效率，并且可以在几秒钟内完成交易。租户、投资者、相关机构、顾问等可以单独验证数据并达成一致的共识，从而验证永久性的财产记录    4 。这提高了验证流程的准确性。房地产巨头 RE/MAX 最近宣布与服务提供商 XYO Network Partners 合作，[建立墨西哥房上市地产国家数据库](https://www.businesswire.com/news/home/20181012005068/en/XYO-Network-Partners-REMAX-M%C3%A9xico-Bring-Blockchain)。他们希望有朝一日能够创建世界上最大的（截至目前）去中心化房地产登记中心之一。

然而，区块链可以带来的另一个重要且可以说是非常民主的变化是投资房地产。与其他投资资产类别不同，即使是小型家庭投资者也可能参与其中，房地产通常需要大量的手工付款才能参与。诸如 ATLANT 和 BitOfProperty 之类的公司将房产的账面价值代币化，并将其转换为加密货币的等价物。这些代币随后在交易所出售，类似于股票和股票的交易方式。[房地产后续产生的任何现金流都会根据其在财产中的“份额”记入贷方或借记给代币所有者](https://www.cbinsights.com/research/blockchain-real-estate-disruption/#financing)。

然而，尽管如此，区块链技术仍处于房地产领域的早期采用阶段，目前的法规还没有明确定义它。诸如分布式应用程序、分布式匿名组织（DAO）、智能合约等概念在许多国家的法律领域是闻所未闻的。一旦所有利益相关者充分接受了区块链复杂性的良好教育，就会彻底改革现有的法规和指导方针，这是最务实的前进方式。 同样，这将是一个缓慢而渐进的变化，但是它是一个急需的变化。本系列的下一篇文章将介绍 “智能合约”，例如由 UBITQUITY 和 XYO 等公司实施的那些是如何在区块链中创建和执行的。

---

1. HSBC, “Global Real Estate,” no. April, 2008 [↩](#fnref1)
2. D. B. Burke, Law of title insurance. Aspen Law & Business, 2000. [↩](#fnref2)
3. M. Swan, O’Reilly – Blockchain. Blueprint for a New Economy – 2015. [↩](#fnref3)
4. Deloite, “Blockchain in commercial real estate The future is here ! Table of contents.” [↩](#fnref4)

---

via: <https://www.ostechnix.com/blockchain-2-0-blockchain-in-real-estate/>

作者：[ostechnix](https://www.ostechnix.com/author/editor/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


ddgr：一个从终端搜索 DuckDuckGo 的命令行工具

Summary: ddgr 是一个命令行实用程序，用于从终端搜索 DuckDuckGo。如果设置了 BROWSER 环境变量，ddgr 可以在几个基于文本的浏览器中开箱即用。

![](/data/attachment/album/201905/29/224249rxxnkk8q523pku22.jpg)

在 Linux 中，Bash 技巧非常棒，它使 Linux 中的一切成为可能。

对于开发人员或系统管理员来说，它真的很管用，因为他们大部分时间都在使用终端。你知道他们为什么喜欢这种技巧吗？

因为这些技巧可以提高他们的工作效率，也能使他们工作更快。

### 什么是 ddgr

[ddgr](https://github.com/jarun/ddgr) 是一个命令行实用程序，用于从终端搜索 DuckDuckGo。如果设置了 `BROWSER` 环境变量，ddgr 可以在几个基于文本的浏览器中开箱即用。

确保你的系统安装了任何一个基于文本的浏览器。你可能知道 [googler](https://www.2daygeek.com/googler-google-search-from-the-command-line-on-linux/)，它允许用户从 Linux 命令行进行 Google 搜索。

它在命令行用户中非常受欢迎，他们期望对隐私敏感的 DuckDuckGo 也有类似的实用程序，这就是 `ddgr` 出现的原因。

与 Web 界面不同，你可以指定每页要查看的搜索结果数。

**建议阅读：**

* [Googler – 从 Linux 命令行搜索 Google](https://www.2daygeek.com/googler-google-search-from-the-command-line-on-linux/)
* [Buku – Linux 中一个强大的命令行书签管理器](https://www.2daygeek.com/buku-command-line-bookmark-manager-linux/)
* [SoCLI – 从终端搜索和浏览 StackOverflow 的简单方法](https://www.2daygeek.com/socli-search-and-browse-stack-overflow-from-linux-terminal/)
* [RTV（Reddit 终端查看器）- 一个简单的 Reddit 终端查看器](https://www.2daygeek.com/rtv-reddit-terminal-viewer-a-simple-terminal-viewer-for-reddit/)

### 什么是 DuckDuckGo

DDG 即 DuckDuckGo。DuckDuckGo（DDG）是一个真正保护用户搜索和隐私的互联网搜索引擎。它没有过滤用户的个性化搜索结果，对于给定的搜索词，它会向所有用户显示相同的搜索结果。

大多数用户更喜欢谷歌搜索引擎，但是如果你真的担心隐私，那么你可以放心地使用 DuckDuckGo。

### ddgr 特性

* 快速且干净（没有广告、多余的 URL 或杂物参数），自定义颜色
* 旨在以最小的空间提供最高的可读性
* 指定每页显示的搜索结果数
* 可以在 omniprompt 中导航结果，在浏览器中打开 URL
* 用于 Bash、Zsh 和 Fish 的搜索和选项补完脚本
* 支持 DuckDuckGo Bang（带有自动补完）
* 直接在浏览器中打开第一个结果（如同 “I’m Feeling Ducky”）
* 不间断搜索：无需退出即可在 omniprompt 中触发新搜索
* 关键字支持（例如：filetype:mime、site:somesite.com）
* 按时间、指定区域搜索，禁用安全搜索
* 支持 HTTPS 代理，支持 Do Not Track，可选择禁用用户代理字符串
* 支持自定义 URL 处理程序脚本或命令行实用程序
* 全面的文档，man 页面有方便的使用示例
* 最小的依赖关系

### 需要条件

`ddgr` 需要 Python 3.4 或更高版本。因此，确保你的系统应具有 Python 3.4 或更高版本。

```
$ python3 --version
Python 3.6.3
```

### 如何在 Linux 中安装 ddgr

我们可以根据发行版使用以下命令轻松安装 `ddgr`。

对于 Fedora ，使用 [DNF 命令](https://www.2daygeek.com/dnf-command-examples-manage-packages-fedora-system/)来安装 `ddgr`。

```
# dnf install ddgr
```

或者我们可以使用 [SNAP 命令](https://www.2daygeek.com/snap-command-examples/)来安装 `ddgr`。

```
# snap install ddgr
```

对于 LinuxMint/Ubuntu，使用 [APT-GET 命令](https://www.2daygeek.com/apt-get-apt-cache-command-examples-manage-packages-debian-ubuntu-systems/) 或 [APT 命令](https://www.2daygeek.com/apt-command-examples-manage-packages-debian-ubuntu-systems/)来安装 `ddgr`。

```
$ sudo add-apt-repository ppa:twodopeshaggy/jarun
$ sudo apt-get update
$ sudo apt-get install ddgr
```

对于基于 Arch Linux 的系统，使用 [Yaourt 命令](https://www.2daygeek.com/install-yaourt-aur-helper-on-arch-linux/)或 [Packer 命令](https://www.2daygeek.com/install-packer-aur-helper-on-arch-linux/)从 AUR 仓库安装 `ddgr`。

```
$ yaourt -S ddgr
或
$ packer -S ddgr
```

对于 Debian，使用 [DPKG 命令](https://www.2daygeek.com/dpkg-command-to-manage-packages-on-debian-ubuntu-linux-mint-systems/) 安装 `ddgr`。

```
# wget https://github.com/jarun/ddgr/releases/download/v1.2/ddgr_1.2-1_debian9.amd64.deb
# dpkg -i ddgr_1.2-1_debian9.amd64.deb
```

对于 CentOS 7，使用 [YUM 命令](https://www.2daygeek.com/yum-command-examples-manage-packages-rhel-centos-systems/)来安装 `ddgr`。

```
# yum install https://github.com/jarun/ddgr/releases/download/v1.2/ddgr-1.2-1.el7.3.centos.x86_64.rpm
```

对于 opensuse，使用 [zypper 命令](https://www.2daygeek.com/zypper-command-examples-manage-packages-opensuse-system/)来安装 `ddgr`。

```
# zypper install https://github.com/jarun/ddgr/releases/download/v1.2/ddgr-1.2-1.opensuse42.3.x86_64.rpm
```

### 如何启动 ddgr

在终端上输入 `ddgr` 命令，不带任何选项来进行 DuckDuckGo 搜索。你将获得类似于下面的输出。

```
$ ddgr
```

![](/data/attachment/album/201905/29/224628haazlafyyvnc3gzo.png)

### 如何使用 ddgr 进行搜索

我们可以通过两种方式启动搜索。从 omniprompt 或者直接从终端开始。你可以搜索任何你想要的短语。

直接从终端：

```
$ ddgr 2daygeek
```

![](/data/attachment/album/201905/29/224638wcl2ctvbe66ezg3k.png)

从 omniprompt：

![](/data/attachment/album/201905/29/224657cp0991z0n9o99koj.png)

### Omniprompt 快捷方式

输入 `?` 以获得 omniprompt，它将显示关键字列表和进一步使用 `ddgr` 的快捷方式。

![](/data/attachment/album/201905/29/224721atg4e8wggtrwburm.png)

### 如何移动下一页、上一页和第一页

它允许用户移动下一页、上一页或第一页。

* `n`： 移动到下一组搜索结果
* `p`： 移动到上一组搜索结果
* `f`： 跳转到第一页

![](/data/attachment/album/201905/29/224730qat0xu7jbdaaxdr3.png)

### 如何启动新搜索

`d` 选项允许用户从 omniprompt 发起新的搜索。例如，我搜索了 “2daygeek website”，现在我将搜索 “Magesh Maruthamuthu” 这个新短语。

从 omniprompt：

```
ddgr (? for help) d magesh maruthmuthu
```

![](/data/attachment/album/201905/29/224741qg9of29e8m9x9g0t.png)

### 在搜索结果中显示完整的 URL

默认情况下，它仅显示文章标题，在搜索中添加 `x` 选项以在搜索结果中显示完整的文章网址。

```
$ ddgr -n 5 -x 2daygeek
```

![](/data/attachment/album/201905/29/224806nu9goqddruidt9ga.png)

### 限制搜索结果

默认情况下，搜索结果每页显示 10 个结果。如果你想为方便起见限制页面结果，可以使用 `ddgr` 带有 `--num` 或 `-n` 参数。

```
$ ddgr -n 5 2daygeek
```

![](/data/attachment/album/201905/29/224823uxh7rnjbb6raa2rd.png)

### 网站特定搜索

要搜索特定网站的特定页面，使用以下格式。这将从网站获取给定关键字的结果。例如，我们在 2daygeek 网站下搜索 “Package Manager”，查看结果。

```
$ ddgr -n 5 --site 2daygeek "package manager"
```

![](/data/attachment/album/201905/29/224836drzlil5vldjvmpuq.png)

---

via: <https://www.2daygeek.com/ddgr-duckduckgo-search-from-the-command-line-in-linux/>

作者：[Magesh Maruthamuthu](https://www.2daygeek.com/author/magesh/) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy) 选题：[lujun9972](https://github.com/lujun9972)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何管理你的 Linux 环境变量

>
> Linux 用户环境变量可以帮助你找到你需要的命令，无须了解系统如何配置的细节而完成大量工作。而这些设置来自哪里和如何被修改它们是另一个话题。
>
>
>

![IIP Photo Archive \(CC BY 2.0\)](/data/attachment/album/201905/29/234206z44k48j4cccj32c2.jpg)

在 Linux 系统上的用户账户配置以多种方法简化了系统的使用。你可以运行命令，而不需要知道它们的位置。你可以重新使用先前运行的命令，而不用发愁系统是如何追踪到它们的。你可以查看你的电子邮件，查看手册页，并容易地回到你的家目录，而不用管你在文件系统中身在何方。并且，当需要的时候，你可以调整你的账户设置，以便其更符合你喜欢的方式。

Linux 环境设置来自一系列的文件：一些是系统范围（意味着它们影响所有用户账户），一些是处于你的家目录中的配置文件里。系统范围的设置在你登录时生效，而本地设置在其后生效，所以，你在你账户中作出的更改将覆盖系统范围设置。对于 bash 用户，这些文件包含这些系统文件：

```
/etc/environment
/etc/bash.bashrc
/etc/profile
```

以及一些本地文件：

```
~/.bashrc
~/.profile # 如果有 ~/.bash_profile 或 ~/.bash_login 就不会读此文件
~/.bash_profile
~/.bash_login
```

你可以修改本地存在的四个文件的任何一个，因为它们处于你的家目录，并且它们是属于你的。

### 查看你的 Linux 环境设置

为查看你的环境设置，使用 `env` 命令。你的输出将可能与这相似：

```
$ env
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;
01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:
*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:
*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:
*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;
31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:
*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:
*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:
*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:
*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:
*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:
*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:
*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:
*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:
*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:
*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:
*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:
*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.spf=00;36:
SSH_CONNECTION=192.168.0.21 34975 192.168.0.11 22
LESSCLOSE=/usr/bin/lesspipe %s %s
LANG=en_US.UTF-8
OLDPWD=/home/shs
XDG_SESSION_ID=2253
USER=shs
PWD=/home/shs
HOME=/home/shs
SSH_CLIENT=192.168.0.21 34975 22
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
SSH_TTY=/dev/pts/0
MAIL=/var/mail/shs
TERM=xterm
SHELL=/bin/bash
SHLVL=1
LOGNAME=shs
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
XDG_RUNTIME_DIR=/run/user/1000
PATH=/home/shs/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
LESSOPEN=| /usr/bin/lesspipe %s
_=/usr/bin/env
```

虽然你可能会看到大量的输出，上面显示的第一大部分用于在命令行上使用颜色标识各种文件类型。当你看到类似 `*.tar=01;31:` 这样的东西，这告诉你 `tar` 文件将以红色显示在文件列表中，然而 `*.jpg=01;35:` 告诉你 jpg 文件将以紫色显现出来。这些颜色旨在使它易于从一个文件列表中分辨出某些文件。你可以在《[在 Linux 命令行中自定义你的颜色](https://www.networkworld.com/article/3269587/customizing-your-text-colors-on-the-linux-command-line.html)》处学习更多关于这些颜色的定义，和如何自定义它们。

当你更喜欢一种不加装饰的显示时，一种关闭颜色显示的简单方法是使用如下命令：

```
$ ls -l --color=never
```

这个命令可以简单地转换到一个别名：

```
$ alias ll2='ls -l --color=never'
```

你也可以使用 `echo` 命令来单独地显现某个设置。在这个命令中，我们显示在历史缓存区中将被记忆命令的数量：

```
$ echo $HISTSIZE
1000
```

如果你已经移动到某个位置，你在文件系统中的最后位置会被记在这里：

```
PWD=/home/shs
OLDPWD=/tmp
```

### 作出更改

你可以使用一个像这样的命令更改环境设置，但是，如果你希望保持这个设置，在你的 `~/.bashrc` 文件中添加一行代码，例如 `HISTSIZE=1234`。

```
$ export HISTSIZE=1234
```

### “export” 一个变量的本意是什么

导出一个环境变量可使设置用于你的 shell 和可能的子 shell。默认情况下，用户定义的变量是本地的，并不被导出到新的进程，例如，子 shell 和脚本。`export` 命令使得环境变量可用在子进程中发挥功用。

### 添加和移除变量

你可以很容易地在命令行和子 shell 上创建新的变量，并使它们可用。然而，当你登出并再次回来时这些变量将消失，除非你也将它们添加到 `~/.bashrc` 或一个类似的文件中。

```
$ export MSG="Hello, World!"
```

如果你需要，你可以使用 `unset` 命令来消除一个变量：

```
$ unset MSG
```

如果变量是局部定义的，你可以通过加载你的启动文件来简单地将其设置回来。例如：

```
$ echo $MSG
Hello, World!
$ unset $MSG
$ echo $MSG

$ . ~/.bashrc
$ echo $MSG
Hello, World!
```

### 小结

用户账户是用一组恰当的启动文件设立的，创建了一个有用的用户环境，而个人用户和系统管理员都可以通过编辑他们的个人设置文件（对于用户）或很多来自设置起源的文件（对于系统管理员）来更改默认设置。

---

via: <https://www.networkworld.com/article/3385516/how-to-manage-your-linux-environment.html>

作者：[Sandra Henry-Stocker](https://www.networkworld.com/author/Sandra-Henry_Stocker/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[robsean](https://github.com/robsean) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


命令别名：保护和服务

>
> Linux shell 允许你将命令彼此链接在一起，一次触发执行复杂的操作，并且可以对此创建别名作为快捷方式。
>
>
>

![](/data/attachment/album/201905/30/214539wl3d4r945p944u2y.jpg)

让我们将继续我们的别名系列。到目前为止，你可能已经阅读了我们的[关于别名的第一篇文章](/article-10377-1.html)，并且应该非常清楚它们是如何为你省去很多麻烦的最简单方法。例如，你已经看到它们帮助我们减少了输入，让我们看看别名派上用场的其他几个案例。

### 别名即快捷方式

Linux shell 最美妙的事情之一是可以使用数以万计的选项和把命令连接在一起执行真正复杂的操作。好吧，也许这种美丽是在旁观者的眼中的，但是我们觉得这个功能很实用。

不利的一面是，你经常需要记得难以记忆或难以打字出来的命令组合。比如说硬盘上的空间非常宝贵，而你想要做一些清洁工作。你的第一步可能是寻找隐藏在你的家目录里的东西。你可以用来判断的一个标准是查找不再使用的内容。`ls` 可以帮助你：

```
ls -lct
```

上面的命令显示了每个文件和目录的详细信息（`-l`），并显示了每一项上次访问的时间（`-c`），然后它按从最近访问到最少访问的顺序排序这个列表（`-t`）。

这难以记住吗？你可能不会每天都使用 `-c` 和 `-t` 选项，所以也许是吧。无论如何，定义一个别名，如：

```
alias lt='ls -lct'
```

会更容易一些。

然后，你也可能希望列表首先显示最旧的文件：

```
alias lo='lt -F | tac'
```

![aliases](/data/attachment/album/201905/30/214540iqxibigr2gq4ybgw.png "aliases")

*图 1：使用 lt 和 lo 别名。*

这里有一些有趣的事情。首先，我们使用别名（`lt`）来创建另一个别名 —— 这是完全可以的。其次，我们将一个新参数传递给 `lt`（后者又通过 `lt` 别名的定义传递给了 `ls`）。

`-F` 选项会将特殊符号附加到项目的名称后，以便更好地区分常规文件（没有符号）和可执行文件（附加了 `*`）、目录文件（以 `/` 结尾），以及所有链接文件、符号链接文件（以 `@` 符号结尾）等等。`-F` 选项是当你回归到单色终端的日子里，没有其他方法可以轻松看到列表项之间的差异时用的。在这里使用它是因为当你将输出从 `lt` 传递到 `tac` 时，你会丢失 `ls` 的颜色。

第三件我们需要注意的事情是我们使用了管道。管道用于你将一个命令的输出传递给另外一个命令时。第二个命令可以使用这些输出作为它的输入。在包括 Bash 在内的许多 shell 里，你可以使用管道符（`|`） 来做传递。

在这里，你将来自 `lt -F` 的输出导给 `tac`。`tac` 这个命令有点玩笑的意思，你或许听说过 `cat` 命令，它名义上用于将文件彼此连接（con`cat`），而在实践中，它被用于将一个文件的内容打印到终端。`tac` 做的事情一样，但是它是以逆序将接收到的内容输出出来。明白了吗？`cat` 和 `tac`，技术人有时候也挺有趣的。

`cat` 和 `tac` 都能输出通过管道传递过来的内容，在这里，也就是一个按时间顺序排序的文件列表。

那么，在有些离题之后，最终我们得到的就是这个列表将当前目录中的文件和目录以新鲜度的逆序列出（即老的在前）。

最后你需要注意的是，当在当前目录或任何目录运行 `lt` 时：

```
# 这可以工作：
lt
# 这也可以：
lt /some/other/directory
```

……而 `lo` 只能在当前目录奏效：

```
# 这可工作：
lo
# 而这不行：
lo /some/other/directory
```

这是因为 Bash 会展开别名的组分。当你键入：

```
lt /some/other/directory
```

Bash 实际上运行的是：

```
ls -lct /some/other/directory
```

这是一个有效的 Bash 命令。

而当你键入：

```
lo /some/other/directory
```

Bash 试图运行：

```
ls -lct -F | tac /some/other/directory
```

这不是一个有效的命令，主要是因为 `/some/other/directory` 是个目录，而 `cat` 和 `tac` 不能用于目录。

### 更多的别名快捷方式

* `alias lll='ls -R'` 会打印出目录的内容，并深入到子目录里面打印子目录的内容，以及子目录的子目录，等等。这是一个查看一个目录下所有内容的方式。
* `mkdir='mkdir -pv'` 可以让你一次性创建目录下的目录。按照 `mkdir` 的基本形式，要创建一个包含子目录的目录，你必须这样：

```
mkdir newdir
mkdir newdir/subdir
```

或这样：

```
mkdir -p newdir/subdir
```

而用这个别名你将只需要这样就行：

```
mkdir newdir/subdir
```

你的新 `mkdir` 也会告诉你创建子目录时都做了什么。

### 别名也是一种保护

别名的另一个好处是它可以作为防止你意外地删除或覆写已有的文件的保护措施。你可能听说过这个 Linux 新用户的传言，当他们以 root 身份运行：

```
rm -rf /
```

整个系统就爆了。而决定输入如下命令的用户：

```
rm -rf /some/directory/ *
```

就很好地干掉了他们的家目录的全部内容。这里不小心键入的目录和 `*` 之间的那个空格有时候很容易就会被忽视掉。

这两种情况我们都可以通过 `alias rm='rm -i'` 别名来避免。`-i` 选项会使 `rm` 询问用户是否真的要做这个操作，在你对你的文件系统做出不可弥补的损失之前给你第二次机会。

对于 `cp` 也是一样，它能够覆盖一个文件而不会给你任何提示。创建一个类似 `alias cp='cp -i'` 来保持安全吧。

### 下一次

我们越来越深入到了脚本领域，下一次，我们将沿着这个方向，看看如何在命令行组合命令以给你真正的乐趣，并可靠地解决系统管理员每天面临的问题。

---

via: <https://www.linux.com/blog/learn/2019/1/aliases-protect-and-serve>

作者：[Paul Brown](https://www.linux.com/users/bro66) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


在 Ubuntu 上安装 Budgie 桌面

Summary: 在这个逐步的教程中学习如何在 Ubuntu 上安装 Budgie 桌面。

在这个逐步的教程中学习如何在 Ubuntu 上安装 Budgie 桌面。

>
> 在这个逐步的教程中学习如何在 Ubuntu 上安装 Budgie 桌面。
>
>
>

![](/data/attachment/album/201905/31/084804qys7b26ggwygazyg.png)

在所有[各种 Ubuntu 版本](https://itsfoss.com/which-ubuntu-install/)中，[Ubuntu Budgie](https://ubuntubudgie.org/) 是最被低估的版本。它外观优雅，而且需要的资源也不多。

阅读这篇 《[Ubuntu Budgie 点评](https://itsfoss.com/ubuntu-budgie-18-review/)》或观看下面的视频，了解 Ubuntu Budgie 18.04 的外观如何。

如果你喜欢 [Budgie 桌面](https://github.com/solus-project/budgie-desktop)但你正在使用其他版本的 Ubuntu，例如默认 Ubuntu 带有 GNOME 桌面，我有个好消息。你可以在当前的 Ubuntu 系统上安装 Budgie 并切换桌面环境。

在这篇文章中，我将告诉你到底该怎么做。但首先，对那些不了解 Budgie 的人进行一点介绍。

Budgie 桌面环境主要由 [Solus Linux 团队开发](https://getsol.us/home/)。它的设计注重优雅和现代使用。Budgie 适用于所有主流 Linux 发行版，可以让用户在其上尝试体验这种新的桌面环境。Budgie 现在非常成熟，并提供了出色的桌面体验。

>
> 警告
>
>
> 在同一系统上安装多个桌面可能会导致冲突，你可能会遇到一些问题，如面板中缺少图标或同一程序的多个图标。
>
>
> 你也许不会遇到任何问题。是否要尝试不同桌面由你决定。
>
>
>

### 在 Ubuntu 上安装 Budgie

此方法未在 Linux Mint 上进行测试，因此我建议你 Mint 上不要按照此指南进行操作。

对于正在使用 Ubuntu 的人，Budgie 现在默认是 Ubuntu 仓库的一部分。因此，我们不需要添加任何 PPA 来下载 Budgie。

要安装 Budgie，只需在终端中运行此命令即可。我们首先要确保系统已完全更新。

```
sudo apt update && sudo apt upgrade
sudo apt install ubuntu-budgie-desktop
```

下载完成后，你将看到选择显示管理器的提示。选择 “lightdm” 以获得完整的 Budgie 体验。

![Select lightdm](/data/attachment/album/201905/31/084833c25g6tsys5ggisqy.png)

安装完成后，重启计算机。然后，你会看到 Budgie 的登录页面。输入你的密码进入主屏幕。

![Budgie Desktop Home](/data/attachment/album/201905/31/084836bk5kv17rjxu5erjp.jpg)

### 切换到其他桌面环境

![Budgie login screen](/data/attachment/album/201905/31/084840f1h91smy711ixtxy.png)

你可以单击登录名旁边的 Budgie 图标获取登录选项。在那里，你可以在已安装的桌面环境（DE）之间进行选择。就我而言，我看到了 Budgie 和默认的 Ubuntu（GNOME）桌面。

![Select your DE](/data/attachment/album/201905/31/084843jn27re2vo7966v2o.png)

因此，无论何时你想登录 GNOME，都可以使用此菜单执行此操作。

### 如何删除 Budgie

如果你不喜欢 Budgie 或只是想回到常规的以前的 Ubuntu，你可以如上节所述切换回常规桌面。

但是，如果你真的想要删除 Budgie 及其组件，你可以按照以下命令回到之前的状态。

**在使用这些命令之前先切换到其他桌面环境：**

```
sudo apt remove ubuntu-budgie-desktop ubuntu-budgie* lightdm
sudo apt autoremove
sudo apt install --reinstall gdm3
```

成功运行所有命令后，重启计算机。

现在，你将回到 GNOME 或其他你有的桌面。

### 你对 Budgie 有什么看法？

Budgie 是[最佳 Linux 桌面环境](https://itsfoss.com/best-linux-desktop-environments/)之一。希望这个简短的指南帮助你在 Ubuntu 上安装了很棒的 Budgie 桌面。

如果你安装了 Budgie，你最喜欢它的什么？请在下面的评论中告诉我们。像往常一样，欢迎任何问题或建议。

---

via: <https://itsfoss.com/install-budgie-ubuntu/>

作者：[Atharva Lele](https://itsfoss.com/author/atharva/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


自己成为一个证书颁发机构（CA）

Summary: 为你的微服务架构或者集成测试创建一个简单的内部 CA。

为你的微服务架构或者集成测试创建一个简单的内部 CA。

>
> 为你的微服务架构或者集成测试创建一个简单的内部 CA。
>
>
>

![](/data/attachment/album/201905/31/091023sg9s0ss11rsoseqg.jpg)

传输层安全（[TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security)）模型（有时也称它的旧名称 SSL）基于<ruby> <a href="https://en.wikipedia.org/wiki/Certificate_authority">  证书颁发机构 </a> <rt>  certificate authoritie </rt></ruby>（CA）的概念。这些机构受到浏览器和操作系统的信任，从而*签名*服务器的的证书以用于验证其所有权。

但是，对于内部网络，微服务架构或集成测试，有时候*本地 CA*更有用：一个只在内部受信任的 CA，然后签名本地服务器的证书。

这对集成测试特别有意义。获取证书可能会带来负担，因为这会占用服务器几分钟。但是在代码中使用“忽略证书”可能会被引入到生产环境，从而导致安全灾难。

CA 证书与常规服务器证书没有太大区别。重要的是它被本地代码信任。例如，在 Python `requests` 库中，可以通过将 `REQUESTS_CA_BUNDLE` 变量设置为包含此证书的目录来完成。

在为集成测试创建证书的例子中，不需要*长期的*证书：如果你的集成测试需要超过一天，那么你应该已经测试失败了。

因此，计算**昨天**和**明天**作为有效期间隔：

```
>>> import datetime
>>> one_day = datetime.timedelta(days=1)
>>> today = datetime.date.today()
>>> yesterday = today - one_day
>>> tomorrow = today - one_day
```

现在你已准备好创建一个简单的 CA 证书。你需要生成私钥，创建公钥，设置 CA 的“参数”，然后自签名证书：CA 证书*总是*自签名的。最后，导出证书文件以及私钥文件。

```
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography import x509
from cryptography.x509.oid import NameOID

private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)
public_key = private_key.public_key()
builder = x509.CertificateBuilder()
builder = builder.subject_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, 'Simple Test CA'),
]))
builder = builder.issuer_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, 'Simple Test CA'),
]))
builder = builder.not_valid_before(yesterday)
builder = builder.not_valid_after(tomorrow)
builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(public_key)
builder = builder.add_extension(
    x509.BasicConstraints(ca=True, path_length=None),
    critical=True)
certificate = builder.sign(
    private_key=private_key, algorithm=hashes.SHA256(),
    backend=default_backend()
)
private_bytes = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.TraditionalOpenSSL,
    encryption_algorithm=serialization.NoEncrption())
public_bytes = certificate.public_bytes(
    encoding=serialization.Encoding.PEM)
with open("ca.pem", "wb") as fout:
    fout.write(private_bytes + public_bytes)
with open("ca.crt", "wb") as fout:
    fout.write(public_bytes)
```

通常，真正的 CA 会需要[证书签名请求](https://en.wikipedia.org/wiki/Certificate_signing_request)（CSR）来签名证书。但是，当你是自己的 CA 时，你可以制定自己的规则！可以径直签名你想要的内容。

继续集成测试的例子，你可以创建私钥并立即签名相应的公钥。注意 `COMMON_NAME` 需要是 `https` URL 中的“服务器名称”。如果你已配置名称查询，你需要服务器能响应对 `service.test.local` 的请求。

```
service_private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)
service_public_key = service_private_key.public_key()
builder = x509.CertificateBuilder()
builder = builder.subject_name(x509.Name([
   x509.NameAttribute(NameOID.COMMON_NAME, 'service.test.local')
]))
builder = builder.not_valid_before(yesterday)
builder = builder.not_valid_after(tomorrow)
builder = builder.public_key(public_key)
certificate = builder.sign(
    private_key=private_key, algorithm=hashes.SHA256(),
    backend=default_backend()
)
private_bytes = service_private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.TraditionalOpenSSL,
    encryption_algorithm=serialization.NoEncrption())
public_bytes = certificate.public_bytes(
    encoding=serialization.Encoding.PEM)
with open("service.pem", "wb") as fout:
    fout.write(private_bytes + public_bytes)
```

现在 `service.pem` 文件有一个私钥和一个“有效”的证书：它已由本地的 CA 签名。该文件的格式可以给 Nginx、HAProxy 或大多数其他 HTTPS 服务器使用。

通过将此逻辑用在测试脚本中，只要客户端配置信任该 CA，那么就可以轻松创建看起来真实的 HTTPS 服务器。

---

via: <https://opensource.com/article/19/4/certificate-authority>

作者：[Moshe Zadka](https://opensource.com/users/moshez/users/elenajon123) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Zettlr：适合写作者和研究人员的 Markdown 编辑器

Typora是目前我在用的markdown编辑器，觉得有一点不方便的是，经常忘记文中引用的图片会删掉。即使在设置中做过设置。

有很多[适用于 Linux 的 Markdown 编辑器](https://itsfoss.com/best-markdown-editors-linux/)，并且还在继续增加。问题是，像 [Boostnote](https://itsfoss.com/boostnote-linux-review/) 一样，大多数是为编码人员设计的，可能不会受到非技术人员的欢迎。让我们看一个想要替代 Word 和昂贵的文字处理器，适用于非技术人员的 Markdown 编辑器。我们来看看 Zettlr 吧。

### Zettlr Markdown 编辑器

![Zettlr Light Mode](/data/attachment/album/201906/01/123458wbnmt4mqnnoffe30.png)

我可能在网站上提到过一两次，我更喜欢用 [Markdown](https://daringfireball.net/projects/markdown/) 写下我的所有文档。它易于学习，不会让你受困于专有文档格式。我还在我的[适合作者的开源工具列表](https://itsfoss.com/open-source-tools-writers/)中提到了 Markdown 编辑器。

我用过许多 Markdown 编辑器，但是我一直有兴趣尝试新的。最近，我遇到了 Zettlr，一个开源 Markdown 编辑器。

[Zettlr](https://www.zettlr.com/) 是一位名叫 [Hendrik Erz](https://github.com/nathanlesage) 的德国社会学家/政治理论家创建的。Hendrik 创建了 Zettlr，因为他对目前的文字处理器感到不满意。他想要可以让他“专注于写作和阅读”的编辑器。

在发现 Markdown 之后，他在不同的操作系统上尝试了几个 Markdown 编辑器。但它们都没有他想要的东西。[根据 Hendrik 的说法](https://www.zettlr.com/about)，“但我不得不意识到没有为高效组织大量文本而写的编辑器。大多数编辑都是由编码人员编写的，因此可以满足工程师和数学家的需求。没有为我这样的社会科学、历史或政治学的学生的编辑器。“

所以他决定创造自己的。2017 年 11 月，他开始编写 Zettlr。

![Zettlr About](/data/attachment/album/201906/01/123501cxnsnqs6qx5pniqn.png)

#### Zettlr 功能

Zettlr 有许多简洁的功能，包括：

* 从 [Zotero 数据库](https://www.zotero.org/)导入源并在文档中引用它们 \* 使用可选的行屏蔽，让你无打扰地专注于写作 \* 支持代码高亮 \* 使用标签对信息进行排序 \* 能够为该任务设定写作目标 \* 查看一段时间的写作统计 \* 番茄钟计时器 \* 浅色/深色主题 \* 使用 [reveal.js](https://revealjs.com/#/) 创建演示文稿 \* 快速预览文档 \* 可以在一个项目文件夹中搜索 Markdown 文档，并用热图展示文字搜索密度。 \* 将文件导出为 HTML、PDF、ODT、DOC、reStructuredText、LaTex、TXT、Emacs ORG、[TextBundle](http://textbundle.org/) 和 Textpack \* 将自定义 CSS 添加到你的文档

当我写这篇文章时，一个对话框弹出来告诉我最近发布了 [1.3.0 beta](https://github.com/Zettlr/Zettlr/releases/tag/v1.3.0-beta)。此测试版将包括几个新的主题，以及一大堆修复，新功能和改进。

![Zettlr Night Mode](/data/attachment/album/201906/01/123504aht3foujhwuuq9gw.png)

#### 安装 Zettlr

目前，唯一可安装 Zettlr 的 Linux 存储库是 [AUR](https://aur.archlinux.org/packages/zettlr-bin/)。如果你的 Linux 发行版不是基于 Arch 的，你可以从网站上下载 macOS、Windows、Debian 和 Fedora 的[安装程序](https://www.zettlr.com/download)。

#### 对 Zettlr 的最后一点想法

注意：为了测试 Zettlr，我用它来写这篇文章。

Zettlr 有许多我希望我之前选择的编辑器 （ghostwriter） 有的简洁的功能，例如为文档设置字数目标。我也喜欢在不打开文档的情况下预览文档的功能。

![Zettlr Settings](/data/attachment/album/201906/01/123506ikk8aa1e9ig81gez.png)

我也遇到了几个问题，但这些更多的是因为 Zettlr 与 ghostwriter 的工作方式略有不同。例如，当我尝试从网站复制引用或名称时，它会将内嵌样式粘贴到 Zettlr 中。幸运的是，它有一个“不带样式粘贴”的选项。有几次我在打字时有轻微的延迟。但那可能是因为它是一个 Electron 程序。

总的来说，我认为 Zettlr 是第一次使用 Markdown 用户的好选择。它有许多 Markdown 编辑器已有的功能，并为那些只使用过文字处理器的用户增加了一些功能。

正如 Hendrik 在 [Zettlr 网站](https://www.zettlr.com/about)中所说的那样，“让自己摆脱文字处理器的束缚，看看你的写作过程如何通过身边的技术得到改善！”

如果你觉得 Zettlr 有用，请考虑支持 [Hendrik](https://www.zettlr.com/supporters)。正如他在网站上所说，“它是免费的，因为我不相信激烈竞争、早逝的创业文化。我只是想帮忙。”

你有没有用过 Zettlr？你最喜欢的 Markdown 编辑器是什么？请在下面的评论中告诉我们。

如果你觉得这篇文章有趣，请在社交媒体，Hacker News 或 [Reddit](http://reddit.com/r/linuxusersgroup) 上分享它。

---

via: <https://itsfoss.com/zettlr-markdown-editor/>

作者：[John Paul](https://itsfoss.com/author/john/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出