开始使用开源待办事项清单管理器

# 开始使用开源待办事项清单管理器

>
> 待办事项清单是跟踪任务列表的强大方法。在我们的 20 个使用开源提升生产力的系列的第七篇文章中了解如何使用它。
>
>
>

去年，我在 19 天里给你介绍了 19 个新（对你而言）的生产力工具。今年，我换了一种方式：使用你在使用或者还没使用的工具，构建一个使你可以在新一年更加高效的环境。

### 使用 todo 跟踪任务

任务管理和待办事项清单是我非常喜欢0的东西。我是一位生产效率的狂热粉丝（以至于我为此做了一个[播客](https://productivityalchemy.com/)），我尝试了各种不同的应用。我甚至为此[做了演讲](https://www.slideshare.net/AllThingsOpen/getting-to-done-on-the-command-line)并[写了些文章](https://opensource.com/article/18/2/getting-to-done-agile-linux-command-line)。因此，当我谈到提高工作效率时，肯定会出现任务管理和待办事项清单工具。

说实话，由于简单、跨平台且易于同步，用 [todo.txt](http://todotxt.org/) 肯定不会错。它是我不断反复提到的两个待办事项清单以及任务管理应用之一（另一个是 [Org 模式](https://orgmode.org/)）。让我反复使用它的原因是它简单、可移植、易于理解，并且有许多很好的附加组件，并且当一台机器有附加组件，而另一台没有，也不会破坏它。由于它是一个 Bash shell 脚本，我还没发现一个无法支持它的系统。

#### 设置 todo.txt

首先，你需要安装基本 shell 脚本并将默认配置文件复制到 `~/.todo` 目录：

```
git clone https://github.com/todotxt/todo.txt-cli.git
cd todo.txt-cli
make
sudo make install
mkdir ~/.todo
cp todo.cfg ~/.todo/config
```

接下来，设置配置文件。一般，我想取消对颜色设置的注释，但必须马上设置的是 `TODO_DIR` 变量：

```
export TODO_DIR="$HOME/.todo"
```

#### 添加待办事件

要添加第一个待办事件，只需输入 `todo.sh add <NewTodo>` 就能添加。这还将在 `$HOME/.todo/` 中创建三个文件：`todo.txt`、`done.txt` 和 `reports.txt`。

添加几个项目后，运行 `todo.sh ls` 查看你的待办事项。

#### 管理任务

你可以通过给项目设置优先级来稍微改善它。要向项目添加优先级，运行 `todo.sh pri # A`。数字是列表中任务的数量，而字母 `A` 是优先级。你可以将优先级设置为从 A 到 Z，因为这是它的排序方式。

要完成任务，运行 `todo.sh do #` 来标记项目已完成并将它移动到 `done.txt`。运行 `todo.sh report` 会向 `report.txt` 写入已完成和未完成项的数量。

所有这三个文件的格式都有详细的说明，因此你可以使用你的文本编辑器修改。`todo.txt` 的基本格式是：

```
(Priority) YYYY-MM-DD Task
```

该日期表示任务的到期日期（如果已设置）。手动编辑文件时，只需在任务前面加一个 `x` 来标记为已完成。运行 `todo.sh archive` 会将这些项目移动到 `done.txt`，你可以编辑该文本文件，并在有时间时将已完成的项目归档。

#### 设置重复任务

我有很多重复的任务，我需要以每天/周/月来计划。

这就是 `todo.txt` 的灵活性所在。通过在 `~/.todo.actions.d/` 中使用[附加组件](https://github.com/todotxt/todo.txt-cli/wiki/Todo.sh-Add-on-Directory)，你可以添加命令并扩展基本 `todo.sh` 的功能。附加组件基本上是实现特定命令的脚本。对于重复执行的任务，插件 [ice\_recur](https://github.com/rlpowell/todo-text-stuff) 应该符合要求。按照其页面上的说明操作，你可以设置任务以非常灵活的方式重复执行。

在该[附加组件目录](https://github.com/todotxt/todo.txt-cli/wiki/Todo.sh-Add-on-Directory)中有很多附加组件，包括同步到某些云服务，也有链接到桌面或移动端应用的组件，这样你可以随时看到待办列表。

我只是简单介绍了这个代办事项清单功能，请花点时间深入了解这个工具的强大！它确实可以帮助我每天完成任务。

---

via: <https://opensource.com/article/20/1/open-source-to-do-list>

作者：[Kevin Sonney](https://opensource.com/users/ksonney) 选题：[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/) 荣誉推出


Ubuntu 中的 root 用户：你应该知道的重要事情

赞，清晰易懂

# Ubuntu 中的 root 用户：你应该知道的重要事情

当你刚开始使用 Linux 时，你将发现与 Windows 的很多不同。其中一个“不同的东西”是 root 用户的概念。

在这个初学者系列中，我将解释几个关于 Ubuntu 的 root 用户的重要的东西。

**请记住，尽管我正在从 Ubuntu 用户的角度编写这篇文章，它应该对大多数的 Linux 发行版也是有效的。**

你将在这篇文章中学到下面的内容：

* 为什么在 Ubuntu 中禁用 root 用户
* 像 root 用户一样使用命
* 切换为 root 用户
* 解锁 root 用户

### 什么是 root 用户？为什么它在 Ubuntu 中被锁定？

在 Linux 中，有一个称为 [root](http://www.linfo.org/root.html) 的超级用户。这是超级管理员账号，它可以做任何事以及使用系统的一切东西。它可以在你的 Linux 系统上访问任何文件和运行任何命令。

能力越大，责任越大。root 用户给予你完全控制系统的能力，因此，它应该被谨慎地使用。root 用户可以访问系统文件，运行更改系统配置的命令。因此，一个错误的命令可能会破坏系统。

这就是为什么 [Ubuntu](https://ubuntu.com/) 和其它基于 Ubuntu 的发行版默认锁定 root 用户，以从意外的灾难中挽救你的原因。

对于你的日常任务，像移动你家目录中的文件，从互联网下载文件，创建文档等等，你不需要拥有 root 权限。

**打个比方来更好地理解它。假设你想要切一个水果，你可以使用一把厨房用刀。假设你想要砍一颗树，你就得使用一把锯子。现在，你可以使用锯子来切水果，但是那不明智，不是吗？**\_

这意味着，你不能是 Ubuntu 中 root 用户或者不能使用 root 权限来使用系统吗？不，你仍然可以在 `sudo` 的帮助下来拥有 root 权限来访问(在下一节中解释)。

>
> **要点：** 使用于常规任务，root 用户权限太过强大。这就是为什么不建议一直使用 root 用户。你仍然可以使用 root 用户来运行特殊的命令。
>
>
>

### 如何在 Ubuntu 中像 root 用户一样运行命令？

对于一些系统的特殊任务来说，你将需要 root 权限。例如。如果你想[通过命令行更新 Ubuntu](https://itsfoss.com/update-ubuntu/)，你不能作为一个常规用户运行该命令。它将给出权限被拒绝的错误。

```
apt update
Reading package lists... Done
E: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)
E: Unable to lock directory /var/lib/apt/lists/
W: Problem unlinking the file /var/cache/apt/pkgcache.bin - RemoveCaches (13: Permission denied)
W: Problem unlinking the file /var/cache/apt/srcpkgcache.bin - RemoveCaches (13: Permission denied)
```

那么，你如何像 root 用户一样运行命令？简单的答案是，在命令前添加 `sudo`，来像 root 用户一样运行。

```
sudo apt update
```

Ubuntu 和很多其它的 Linux 发行版使用一个被称为 `sudo` 的特殊程序机制。`sudo` 是一个以 root 用户（或其它用户）来控制运行命令访问的程序。

实际上，`sudo` 是一个非常多用途的工具。它可以配置为允许一个用户像 root 用户一样来运行所有的命令，或者仅仅一些命令。你也可以配置为无需密码即可使用 sudo 运行命令。这个主题内容比较丰富，也许我将在另一篇文章中详细讨论它。

就目前而言，你应该知道[当你安装 Ubuntu 时](https://itsfoss.com/install-ubuntu/)，你必须创建一个用户账号。这个用户账号在你系统上以管理员身份来工作，并且按照 Ubuntu 中的默认 sudo 策略，它可以在你的系统上使用 root 用户权限来运行任何命令。

`sudo` 的问题是，运行 **sudo 不需要 root 用户密码，而是需要用户自己的密码**。

并且这就是为什么当你使用 `sudo` 运行一个命令，会要求输入正在运行 `sudo` 命令的用户的密码的原因：

```
[email protected]:~$ sudo apt update
[sudo] password for abhishek:
```

正如你在上面示例中所见 `abhishek` 在尝试使用 `sudo` 来运行 `apt update` 命令，系统要求输入 `abhishek` 的密码。

**如果你对 Linux 完全不熟悉，当你在终端中开始输入密码时，你可能会惊讶，在屏幕上什么都没有发生。这是十分正常的，因为作为默认的安全功能，在屏幕上什么都不会显示。甚至星号（`*`）都没有。输入你的密码并按回车键。**

>
> **要点：**为在 Ubuntu 中像 root 用户一样运行命令，在命令前添加 `sudo`。 当被要求输入密码时，输入你的账户的密码。当你在屏幕上输入密码时，什么都看不到。请继续输入密码，并按回车键。
>
>
>

### 如何在 Ubuntu 中成为 root 用户？

你可以使用 `sudo` 来像 root 用户一样运行命令。但是，在某些情况下，你必须以 root 用户身份来运行一些命令，而你总是忘了在命令前添加 `sudo`，那么你可以临时切换为 root 用户。

`sudo` 命令允许你来模拟一个 root 用户登录的 shell ，使用这个命令：

```
sudo -i
```

```
[email protected]:~$ sudo -i
[sudo] password for abhishek:
[email protected]:~# whoami
root

```

你将注意到，当你切换为 root 用户时，shell 命令提示符从 `$`（美元符号）更改为 `#`（英镑符号）。我开个（拙劣的）玩笑，英镑比美元强大。

**虽然我已经向你显示如何成为 root 用户，但是我必须警告你，你应该避免作为 root 用户使用系统。毕竟它有阻拦你使用 root 用户的原因。**

另外一种临时切换为 root 用户的方法是使用 `su` 命令：

```
sudo su
```

如果你尝试使用不带有的 `sudo` 的 `su` 命令，你将遇到 “su authentication failure” 错误。

你可以使用 `exit` 命令来恢复为正常用户。

```
exit
```

### 如何在 Ubuntu 中启用 root 用户？

现在你知道，root 用户在基于 Ubuntu 发行版中是默认锁定的。

Linux 给予你在系统上想做什么就做什么的自由。解锁 root 用户就是这些自由之一。

如果出于某些原因，你决定启用 root 用户，你可以通过为其设置一个密码来做到：

```
sudo passwd root
```

再强调一次，不建议使用 root 用户，并且我也不鼓励你在桌面上这样做。如果你忘记了密码，你将不能再次[在 Ubuntu 中更改 root 用户密码](https://itsfoss.com/how-to-hack-ubuntu-password/)。（LCTT 译注：可以通过单用户模式修改。）

你可以通过移除密码来再次锁定 root 用户：

```
sudo passwd -dl root
```

### 最后…

我希望你现在对 root 概念理解得更好一点。如果你仍然有些关于它的困惑和问题，请在评论中让我知道。我将尝试回答你的问题，并且也可能更新这篇文章。

---

via: <https://itsfoss.com/root-user-ubuntu/>

作者：[Abhishek Prakash](https://itsfoss.com/author/abhishek/) 选题：[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/) 荣誉推出


如何在 Ubuntu Linux 中设置或更改时区

# 如何在 Ubuntu Linux 中设置或更改时区

[你安装 Ubuntu 时](https://itsfoss.com/install-ubuntu/)，它会要求你设置时区。如果你选择一个错误的时区，或者你移动到世界的一些其它地方，你可以很容易地在以后更改它。

### 如何在 Ubuntu 和其它 Linux 发行版中更改时区

这里有两种方法来更改 Ubuntu 中的时区。你可以使用图形化设置或在终端中使用 `timedatectl` 命令。你也可以直接更改 `/etc/timezone` 文件，但是我不建议这样做。

在这篇初学者教程中，我将向你展示图形化和终端两种方法：

* [通过 GUI 更改 Ubuntu 中的时区](tmp.bHvVztzy6d#change-timezone-gui) （适合桌面用户）
* [通过命令行更改 Ubuntu 中的时区](tmp.bHvVztzy6d#change-timezone-command-line) （桌面和服务器都工作）

#### 方法 1: 通过终端更改 Ubuntu 时区

[Ubuntu](https://ubuntu.com/) 或一些使用 systemd 的其它发行版可以在 Linux 终端中使用 `timedatectl` 命令来设置时区。

你可以使用没有任何参数的 `timedatectl` 命令来检查当前是日期和时区设置：

```
[email protected]:~$ timedatectl
                      Local time: Sat 2020-01-18 17:39:52 IST
                  Universal time: Sat 2020-01-18 12:09:52 UTC
                        RTC time: Sat 2020-01-18 12:09:52
                       Time zone: Asia/Kolkata (IST, +0530)
       System clock synchronized: yes
systemd-timesyncd.service active: yes
                 RTC in local TZ: no
```

正如你在上面的输出中所看，我的系统使用 Asia/Kolkata 。它也告诉我现在比世界时早 5 小时 30 分钟。

为在 Linux 中设置时区，你需要知道准确的时区。你必需使用时区的正确的格式 (时区格式是洲/城市)。

为获取时区列表，使用 `timedatectl` 命令的 `list-timezones` 参数:

```
timedatectl list-timezones
```

它将向你显示大量可用的时区列表。

你可以使用向上箭头和向下箭头或 `PgUp` 和 `PgDown` 键来在页面之间移动。

你也可以 `grep` 输出，并搜索你的时区。例如，假如你正在寻找欧洲的时区，你可以使用：

```
timedatectl list-timezones | grep -i europe
```

比方说，你想设置时区为巴黎。在这里，使用的时区值的 Europe/Paris ：

```
timedatectl set-timezone Europe/Paris
```

它虽然不显示任何成功信息，但是时区会立即更改。你不需要重新启动或注销。

记住，虽然你不需要成为 root 用户并对命令使用 `sudo`，但是你的账户仍然需要拥有管理器权限来更改时区。

你可以使用 [date 命令](https://linuxhandbook.com/date-command/) 来验证更改的时间好时区：

```
[email protected]:~$ date
Sat Jan 18 13:56:26 CET 2020
```

#### 方法 2: 通过 GUI 更改 Ubuntu 时区

按下 `super` 键 (Windows 键) ，并搜索设置：

在左侧边栏中，向下滚动一点，查看详细信息：

 Details" title="Go to Settings -> Details">

在详细信息中，你将在左侧边栏中找到“日期和时间”。在这里，你应该关闭自动时区选项（如果它已经被启用），然后在时区上单击：

 Date & Time, turn off the Automatic Time Zone" title="In Details -> Date & Time, turn off the Automatic Time Zone">

当你单击时区时，它将打开一个交互式地图，你可以在你选择的地理位置上单击，关闭窗口。

在选择新的时区后，除了关闭这个地图后，你不必做任何事情。不需要注销或 [关闭 Ubuntu](https://itsfoss.com/schedule-shutdown-ubuntu/)。

我希望这篇快速教程能帮助你在 Ubuntu 和其它 Linux 发行版中更改时区。如果你有问题或建议，请告诉我。

---

via: <https://itsfoss.com/change-timezone-ubuntu/>

作者：[Abhishek Prakash](https://itsfoss.com/author/abhishek/) 选题：[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/) 荣誉推出


添加计分到你的 Python 游戏

# 添加计分到你的 Python 游戏

>
> 在本系列的第十一篇有关使用 Python Pygame 模块进行编程的文章中，显示玩家获得战利品或受到伤害时的得分。
>
>
>

这是仍在进行中的关于使用 [Pygame](https://www.pygame.org/news) 模块来在 [Python 3](https://www.python.org/) 在创建电脑游戏的第十一部分。先前的文章是：

* [通过构建一个简单的掷骰子游戏去学习怎么用 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 游戏中放置平台](/article-10902-1.html)
* [在你的 Python 游戏中模拟引力](/article-11780-1.html)
* [为你的 Python 平台类游戏添加跳跃功能](/article-11790-1.html)
* [使你的 Python 游戏玩家能够向前和向后跑](/article-11819-1.html)
* [在你的 Python 平台类游戏中放一些奖励](/article-11828-1.html)

如果你已经跟随这一系列很久，那么已经学习了使用 Python 创建一个视频游戏所需的所有基本语法和模式。然而，它仍然缺少一个至关重要的组成部分。这一组成部分不仅仅对用 Python 编程游戏重要；不管你探究哪个计算机分支，你都必需精通：作为一个程序员，通过阅读一种语言的或库的文档来学习新的技巧。

幸运的是，你正在阅读本文的事实表明你熟悉文档。为了使你的平台类游戏更加美观，在这篇文章中，你将在游戏屏幕上添加得分和生命值显示。不过，教你如何找到一个库的功能以及如何使用这些新的功能的这节课程并没有多神秘。

### 在 Pygame 中显示得分

现在，既然你有了可以被玩家收集的奖励，那就有充分的理由来记录分数，以便你的玩家看到他们收集了多少奖励。你也可以跟踪玩家的生命值，以便当他们被敌人击中时会有相应结果。

你已经有了跟踪分数和生命值的变量，但是这一切都发生在后台。这篇文章教你在游戏期间在游戏屏幕上以你选择的一种字体来显示这些统计数字。

### 阅读文档

大多数 Python 模块都有文档，即使那些没有文档的模块，也能通过 Python 的帮助功能来进行最小的文档化。[Pygame 的主页面](http://pygame.org/news) 链接了它的文档。不过，Pygame 是一个带有很多文档的大模块，并且它的文档不像在 Opensource.com 上的文章一样，以同样易理解的（和友好的、易解释的、有用的）叙述风格来撰写的。它们是技术文档，并且列出在模块中可用的每个类和函数，各自要求的输入类型等等。如果你不适应参考代码组件描述，这可能会令人不知所措。

在烦恼于库的文档前，第一件要做的事，就是来想想你正在尝试达到的目标。在这种情况下，你想在屏幕上显示玩家的得分和生命值。

在你确定你需要的结果后，想想它需要什么的组件。你可以从变量和函数的方面考虑这一点，或者，如果你还没有自然地想到这一点，你可以进行一般性思考。你可能意识到需要一些文本来显示一个分数，你希望 Pygame 在屏幕上绘制这些文本。如果你仔细思考，你可能会意识到它与在屏幕上渲染一个玩家、奖励或一个平台并多么大的不同。

从技术上讲，你*可以*使用数字图形，并让 Pygame 显示这些数字图形。它不是达到你目标的最容易的方法，但是如果它是你唯一知道的方法，那么它是一个有效的方法。不过，如果你参考 Pygame 的文档，你看到列出的模块之一是 `font`，这是 Pygame 使得在屏幕上来使打印文本像输入文字一样容易的方法。

### 解密技术文档

`font` 文档页面以 `pygame.font.init()` 开始，它列出了用于初始化字体模块的函数。它由 `pygame.init()` 自动地调用，你已经在代码中调用了它。再强调一次，从技术上讲，你已经到达一个*足够好*的点。虽然你尚不知道*如何做*，你知道你*能够*使用 `pygame.font` 函数来在屏幕上打印文本。

然而，如果你阅读更多一些，你会找到这里还有一种更好的方法来打印字体。`pygame.freetype` 模块在文档中的描述方式如下：

>
> `pygame.freetype` 模块是 `pygame.fontpygame` 模块的一个替代品，用于加载和渲染字体。它有原函数的所有功能，外加很多新的功能。
>
>
>

在 `pygame.freetype` 文档页面的下方，有一些示例代码：

```
import pygame
import pygame.freetype
```

你的代码应该已经导入了 Pygame，不过，请修改你的 `import` 语句以包含 Freetype 模块：

```
import pygame
import sys
import os
import pygame.freetype
```

### 在 Pygame 中使用字体

从 `font` 模块的描述中可以看出，显然 Pygame 使用一种字体（不管它的你提供的或内置到 Pygame 的默认字体）在屏幕上渲染字体。滚动浏览 `pygame.freetype` 文档来找到 `pygame.freetype.Font` 函数：

```
pygame.freetype.Font
从支持的字体文件中创建一个新的字体实例。

Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font

pygame.freetype.Font.name
  符合规则的字体名称。

pygame.freetype.Font.path
  字体文件路径。

pygame.freetype.Font.size
  在渲染中使用的默认点大小
```

这描述了如何在 Pygame 中构建一个字体“对象”。把屏幕上的一个简单对象视为一些代码属性的组合对你来说可能不太自然，但是这与你构建英雄和敌人精灵的方式非常类似。你需要一个字体文件，而不是一个图像文件。在你有一个字体文件后，你可以在你的代码中使用 `pygame.freetype.Font` 函数来创建一个字体对象，然后使用该对象来在屏幕上渲染文本。

因为并不是世界上的每个人的电脑上都有完全一样的字体，因此将你选择的字体与你的游戏捆绑在一起是很重要的。要捆绑字体，首先在你的游戏文件夹中创建一个新的目录，放在你为图像而创建的文件目录旁边。称其为 `fonts` 。

即使你的计算机操作系统随附了几种字体，但是将这些字体给予其他人是非法的。这看起来很奇怪，但法律就是这样运作的。如果想与你的游戏一起随附一种字体，你必需找到一种开源或知识共享的字体，以允许你随游戏一起提供该字体。

专门提供自由和合法字体的网站包括：

* [Font Library](https://fontlibrary.org/)
* [Font Squirrel](https://www.fontsquirrel.com/)
* [League of Moveable Type](https://www.theleagueofmoveabletype.com/)

当你找到你喜欢的字体后，下载下来。解压缩 ZIP 或 [TAR](https://opensource.com/article/17/7/how-unzip-targz-file) 文件，并移动 `.ttf` 或 `.otf` 文件到你的项目目录下的 `fonts` 文件夹中。

你没有安装字体到你的计算机上。你只是放置字体到你游戏的 `fonts` 文件夹中，以便 Pygame 可以使用它。如果你想，你*可以*在你的计算机上安装该字体，但是没有必要。重要的是将字体放在你的游戏目录中，这样 Pygame 可以“描绘”字体到屏幕上。

如果字体文件的名称复杂且带有空格或特殊字符，只需要重新命名它即可。文件名称是完全任意的，并且对你来说，文件名称越简单，越容易将其键入你的代码中。

现在告诉 Pygame 你的字体。从文档中你知道，当你至少提供了字体文件路径给 `pygame.freetype.Font` 时（文档明确指出所有其余属性都是可选的），你将在返回中获得一个字体对象：

```
Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font
```

创建一个称为 `myfont` 的新变量来充当你在游戏中字体，并放置 `Font` 函数的结果到这个变量中。这个示例中使用 `amazdoom.ttf` 字体，但是你可以使用任何你想使用的字体。在你的设置部分放置这些代码：

```
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf")
font_size = tx
myfont = pygame.freetype.Font(font_path, font_size)
```

### 在 Pygame 中显示文本

现在你已经创建一个字体对象，你需要一个函数来绘制你想绘制到屏幕上的文本。这和你在你的游戏中绘制背景和平台是相同的原理。

首先，创建一个函数，并使用 `myfont` 对象来创建一些文本，设置颜色为某些 RGB 值。这必须是一个全局函数；它不属于任何具体的类：

```
def stats(score,health):
    myfont.render_to(world, (4, 4), "Score:"+str(score), WHITE, None, size=64)
    myfont.render_to(world, (4, 72), "Health:"+str(health), WHITE, None, size=64)
```

当然，你此刻已经知道，如果它不在主循环中，你的游戏将不会发生任何事，所以在文件的底部添加一个对你的 `stats` 函数的调用：

```
    for e in enemy_list:
        e.move()
    stats(player.score,player.health) # draw text
    pygame.display.flip()
```

尝试你的游戏。

当玩家收集奖励品时，得分会上升。当玩家被敌人击中时，生命值下降。成功！

不过，这里有一个问题。当一个玩家被敌人击中时，健康度会*一路*下降，这是不公平的。你刚刚发现一个非致命的错误。非致命的错误是这些在应用程序中小问题，（通常）不会阻止应用程序启动或甚至导致停止工作，但是它们要么没有意义，要么会惹恼用户。这里是如何解决这个问题的方法。

### 修复生命值计数

当前生命值系统的问题是，敌人接触玩家时，Pygame 时钟的每一次滴答，健康度都会减少。这意味着一个缓慢移动的敌人可能在一次遭遇中将一个玩家降低健康度至 -200 ，这不公平。当然，你可以给你的玩家一个 10000 的起始健康度得分，而不用担心它；这可以工作，并且可能没有人会注意。但是这里有一个更好的方法。

当前，你的代码侦查出一个玩家和一个敌人发生碰撞的时候。生命值问题的修复是检测*两个*独立的事件：什么时候玩家和敌人碰撞，并且，在它们碰撞后，什么时候它们*停止*碰撞。

首先，在你的玩家类中，创建一个变量来代表玩家和敌人碰撞在一起：

```
        self.frame = 0
        self.health = 10
        self.damage = 0
```

在你的 `Player` 类的 `update` 函数中，*移除*这块代码块：

```
        for enemy in enemy_hit_list:
            self.health -= 1
            #print(self.health)
```

并且在它的位置，只要玩家当前没有被击中，检查碰撞：

```
        if self.damage == 0:
            for enemy in enemy_hit_list:
                if not self.rect.contains(enemy):
                    self.damage = self.rect.colliderect(enemy)
```

你可能会在你删除的语句块和你刚刚添加的语句块之间看到相似之处。它们都在做相同的工作，但是新的代码更复杂。最重要的是，只有当玩家*当前*没有被击中时，新的代码才运行。这意味着，当一个玩家和敌人碰撞时，这些代码运行一次，而不是像以前那样一直发生碰撞。

新的代码使用两个新的 Pygame 函数。`self.rect.contains` 函数检查一个敌人当前是否在玩家的边界框内，并且当它是 `true` 时， `self.rect.colliderect` 设置你的新的 `self.damage` 变量为 1，而不管它多少次是 `true` 。

现在，即使被一个敌人击中 3 秒，对 Pygame 来说仍然看作一次击中。

我通过通读 Pygame 的文档而发现了这些函数。你没有必要一次阅读完全部的文档，并且你也没有必要阅读每个函数的每个单词。不过，花费时间在你正在使用的新的库或模块的文档上是很重要的；否则，你极有可能在重新发明轮子。不要花费一个下午的时间来尝试修改拼接一个解决方案到一些东西，而这些东西已经被你正在使用的框架的所解决。阅读文档，知悉函数，并从别人的工作中获益！

最后，添加另一个代码语句块来侦查出什么时候玩家和敌人不再接触。然后直到那时，才从玩家减少一个生命值。

```
        if self.damage == 1:
            idx = self.rect.collidelist(enemy_hit_list)
            if idx == -1:
                self.damage = 0   # set damage back to 0
                self.health -= 1  # subtract 1 hp
```

注意，*只有*当玩家被击中时，这个新的代码才会被触发。这意味着，在你的玩家在你的游戏世界正在探索或收集奖励时，这个代码不会运行。它仅当 `self.damage` 变量被激活时运行。

当代码运行时，它使用 `self.rect.collidelist` 来查看玩家是否*仍然*接触在你敌人列表中的敌人（当其未侦查到碰撞时，`collidelist` 返回 -1）。在它没有接触敌人时，是该处理 `self.damage` 的时机：通过设置 `self.damage` 变量回到 0 来使其无效，并减少一点生命值。

现在尝试你的游戏。

### 得分反应

现在，你有一个来让你的玩家知道它们分数和生命值的方法，当你的玩家达到某些里程碑时，你可以确保某些事件发生。例如，也许这里有一个特殊的恢复一些生命值的奖励项目。也许一个到达 0 生命值的玩家不得不从一个关卡的起始位置重新开始。

你可以在你的代码中检查这些事件，并且相应地操纵你的游戏世界。你已经知道该怎么做，所以请浏览文档来寻找新的技巧，并且独立地尝试这些技巧。

这里是到目前为止所有的代码：

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

# 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
import pygame.freetype

'''
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.damage = 0
        self.collide_delta = 0
        self.jump_delta = 6
        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 jump(self,platform_list):
        self.jump_delta = 0

    def gravity(self):
        self.movey += 3.2 # how fast player falls

        if self.rect.y > worldy and self.movey >= 0:
            self.movey = 0
            self.rect.y = worldy-ty

    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)
        if self.damage == 0:
            for enemy in enemy_hit_list:
                if not self.rect.contains(enemy):
                    self.damage = self.rect.colliderect(enemy)

        if self.damage == 1:
            idx = self.rect.collidelist(enemy_hit_list)
            if idx == -1:
                self.damage = 0   # set damage back to 0
                self.health -= 1  # subtract 1 hp

        loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False)
        for loot in loot_hit_list:
            loot_list.remove(loot)
            self.score += 1
            print(self.score)

        plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
        for p in plat_hit_list:
            self.collide_delta = 0 # stop jumping
            self.movey = 0
            if self.rect.y > p.rect.y:
                self.rect.y = p.rect.y+ty
            else:
                self.rect.y = p.rect.y-ty

        ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
        for g in ground_hit_list:
            self.movey = 0
            self.rect.y = worldy-ty-ty
            self.collide_delta = 0 # stop jumping
            if self.rect.y > g.rect.y:
                self.health -=1
                print(self.health)

        if self.collide_delta < 6 and self.jump_delta < 6:
            self.jump_delta = 6*2
            self.movey -= 33  # how high to jump
            self.collide_delta += 6
            self.jump_delta    += 6

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.movey = 0
        #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

        self.movey += 3.2

        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

        if not self.rect.y >= worldy-ty-ty:
            self.rect.y += self.movey

        plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
        for p in plat_hit_list:
            self.movey = 0
            if self.rect.y > p.rect.y:
                self.rect.y = p.rect.y+ty
            else:
                self.rect.y = p.rect.y-ty

        ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
        for g in ground_hit_list:
            self.rect.y = worldy-ty-ty

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,tx,ty):
        if lvl == 1:
            loot_list = pygame.sprite.Group()
            loot = Platform(200,ty*7,tx,ty, 'loot_1.png')
            loot_list.add(loot)

        if lvl == 2:
            print(lvl)

        return loot_list

    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,'ground.png')
                ground_list.add(ground)
                i=i+1

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

        return ground_list

    def platform(lvl,tx,ty):
        plat_list = pygame.sprite.Group()
        ploc = []
        i=0
        if lvl == 1:
            ploc.append((20,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,'ground.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

def stats(score,health):
    myfont.render_to(world, (4, 4), "Score:"+str(score), SNOWGRAY, None, size=64)
    myfont.render_to(world, (4, 72), "Health:"+str(health), SNOWGRAY, None, size=64)

'''
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)
SNOWGRAY = (137,164,166)
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
forwardx = 600
backwardx = 230

eloc = []
eloc = [200,20]
gloc = []
tx = 64 #tile size
ty = 64 #tile size

font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf")
font_size = tx
myfont = pygame.freetype.Font(font_path, font_size)

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

enemy_list = Level.bad( 1, eloc )
ground_list = Level.ground( 1,gloc,tx,ty )
plat_list = Level.platform( 1,tx,ty )
loot_list = Level.loot(1,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'):
                print("LEFT")
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print("RIGHT")
                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 == pygame.K_UP or event.key == ord('w'):
                player.jump(plat_list)

            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

    # scroll the world forward
    if player.rect.x >= forwardx:
        scroll = player.rect.x - forwardx
        player.rect.x = forwardx
        for p in plat_list:
            p.rect.x -= scroll
        for e in enemy_list:
            e.rect.x -= scroll
        for l in loot_list:

            l.rect.x -= scroll

    # scroll the world backward
    if player.rect.x <= backwardx:
        scroll = backwardx - player.rect.x
        player.rect.x = backwardx
        for p in plat_list:
            p.rect.x += scroll
        for e in enemy_list:
            e.rect.x += scroll
        for l in loot_list:
            l.rect.x += scroll

    world.blit(backdrop, backdropbox)
    player.gravity() # check gravity
    player.update()
    player_list.draw(world) #refresh player position
    enemy_list.draw(world)  # refresh enemies
    ground_list.draw(world)  # refresh enemies
    plat_list.draw(world)   # refresh platforms
    loot_list.draw(world)   # refresh loot
    for e in enemy_list:
        e.move()
    stats(player.score,player.health) # draw text
    pygame.display.flip()
    clock.tick(fps)
```

---

via: <https://opensource.com/article/20/1/add-scorekeeping-your-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/) 荣誉推出


我珍藏的 Bash 秘籍

Summary: 通过别名和其他捷径来提高你经常忘记的那些事情的效率。

通过别名和其他捷径来提高你经常忘记的那些事情的效率。

# 我珍藏的 Bash 秘籍

>
> 通过别名和其他捷径来提高你经常忘记的那些事情的效率。
>
>
>

<img src="content_image/album_202002_02_115932hbw7b782swjj7ft5.jpg" alt="" title="">

要是你整天使用计算机，如果能找到需要重复执行的命令并记下它们以便以后轻松使用那就太棒了。它们全都呆在那里，藏在 `~/.bashrc` 中（或 [zsh 用户](https://opensource.com/article/19/9/getting-started-zsh)的 `~/.zshrc` 中），等待着改善你的生活！

在本文中，我分享了我最喜欢的这些助手命令，对于我经常遗忘的事情，它们很有用，也希望这可以帮助到你，以及为你解决一些经常头疼的问题。

### 完事吱一声

当我执行一个需要长时间运行的命令时，我经常采用多任务的方式，然后就必须回头去检查该操作是否已完成。然而通过有用的 `say` 命令，现在就不用再这样了（这是在 MacOS 上；请根据你的本地环境更改为等效的方式）：

```
function looooooooong {
    START=$(date +%s.%N)
    $*
    EXIT_CODE=$?
    END=$(date +%s.%N)
    DIFF=$(echo "$END - $START" | bc)
    RES=$(python -c "diff = $DIFF; min = int(diff / 60); print('%s min' % min)")
    result="$1 completed in $RES, exit code $EXIT_CODE."
    echo -e "\n⏰  $result"
    ( say -r 250 $result 2>&1 > /dev/null & )
}
```

这个命令会记录命令的开始和结束时间，计算所需的分钟数，并“说”出调用的命令、花费的时间和退出码。当简单的控制台铃声无法使用时，我发现这个超级有用。

### 安装小助手

我在小时候就开始使用 Ubuntu，而我需要学习的第一件事就是如何安装软件包。我曾经首先添加的别名之一是它的助手（根据当天的流行梗命名的）：

```
alias canhas="sudo apt-get install -y"
```

### GPG 签名

有时候，我必须在没有 GPG 扩展程序或应用程序的情况下给电子邮件签署 [GPG](https://gnupg.org/) 签名，我会跳到命令行并使用以下令人讨厌的别名：

```
alias gibson="gpg --encrypt --sign --armor"
alias ungibson="gpg --decrypt"
```

### Docker

Docker 的子命令很多，但是 Docker compose 的更多。我曾经使用这些别名来将 `--rm` 标志丢到脑后，但是现在不再使用这些有用的别名了：

```
alias dc="docker-compose"
alias dcr="docker-compose run --rm"
alias dcb="docker-compose run --rm --build"
```

### Google Cloud 的 gcurl 助手

对于我来说，Google Cloud 是一个相对较新的东西，而它有[极多的文档](https://cloud.google.com/service-infrastructure/docs/service-control/getting-started)。`gcurl` 是一个别名，可确保在用带有身份验证标头的本地 `curl` 命令连接 Google Cloud API 时，可以获得所有正确的标头。

### Git 和 ~/.gitignore

我工作中用 Git 很多，因此我有一个专门的部分来介绍 Git 助手。

我最有用的助手之一是我用来克隆 GitHub 存储库的。你不必运行：

```
git clone git@github.com:org/repo /Users/glasnt/git/org/repo
```

我设置了一个克隆函数：

```
clone(){
    echo Cloning $1 to ~/git/$1
    cd ~/git
    git clone git@github.com:$1 $1
    cd $1
}
```

即使每次进入 `~/.bashrc` 文件看到这个时，我总是会忘记和傻笑，我也有一个“刷新上游”命令：

```
alias yoink="git checkout master && git fetch upstream master && git merge upstream/master"
```

给 Git 一族的另一个助手是全局忽略文件。在你的 `git config --global --list` 中，你应该看到一个 `core.excludesfile`。如果没有，请[创建一个](https://help.github.com/en/github/using-git/ignoring-files#create-a-global-gitignore)，然后将你总是放到各个 `.gitignore` 文件中的内容填满它。作为 MacOS 上的 Python 开发人员，对我来说，这些内容是：

```
.DS_Store     # macOS clutter
venv/         # I never want to commit my virtualenv
*.egg-info/*  # ... nor any locally compiled packages
__pycache__   # ... or source
*.swp         # ... nor any files open in vim
```

你可以在 [Gitignore.io](https://www.gitignore.io/) 或 GitHub 上的 [Gitignore 存储库](https://github.com/github/gitignore)上找到其他建议。

### 轮到你了

你最喜欢的助手命令是什么？请在评论中分享。

---

via: <https://opensource.com/article/20/1/bash-scripts-aliases>

作者：[Katie McLaughlin](https://opensource.com/users/glasnt) 选题：[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/) 荣誉推出


“Linux” 小程序发布 Web 版，现在你可以在 PC 上快速查询命令啦！

当我查询完一个命令，想再查询下一个命令的时候。如果能有个查询框就好了（或者给个返回按钮也行）。

啊，不好意思，alt+←好像也可以返回，打扰了

有个命令列表会更好些吧，可买学习不知道的命令。

有个命令列表会更好些吧，可以学习陌生的命令。

# “Linux” 小程序发布 Web 版，现在你可以在 PC 上快速查询命令啦！

“Linux” 小程序是 Linux 中国在 2019 年 2 月（恰恰是去年春节）发布的小程序，该小程序采用众包模式，对著名的 TLDR 项目中的 Linux 命令示例进行了翻译，并通过微信小程序的形式展现。

在过去的一年里，我们和广大的社区贡献者，一同将这个小程序中的内容进行了翻译，如今翻译进度已经接近 100%。在 2020 年的春节，我们面向更多的 PC 端用户，开放了 Web 版的 Linux 命令查询。Web 版和小程序版数据同步，让你在使用桌面计算机时也可以进行命令查询。

<img src="content_image/album_202002_02_123758gp9lljs332zls626.jpg" alt="" title="">

现在，你可以通过 <https://tldr.linux.cn/> 来体验 PC 端的命令查询。

除此之外，为了让这个项目可以更好的发展，我们也将这个项目开源出来，你可以在 [GitHub](https://github.com/LCTT/tldr.linux.cn) 中找到它。**如果觉得不错，不妨在 GitHub 给我们一个星标！**

不仅如此，我们还邀请了本项目的开发者来写了经验分享文章（请关注我们的后继文章），帮助有兴趣的开发者快速上手开发类似的项目，既可以参与到我们的项目协作中，也可以通过学习一些经验，来了解到更多关于 Web 应用开发的知识。


跟我学 “Linux” 小程序 Web 版开发（一）：初始化

Summary: 从今天起，我们将在 Linux 中国官网和公众号上，连载 TL;DR Web 应用开发背后的故事。接下来，请看来自开发 gg 的文章。

# 跟我学 “Linux” 小程序 Web 版开发（一）：初始化

“Linux” 小程序是 Linux 中国在 2019 年 2 月（恰恰是去年春节）发布的小程序，该小程序采用众包模式，对著名的 TLDR 项目中的 Linux 命令示例进行了翻译，并通过微信小程序的形式展现。在 2020 年的春节，我们面向更多的 PC 端用户，开放了 Web 版的 Linux 命令查询。Web 版和小程序版数据同步，让你在使用桌面计算机时也可以进行命令查询。

从今天起，我们将在 Linux 中国官网和公众号上，连载 [TL;DR Web 应用](https://tldr.linux.cn/)开发背后的故事。接下来，请看来自开发 gg 的文章。

### 背景描述

Linux 中国曾在过去的一年开发和运行了一个中国版 TL;DR 客户端。不过当时做的版本是[小程序的版本](/article-10528-1.html)，一直以来，受限于小程序·云开发没有 Web SDK ，因此无法将应用能力迁移到更多的平台上。刚好最近云开发提供了 Web SDK，并已经逐步成熟，于是我们便可以借此机会，将业务实现 PC 化，服务更多人群。

在开发出初步的版本之后，我们决定以这个项目作为范例，将我们开发经验分享给大家，大家可以参考开发自己的云应用。

### 项目设计

在进行项目开发时，先对项目进行了基本的 UI 设计：

这里用到的是 [balsamiq](https://balsamiq.com/index.html) 的手绘线框图来完成产品设计，以避免我个人过度追求完美，而让产品延期迟迟不能上线的问题（这样的事情在历史上发生了非常多次）

### 技术选项

由于需要的是一个前端页面，因此，在技术选型方面，几乎没有太多的异议。使用最为熟悉的技术栈来完成。

* 前端框架：Vue
* 路由器：Vue Router
* CSS 框架：Vuetify.js

### 镜像配置

因为身处国内， npm 的速度必然不太好，因此需要进行相应的镜像设定，确保 npm 和 yarn 在安装依赖。这里使用的是腾讯云提供的镜像。

```
# Npm 设置
npm config set registry http://mirrors.cloud.tencent.com/npm/

# yarn 设置
yarn config set registry http://mirrors.cloud.tencent.com/npm/ -g
```

### 初始化 Vue 项目

首先，需要安装 Vue CLI，以进行项目的生成，这里我已经完成安装，就不再赘述。（Vue CLI 的安装教程[点击这里](https://cli.vuejs.org/zh/guide/)）

执行如下命令初始化项目：

```
vue create tldr
```

等待其完成安装以后，进入项目，并启动项目。

```
cd tldr
yarn serve
```

随即，可以在浏览器中访问 localhost:8080 查看项目：

>
> 记得引入 git 做版本控制，文章里就不介绍了。没意思。
>
>
>

### 安装 Vue Router

在完成 Vue 项目的初始化以后，接下来需要进行 Vue Router 的配置了。

Vue Router 的配置在引入了 Vue 3 以后，显得非常的简单，直接执行如下命令即可：

```
vue add router
```

执行过程中，会问你是否需要启用 History Mode，根据需要选取，我使用的是 History Mode。

设置完成以后，保存并重启 Vue 的开发服务器，你会在预览中看到 Router 添加的 “Home” 和 “About”。

### 安装 Vuetify.js

接下来安装的是 [Vuetify.js](https://vuetifyjs.com/zh-Hans/) ，由于框架提供了相应的支持，因此在开发时也非常简单，只需要执行如下命令就可以完成初始化。

```
vue add vuetify
```

会问你选择那种预设，直接使用 Default 即可。

保存并重启开发服务器，你会看到这样的界面，则说明配置完成。

### 部署测试应用

在进行下一步开发的时候，需要先进行一下项目的部署，从而获得一个测试的域名，方便后续的开发。

这里项目的开发我并没有使用云开发自己的 Web 托管 （因为我们不是按量付费套餐，所以没有办法开启），而是使用了 Now.sh 的，这里就不再过多赘述。

### 引入云开发 SDK

云开发提供了 Web SDK ，可以通过 npm 安装，并引用。

执行如下命令来安装：

```
yarn add tcb-js-sdk
```

安装完成后，在 `main.js` 中引入 tcb，并通过修改 Vue 的原型来实现挂载 Vue。

```
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify';
const tcb = require('tcb-js-sdk')  // 新增的引入 TCB

Vue.config.productionTip = false

Vue.prototype['$tcb'] = tcb.init({ // 新增的修改原型
    env: 'prod-2c59c7'             // 新增的修改原型
})                                 // 新增的修改原型

new Vue({
  router,
  vuetify,
  render: h => h(App)
}).$mount('#app')
```

这样就可以在应用运行的整个周期中使用 `this.$tcb` 来调用云开发的相关逻辑。

### 总结

在完成了项目的初始化以后，回过头来看一看这在初始化项目过程中，都做了哪些事情。

1. 配置 npm 镜像，以确保 Node 包的安装速度
2. 使用 Vue CLI 来初始化项目
3. 安装 Vue Router & Vuetify.js
4. 部署应用
5. 安装 tcb-js-sdk 以调用云开发数据

请期待本系列的下一篇关于界面开发的文章。


分布式跟踪系统的四大功能模块如何协同工作

Summary: 了解分布式跟踪中的主要体系结构决策，以及各部分如何组合在一起。

了解分布式跟踪中的主要体系结构决策，以及各部分如何组合在一起。

# 分布式跟踪系统的四大功能模块如何协同工作

>
> 了解分布式跟踪中的主要体系结构决策，以及各部分如何组合在一起。
>
>
>

早在十年前，认真研究过分布式跟踪基本上只有学者和一小部分大型互联网公司中的人。对于任何采用微服务的组织来说，它如今成为一种筹码。其理由是确立的：微服务通常会发生让人意想不到的错误，而分布式跟踪则是描述和诊断那些错误的最好方法。

也就是说，一旦你准备将分布式跟踪集成到你自己的应用程序中，你将很快意识到对于不同的人来说“<ruby> 分布式跟踪 <rt>  Distributed Tracing </rt></ruby>”一词意味着不同的事物。此外，跟踪生态系统里挤满了具有相似内容的重叠项目。本文介绍了分布式跟踪系统中四个（可能）独立的功能模块，并描述了它们间将如何协同工作。

### 分布式跟踪：一种思维模型

大多数用于跟踪的思维模型来源于 [Google 的 Dapper 论文](https://research.google.com/pubs/pub36356.html)。[OpenTracing](http://opentracing.io/) 使用相似的术语，因此，我们从该项目借用了以下术语：

* <ruby> 跟踪 <rt>  Trace </rt></ruby>：事物在分布式系统运行的过程描述。
* <ruby> 跨度 <rt>  Span </rt></ruby>：一种命名的定时操作，表示工作流的一部分。跨度可接受键值对标签以及附加到特定跨度实例的细粒度的、带有时间戳的结构化日志。
* <ruby> 跨度上下文 <rt>  Span context </rt></ruby>：携带分布式事务的跟踪信息，包括当它通过网络或消息总线将服务传递给服务时。跨度上下文包含跟踪标识符、跨度标识符以及跟踪系统所需传播到下游服务的任何其他数据。

如果你想要深入研究这种思维模式的细节，请仔细参照 [OpenTracing 技术规范](https://research.google.com/pubs/pub36356.html)。

### 四大功能模块

从应用层分布式跟踪系统的观点来看，现代软件系统架构如下图所示：

现代软件系统的组件可分为三类：

* **应用程序和业务逻辑**：你的代码。
* **广泛共享库**：他人的代码
* **广泛共享服务**：他人的基础架构

这三类组件有着不同的需求，驱动着监控应用程序的分布式跟踪系统的设计。最终的设计得到了四个重要的部分：

* <ruby> 跟踪检测 API <rt>  A tracing instrumentation API </rt></ruby>：修饰应用程序代码
* <ruby> 线路协议 <rt>  Wire protocol </rt></ruby>：在 RPC 请求中与应用程序数据一同发送的规定
* <ruby> 数据协议 <rt>  Data protocol </rt></ruby>：将异步信息（带外）发送到你的分析系统的规定
* <ruby> 分析系统 <rt>  Analysis system </rt></ruby>：用于处理跟踪数据的数据库和交互式用户界面

为了更深入的解释这个概念，我们将深入研究驱动该设计的细节。如果你只需要我的一些建议，请跳转至下方的四大解决方案。

### 需求，细节和解释

应用程序代码、共享库以及共享式服务在操作上有显著的差别，这种差别严重影响了对其进行检测的请求操作。

#### 检测应用程序代码和业务逻辑

在任何特定的微服务中，由微服务开发者编写的大部分代码是应用程序或者商业逻辑。这部分代码规定了特定区域的操作。通常，它包含任何特殊、独一无二的逻辑判断，这些逻辑判断首先证明了创建新型微服务的合理性。基本上按照定义，**该代码通常不会在多个服务中共享或者以其他方式出现。**

也即是说你仍需了解它，这也意味着需要以某种方式对它进行检测。一些监控和跟踪分析系统使用<ruby> 黑盒代理 <rt>  black-box agents </rt></ruby>自动检测代码，另一些系统更想使用显式的白盒检测工具。对于后者，抽象跟踪 API 提供了许多对于微服务的应用程序代码来说更为实用的优势：

* 抽象 API 允许你在不重新编写检测代码的条件下换新的监视工具。你可能想要变更云服务提供商、供应商和监测技术，而一大堆不可移植的检测代码将会为该过程增加有意义的开销和麻烦。
* 事实证明，除了生产监控之外，该工具还有其他有趣的用途。现有的项目使用相同的跟踪工具来驱动测试工具、分布式调试器、“混沌工程”故障注入器和其他元应用程序。
* 但更重要的是，若将应用程序组件提取到共享库中要怎么办呢？由上述内容可得到结论：

#### 检测共享库

在大多数应用程序中出现的实用程序代码（处理网络请求、数据库调用、磁盘写操作、线程、并发管理等）通常情况下是通用的，而非特别应用于某个特定应用程序。这些代码会被打包成库和框架，而后就可以被装载到许多的微服务上并且被部署到多种不同的环境中。

其真正的不同是：对于共享代码，其他人则成为了使用者。大多数用户有不同的依赖关系和操作风格。如果尝试去使用该共享代码，你将会注意到几个常见的问题：

* 你需要一个 API 来编写检测。然而，你的库并不知道你正在使用哪个分析系统。会有多种选择，并且运行在相同应用下的所有库无法做出不兼容的选择。
* 由于这些包封装了所有网络处理代码，因此从请求报头注入和提取跨度上下文的任务往往指向 RPC 库。然而，共享库必须了解到每个应用程序正在使用哪种跟踪协议。
* 最后，你不想强制用户使用相互冲突的依赖项。大多数用户有不同的依赖关系和操作风格。即使他们使用 gRPC，绑定的 gRPC 版本是否相同？因此任何你的库附带用于跟踪的监控 API 必定是免于依赖的。

**因此，一个（a）没有依赖关系、（b）与线路协议无关、（c）使用流行的供应商和分析系统的抽象 API 应该是对检测共享库代码的要求。**

#### 检测共享式服务

最后，有时整个服务（或微服务集合体）的通用性足以使许多独立的应用程序使用它们。这种共享式服务通常由第三方托管和管理，例如缓存服务器、消息队列以及数据库。

从应用程序开发者的角度来看，理解共享式服务本质上是黑盒子是极其重要的。它不可能将你的应用程序监控注入到共享式服务。恰恰相反，托管服务通常会运行它自己的监控方案。

### 四个方面的解决方案

因此，抽象的跟踪应用程序接口将会帮助库发出数据并且注入/抽取跨度上下文。标准的线路协议将会帮助黑盒服务相互连接，而标准的数据格式将会帮助分离的分析系统合并其中的数据。让我们来看一下部分有希望解决这些问题的方案。

#### 跟踪 API：OpenTracing 项目

如你所见，我们需要一个跟踪 API 来检测应用程序代码。为了将这种工具扩展到大多数进行跨度上下文注入和提取的共享库中，则必须以某种关键方式对 API 进行抽象。

[OpenTracing](http://opentracing.io/) 项目主要针对解决库开发者的问题，OpenTracing 是一个与供应商无关的跟踪 API，它没有依赖关系，并且迅速得到了许多监控系统的支持。这意味着，如果库附带了内置的本地 OpenTracing 工具，当监控系统在应用程序启动连接时，跟踪将会自动启动。

就个人而言，作为一个已经编写、发布和操作开源软件十多年的人，在 OpenTracing 项目上工作并最终解决这个观察性的难题令我十分满意。

除了 API 之外，OpenTracing 项目还维护了一个不断增长的工具列表，其中一些可以在[这里](https://github.com/opentracing-contrib/)找到。如果你想参与进来，无论是通过提供一个检测插件，对你自己的 OSS 库进行本地测试，或者仅仅只想问个问题，都可以通过 [Gitter](https://gitter.im/opentracing/public) 向我们打招呼。

#### 线路协议： HTTP 报头 trace-context

为了监控系统能进行互操作，以及减轻从一个监控系统切换为另外一个时带来的迁移问题，需要标准的线路协议来传播跨度上下文。

[w3c 分布式跟踪上下文社区小组](https://www.w3.org/community/trace-context/)在努力制定此标准。目前的重点是制定一系列标准的 HTTP 报头。该规范的最新草案可以在[此处](https://w3c.github.io/distributed-tracing/report-trace-context.html)找到。如果你对此小组有任何的疑问，[邮件列表](http://lists.w3.org/Archives/Public/public-trace-context/)和[Gitter 聊天室](https://gitter.im/TraceContext/Lobby)是很好的解惑地点。

（LCTT 译注：本文原文发表于 2018 年 5 月，可能现在社区已有不同进展）

#### 数据协议 （还未出现！！）

对于黑盒服务，在无法安装跟踪程序或无法与程序进行交互的情况下，需要使用数据协议从系统中导出数据。

目前这种数据格式和协议的开发工作尚处在初级阶段，并且大多在 w3c 分布式跟踪上下文工作组的上下文中进行工作。需要特别关注的是在标准数据模式中定义更高级别的概念，例如 RPC 调用、数据库语句等。这将允许跟踪系统对可用数据类型做出假设。OpenTracing 项目也通过定义一套[标准标签集](https://github.com/opentracing/specification/blob/master/semantic_conventions.md)来解决这一事务。该计划是为了使这两项努力结果相互配合。

注意当前有一个中间地带。对于由应用程序开发者操作但不想编译或以其他方式执行代码修改的“网络设备”，动态链接可以帮助避免这种情况。主要的例子就是服务网格和代理，就像 Envoy 或者 NGINX。针对这种情况，可将兼容 OpenTracing 的跟踪器编译为共享对象，然后在运行时动态链接到可执行文件中。目前 [C++ OpenTracing API](https://github.com/opentracing/opentracing-cpp) 提供了该选项。而 JAVA 的 OpenTracing [跟踪器解析](https://github.com/opentracing-contrib/java-tracerresolver)也在开发中。

这些解决方案适用于支持动态链接，并由应用程序开发者部署的的服务。但从长远来看，标准的数据协议可以更广泛地解决该问题。

#### 分析系统：从跟踪数据中提取有见解的服务

最后不得不提的是，现在有足够多的跟踪监视解决方案。可以在[此处](http://opentracing.io/documentation/pages/supported-tracers)找到已知与 OpenTracing 兼容的监控系统列表，但除此之外仍有更多的选择。我更鼓励你研究你的解决方案，同时希望你在比较解决方案时发现本文提供的框架能派上用场。除了根据监控系统的操作特性对其进行评级外（更不用提你是否喜欢 UI 和其功能），确保你考虑到了上述三个重要方面、它们对你的相对重要性以及你感兴趣的跟踪系统如何为它们提供解决方案。

### 结论

最后，每个部分的重要性在很大程度上取决于你是谁以及正在建立什么样的系统。举个例子，开源库的作者对 OpenTracing API 非常感兴趣，而服务开发者对 trace-context 规范更感兴趣。当有人说一部分比另一部分重要时，他们的意思通常是“一部分对我来说比另一部分重要”。

然而，事实是：分布式跟踪已经成为监控现代系统所必不可少的事物。在为这些系统进行构建模块时，“尽可能解耦”的老方法仍然适用。在构建像分布式监控系统一样的跨系统的系统时，干净地解耦组件是维持灵活性和前向兼容性地最佳方式。

感谢你的阅读！现在当你准备好在你自己的应用程序中实现跟踪服务时，你已有一份指南来了解他们正在谈论哪部分部分以及它们之间如何相互协作。

---

via: <https://opensource.com/article/18/5/distributed-tracing>

作者：[Ted Young](https://opensource.com/users/tedsuo) 选题：[lujun9972](https://github.com/lujun9972) 译者：[chenmu-kk](https://github.com/chenmu-kk) 校对：[wxy](https://github.com/wxy)

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


使用这个 Python 程序记录你的活动

# 使用这个 Python 程序记录你的活动

>
> jrnl 可以创建可搜索、带时间戳、可导出、加密的（如果需要）的日常活动日志。在我们的 20 个使用开源提升生产力的系列的第八篇文章中了解更多。
>
>
>

去年，我在 19 天里给你介绍了 19 个新（对你而言）的生产力工具。今年，我换了一种方式：使用你在使用或者还没使用的工具，构建一个使你可以在新一年更加高效的环境。

### 使用 jrnl 记录日志

在我的公司，许多人会在下班之前在 Slack 上发送一个“一天结束”的状态。在有着许多项目和全球化的团队里，这是一个分享你已完成、未完成以及你需要哪些帮助的一个很好的方式。但有时候我太忙了，以至于我忘了做了什么。这时候就需要记录日志了。

打开一个文本编辑器并在你做一些事的时候添加一行很容易。但是在需要找出你在什么时候做的笔记，或者要快速提取相关的行时会有挑战。幸运的是，[jrnl](https://jrnl.sh/) 可以提供帮助。

jrnl 能让你在命令行中快速输入条目、搜索过去的条目并导出为 HTML 和 Markdown 等富文本格式。你可以有多个日志，这意味着你可以将工作条目与私有条目分开。它将条目存储为纯文本，因此即使 jrnl 停止工作，数据也不会丢失。

由于 jrnl 是一个 Python 程序，最简单的安装方法是使用 `pip3 install jrnl`。这将确保你获得最新和最好的版本。第一次运行它会询问一些问题，接下来就能正常使用。

现在，每当你需要做笔记或记录日志时，只需输入 `jrnl <some text>`，它将带有时间戳的记录保存到默认文件中。你可以使用 `jrnl -on YYYY-MM-DD` 搜索特定日期条目，`jrnl -from YYYY-MM-DD` 搜索在那日期之后的条目，以及用 `jrnl -to YYYY-MM-DD` 搜索到那日期的条目。搜索词可以与 `-and` 参数结合使用，允许像 `jrnl -from 2019-01-01 -and -to 2019-12-31` 这类搜索。

你还可以使用 `--edit` 标志编辑日志中的条目。开始之前，通过编辑文件 `~/.config/jrnl/jrnl.yaml` 来设置默认编辑器。你还可以指定日志使用什么文件、用于标签的特殊字符以及一些其他选项。现在，重要的是设置编辑器。我使用 Vim，jrnl 的文档中有一些使用其他编辑器如 VSCode 和 Sublime Text 的[有用提示](https://jrnl.sh/recipes/#external-editors)。

jrnl 还可以加密日志文件。通过设置全局 `encrypt` 变量，你将告诉 jrnl 加密你定义的所有日志。还可在配置文件中的针对文件设置 `encrypt: true` 来加密文件。

```
journals:
  default: ~/journals/journal.txt
  work: ~/journals/work.txt
  private:
    journal: ~/journals/private.txt
    encrypt: true
```

如果日志尚未加密，系统将提示你输入在对它进行任何操作的密码。日志文件将加密保存在磁盘上，以免受窥探。[jrnl 文档](https://jrnl.sh/encryption/) 中包含其工作原理、使用哪些加密方式等的更多信息。

日志记录帮助我记住什么时候做了什么事，并在我需要的时候能够找到它。

---

via: <https://opensource.com/article/20/1/python-journal>

作者：[Kevin Sonney](https://opensource.com/users/ksonney) 选题：[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/) 荣誉推出


跟我学 “Linux” 小程序 Web 版开发（二）：UI 开发

Summary: 在这一部分中，借助 Vue 的 method 、onkeydown 和 computed 实现了页面基本逻辑的构建。并借助 Vuetify 的一些基础组件来构建页面。

# 跟我学 “Linux” 小程序 Web 版开发（二）：UI 开发

### 工作量分析

在我们进行这部分开发的时候，接下来我们需要进行相应的功能安排和分类。

简单看来，我需要开发 3 个页面：

1. **首页**：首页负责用户默认访问。
2. **列表页**：列表页面则是在搜索过程中，如果有多个结果，则进入到列表页面。如果有单个结果，则应该进入到详情页面。
3. **结果页**：结果页负责显示命令的具体的翻译结果。

根据实际的工作拆分组件的化，我需要有一个 Layout 组件来负责整体的页面的环境渲染。但是，考虑到组件的复用，于是决定将首页的 Title 进行优化，使首页和详情页保持一致。

在新版的布局情况下，我就可以将顶部的的 title 和底部的 Link 放在最外侧的组件中。

### 创建 Router & Page

在思考情况后，接下来我们来创建 Router 和 Page。首先，删除 `views` 下的 `About.vue`（因为这个页面我们不需要）。然后创建 `List.vue` 和 `Result.vue` ，用作后续的开发准备。

创建完成后，修改 `router/index.js` 中的 `routes` 部分：

```
const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue')
  },
  {
    path: '/list/:cmd',
    name: 'list',
    component: () => import(/* webpackChunkName: "list" */ '../views/List.vue')
  },
  {
    path: '/cmd/:cmd',
    name: 'command',
    component: () => import(/* webpackChunkName: "cmd" */ '../views/Result.vue')
  }
]
```

完成定义后，我们就可以通过形如 <https://tldr.linux.cn/list/ls> 和 <https://tldr.linux.cn/cmd/ls> 这样的方式来访问具体的命令了。

### 定义页面

接下来需要编写 Home、List 和 Result 这三个页面。由于这三个页面在内容方面没有太多的可以借鉴的点，所以我们更多关注于使用页面中 `<script>` 的部分。

```
<template>
  <div class="home">
        <v-text-field
        v-model="cmd"
        @keydown="onKeyDown"
        autofocus
        ></v-text-field>
  </div>
</template>

<script>
export default {
  name: 'home',
  data:function(){
    return {
      cmd:""
    }
  },
  methods:{
    onKeyDown: function(e)  {

    }
  },
  computed:{
    isLoaded:function(){
        return !this.loaded
    }
  }
}
</script>
```

上述代码是我在三个页面几乎都会使用到的结构，删除其中的一些无用的代码以后，基本上在每个页面都可以看到。这里我重点说一下其中的一些比较特殊的用法。

首先第一个是在 `v-text-field` 上加入的 `@keydown="onKeyDown"` 这个绑定，这个绑定将会帮助实现用户点击回车以后，自动触发事件。这样在用户输入完命令后，按下回车就自动执行后续的操作，而不需要再移动鼠标指针去点击按钮启动搜索。

其次，在 `v-text-field` 上加入了`autofocus` ，来实现进入页面后，自动为输入框加入`focus`，从而实现页面加载完成后，用户就可以输入命令。

这样的一些配置，可以让**用户的体验做到最好**。

除此之外，我还用到了 `computed` ，来做数据调整，确保我可以控制内容。

### 一些小的特性的点

#### 使用骨架图来优化体验

由于我们的应用在列表页面和详情页面存在数据的查询时间，为了让应用在加载的时候，不会因为加载中而退出页面，我加入了 v-skeleton-loader 组件，这样用户在数据查询的时候，看骨架图来缓解用户的焦虑。

在组件层面，我配置了 `v-if` 来做显示的控制，并将 type 设置为 `card,article,card,article` 来实现多样化的组件加载支持。

```
 <v-skeleton-loader
    v-if="isLoaded"
    type="card,article,card,article"
    min-height="800"
    ></v-skeleton-loader>
```

### 总结

在这一部分中，借助 Vue 的 `method` 、`onkeydown` 和 `computed` 实现了页面基本逻辑的构建。并借助 Vuetify 的一些基础组件来构建页面。

在这一部分，我想告诉大家的更多是在 UI 的部分，我们在做的时候不仅仅需要考虑的是界面，更多还需要考虑在 UX 侧体验的优化，组件库提供给我们的配置项目，可以优化产品体验。


查看 Linux 系统中进程和用户的内存使用情况

# 查看 Linux 系统中进程和用户的内存使用情况

>
> 有一些命令可以用来检查 Linux 系统中的内存使用情况，下面是一些更好的命令。
>
>
>

<img src="content_image/album_202002_03_205550dl2464wdwlnl06if.jpg" alt="Fancycrave" title="Fancycrave">

有许多工具可以查看 Linux 系统中的内存使用情况。一些命令被广泛使用，比如 `free`、`ps`。而另一些命令允许通过多种方式展示系统的性能统计信息，比如 `top`。在这篇文章中，我们将介绍一些命令以帮助你确定当前占用着最多内存资源的用户或者进程。

下面是一些按照进程查看内存使用情况的命令：

### 按照进程查看内存使用情况

#### 使用 top

`top` 是最好的查看内存使用情况的命令之一。为了查看哪个进程使用着最多的内存，一个简单的办法就是启动 `top`，然后按下 `shift+m`，这样便可以查看按照内存占用百分比从高到底排列的进程。当你按下了 `shift+m` ，你的 `top` 应该会得到类似于下面这样的输出结果：

```
$top
top - 09:39:34 up 5 days, 3 min,  3 users,  load average: 4.77, 4.43, 3.72
Tasks: 251 total,   3 running, 247 sleeping,   1 stopped,   0 zombie
%Cpu(s): 50.6 us, 35.9 sy,  0.0 ni, 13.4 id,  0.2 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   5944.4 total,    128.9 free,   2509.3 used,   3306.2 buff/cache
MiB Swap:   2048.0 total,   2045.7 free,      2.2 used.   3053.5 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  400 nemo      20   0 3309580 550188 168372 S   0.3   9.0   1:33.27 Web Content
32469 nemo      20   0 3492840 447372 163296 S   7.3   7.3   3:55.60 firefox
32542 nemo      20   0 2845732 433388 140984 S   6.0   7.1   4:11.16 Web Content
  342 nemo      20   0 2848520 352288 118972 S  10.3   5.8   4:04.89 Web Content
 2389 nemo      20   0 1774412 236700  90044 S  39.7   3.9   9:32.64 vlc
29527 nemo      20   0 2735792 225980  84744 S   9.6   3.7   3:02.35 gnome-shell
30497 nemo      30  10 1088476 159636  88884 S   0.0   2.6   0:11.99 update-manager
30058 nemo      20   0 1089464 140952  33128 S   0.0   2.3   0:04.58 gnome-software
32533 nemo      20   0 2389088 104712  79544 S   0.0   1.7   0:01.43 WebExtensions
 2256 nemo      20   0 1217884 103424  31304 T   0.0   1.7   0:00.28 vlc
 1713 nemo      20   0 2374396  79588  61452 S   0.0   1.3   0:00.49 Web Content
29306 nemo      20   0  389668  74376  54340 S   2.3   1.2   0:57.25 Xorg
32739 nemo      20   0  289528  58900  34480 S   1.0   1.0   1:04.08 RDD Process
29732 nemo      20   0  789196  57724  42428 S   0.0   0.9   0:00.38 evolution-alarm
 2373 root      20   0  150408  57000   9924 S   0.3   0.9  10:15.35 nessusd
```

注意 `%MEM` 排序。列表的大小取决于你的窗口大小，但是占据着最多的内存的进程将会显示在列表的顶端。

#### 使用 ps

`ps` 命令中的一列用来展示每个进程的内存使用情况。为了展示和查看哪个进程使用着最多的内存，你可以将 `ps` 命令的结果传递给 `sort` 命令。下面是一个有用的示例：

```
$ ps aux | sort -rnk 4 | head -5
nemo       400  3.4  9.2 3309580 563336 ?      Sl   08:59   1:36 /usr/lib/firefox/firefox -contentproc -childID 6 -isForBrowser -prefsLen 9086 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo     32469  8.2  7.7 3492840 469516 ?      Sl   08:54   4:15 /usr/lib/firefox/firefox -new-window
nemo     32542  8.9  7.6 2875428 462720 ?      Sl   08:55   4:36 /usr/lib/firefox/firefox -contentproc -childID 2 -isForBrowser -prefsLen 1 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo       342  9.9  5.9 2854664 363528 ?      Sl   08:59   4:44 /usr/lib/firefox/firefox -contentproc -childID 5 -isForBrowser -prefsLen 8763 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo      2389 39.5  3.8 1774412 236116 pts/1  Sl+  09:15  12:21 vlc videos/edge_computing.mp4
```

在上面的例子中（文中已截断），`sort` 命令使用了 `-r` 选项（反转）、`-n` 选项（数字值）、`-k` 选项（关键字），使 `sort` 命令对 `ps` 命令的结果按照第四列（内存使用情况）中的数字逆序进行排列并输出。如果我们首先显示 `ps` 命令的标题，那么将会便于查看。

```
$ ps aux | head -1; ps aux | sort -rnk 4 | head -5
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
nemo       400  3.4  9.2 3309580 563336 ?      Sl   08:59   1:36 /usr/lib/firefox/firefox -contentproc -childID 6 -isForBrowser -prefsLen 9086 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo     32469  8.2  7.7 3492840 469516 ?      Sl   08:54   4:15 /usr/lib/firefox/firefox -new-window
nemo     32542  8.9  7.6 2875428 462720 ?      Sl   08:55   4:36 /usr/lib/firefox/firefox -contentproc -childID 2 -isForBrowser -prefsLen 1 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo       342  9.9  5.9 2854664 363528 ?      Sl   08:59   4:44 /usr/lib/firefox/firefox -contentproc -childID 5 -isForBrowser -prefsLen 8763 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo      2389 39.5  3.8 1774412 236116 pts/1  Sl+  09:15  12:21 vlc videos/edge_computing.mp4
```

如果你喜欢这个命令，你可以用下面的命令为他指定一个别名，如果你想一直使用它，不要忘记把该命令添加到你的 `~/.bashrc` 文件中。

```
$ alias mem-by-proc="ps aux | head -1; ps aux | sort -rnk 4"
```

下面是一些根据用户查看内存使用情况的命令：

### 按用户查看内存使用情况

#### 使用 top

按照用户检查内存使用情况会更复杂一些，因为你需要找到一种方法把用户所拥有的所有进程统计为单一的内存使用量。

如果你只想查看单个用户进程使用情况，`top` 命令可以采用与上文中同样的方法进行使用。只需要添加 `-U` 选项并在其后面指定你要查看的用户名，然后按下 `shift+m` 便可以按照内存使用有多到少进行查看。

```
$ top -U nemo
top - 10:16:33 up 5 days, 40 min,  3 users,  load average: 1.91, 1.82, 2.15
Tasks: 253 total,   2 running, 250 sleeping,   1 stopped,   0 zombie
%Cpu(s): 28.5 us, 36.8 sy,  0.0 ni, 34.4 id,  0.3 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   5944.4 total,    224.1 free,   2752.9 used,   2967.4 buff/cache
MiB Swap:   2048.0 total,   2042.7 free,      5.2 used.   2812.0 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  400 nemo      20   0 3315724 623748 165440 S   1.0  10.2   1:48.78 Web Content
32469 nemo      20   0 3629380 607492 161688 S   2.3  10.0   6:06.89 firefox
32542 nemo      20   0 2886700 404980 136648 S   5.6   6.7   6:50.01 Web Content
  342 nemo      20   0 2922248 375784 116096 S  19.5   6.2   8:16.07 Web Content
 2389 nemo      20   0 1762960 234644  87452 S   0.0   3.9  13:57.53 vlc
29527 nemo      20   0 2736924 227260  86092 S   0.0   3.7   4:09.11 gnome-shell
30497 nemo      30  10 1088476 156372  85620 S   0.0   2.6   0:11.99 update-manager
30058 nemo      20   0 1089464 138160  30336 S   0.0   2.3   0:04.62 gnome-software
32533 nemo      20   0 2389088 102532  76808 S   0.0   1.7   0:01.79 WebExtensions
```

#### 使用 ps

你依旧可以使用 `ps` 命令通过内存使用情况来排列某个用户的进程。在这个例子中，我们将使用 `grep` 命令来筛选得到某个用户的所有进程。

```
$ ps aux | head -1; ps aux | grep ^nemo| sort -rnk 4 | more
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
nemo     32469  7.1 11.5 3724364 701388 ?      Sl   08:54   7:21 /usr/lib/firefox/firefox -new-window
nemo       400  2.0  8.9 3308556 543232 ?      Sl   08:59   2:01 /usr/lib/firefox/firefox -contentproc -childID 6 -isForBrowser -prefsLen 9086 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni/usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo     32542  7.9  7.1 2903084 436196 ?      Sl   08:55   8:07 /usr/lib/firefox/firefox -contentproc -childID 2 -isForBrowser -prefsLen 1 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo       342 10.8  7.0 2941056 426484 ?      Rl   08:59  10:45 /usr/lib/firefox/firefox -contentproc -childID 5 -isForBrowser -prefsLen 8763 -prefMapSize 210653 -parentBuildID 20200107212822 -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 32469 true tab
nemo      2389 16.9  3.8 1762960 234644 pts/1  Sl+  09:15  13:57 vlc videos/edge_computing.mp4
nemo     29527  3.9  3.7 2736924 227448 ?      Ssl  08:50   4:11 /usr/bin/gnome-shell
```

### 使用 ps 和其他命令的搭配

如果你想比较某个用户与其他用户内存使用情况将会比较复杂。在这种情况中，创建并排序一个按照用户总的内存使用量是一个不错的方法，但是它需要做一些更多的工作，并涉及到许多命令。在下面的脚本中，我们使用 `ps aux | grep -v COMMAND | awk '{print $1}' | sort -u` 命令得到了用户列表。其中包含了系统用户比如 `syslog`。我们对每个任务使用 `awk` 命令以收集每个用户总的内存使用情况。在最后一步中，我们展示每个用户总的内存使用量（按照从大到小的顺序）。

```
#!/bin/bash

stats=””
echo "%   user"
echo "============"

# collect the data
for user in `ps aux | grep -v COMMAND | awk '{print $1}' | sort -u`
do
  stats="$stats\n`ps aux | egrep ^$user | awk 'BEGIN{total=0}; \
    {total += $4};END{print total,$1}'`"
done

# sort data numerically (largest first)
echo -e $stats | grep -v ^$ | sort -rn | head
```

这个脚本的输出可能如下：

```
$ ./show_user_mem_usage
%   user
============
69.6 nemo
5.8 root
0.5 www-data
0.3 shs
0.2 whoopsie
0.2 systemd+
0.2 colord
0.2 clamav
0 syslog
0 rtkit
```

在 Linux 有许多方法可以报告内存使用情况。可以通过一些用心设计的工具和命令，来查看并获得某个进程或者用户占用着最多的内存。

---

via: <https://www.networkworld.com/article/3516319/showing-memory-usage-in-linux-by-process-and-user.html>

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

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


“文章助手”小程序已服务十万次 URL 访问

# “文章助手”小程序已服务十万次 URL 访问

为了解决微信公众号文章内不能嵌入外部链接的问题，我们之前开发了一个小程序“文章助手”，用于通过微信小程序在微信文章内提供访问外部链接的最佳体验。

一般而言，公众号内是无法放超链接的。这对于一般的文章来说其实不要紧，但是对于我们这种技术类的文章，往往会带有很多链接，而不能插入链接，会导致一篇文章的价值和可信性降低。

这个小程序发布之后，除了在我们的公众号内进行了使用，而且也得到很多公众号的支持。就在刚才，我再次查看这个小程序时，发现其首页显示的累计服务次数已经超过了 10 万。

关于这个小程序如何使用，请查看[它的介绍](/article-10838-1.html)和这个小程序的[辅助工具介绍](/article-10896-1.html)。最近宅在家，欢迎大家对这个小程序提提改进意见，我估计有时间实现~

顺便提一句，我们还开发了几个小程序，欢迎大家使用：

* 文章助手：提供微信公众号文章内的外链访问
* Linux文章：Linux中国官网文章访问
* Linux：Linux 命令速查（TLDR）


跟我学 “Linux” 小程序 Web 版开发（三）：云开发相关数据调用

Summary: 在完成了界面的实现后，接下来可以开始进行和云开发相关的数据对接。

在完成了界面的实现后，接下来可以开始进行和云开发相关的数据对接。

# 跟我学 “Linux” 小程序 Web 版开发（三）：云开发相关数据调用

### 介绍

在完成了界面的实现后，接下来可以开始进行和云开发相关的数据对接。完成数据对接后，应用基础就打好了，接下来的就是发布上线以及一些小的功能的加入。

### 配置

在进行相关的配置调用的时候，你需要先登录腾讯云控制台，进行一些配置。使用你的小程序账号登录腾讯云，并在其中找到“云开发”产品。进入到“产品控制台”。

在“产品控制台”中找到你的环境，点击进入“详情页”：

在环境“详情页面”选择“用户管理”->“登录设置”->“匿名登录”：

启用匿名登录。

**云开发的数据查询目前必须登录后才可以查询，因为希望给用户提供的是免登录的解决方案，因此，必须开通匿名登录，确保可以进行数据查询。**

由于需要在网页中调用相应的函数，因此，也需要在同一个页面的 WEB 安全域名中添加应用的上线域名（本地调试用的 localhost 无需添加）。

为应用程序添加匿名登录的逻辑
--------------

>
> 此部分代码位置：<https://github.com/LCTT/tldr.linux.cn/blob/master/src/main.js>
>
>
>

由于希望用户可以打开网页就可以查询数据，因此，必须在用户无感的情况下，完成匿名登录逻辑。

根据对 Vue 生命周期的预研，将相应的逻辑放在了 `beforeCreate` 中，确保在应用初始化完成后，就可以自动完成匿名登录。

具体实现代码如下：

```
// main.js
new Vue({
  router,
  vuetify,
  render: h => h(App),
  beforeCreate: function(){                   // 新增匿名登陆逻辑
        const auth = this.$tcb.auth();        // 新增匿名登陆逻辑
        auth.signInAnonymously();             // 新增匿名登陆逻辑
  }                                           // 新增匿名登陆逻辑
}).$mount('#app')
```

加入完成后，你可以使用云开发的数据库等命令，来完成相应的数据库调用，验证自己的调用是否正常。

>
> 在这里需要注意，由于 Vue 默认的 ESLint 规则限制，默认是无法在 Vue 项目代码中使用 `console.log` 的，你需要使用一些命令来跳过相应的检查 只需要在你需要打印变量的前一行加入 `// eslint-disable-next-line` 就可以避免对应的检查了。
>
>
>

调用数据
----

>
> 此部分代码位置：<https://github.com/LCTT/tldr.linux.cn/blob/master/src/views/Result.vue>
>
>
>

完成初始化后，就需要完成相应的数据调用，这里不再针对每一个进行讲解，选择一个例子来说明。

```
const db = this.$tcb.database();
const cmd = db.collection('command');
if(this.id){ // 这里的 id 是 props 传入的参数，为命令对应的 doc id
    cmd.doc(this.id).get().then(res => {
      this.command = res.data
    })
    }else{
    cmd.where({
      name: this.$route.params.cmd // 命令可以从 Route 中获取，但实际场景下，因为开启了 `props: true`，也可以直接从 props 中获取。
    }).limit(1).get().then(res => {
      this.command = res.data[0]
    }).catch((err) => {
      alert("命令查询出错，请联系我们")
      // eslint-disable-next-line
      console.error(err)
    })
}
```

在这段代码中，首先是前期挂载的 `$tcb` 中抽取 `database()` 对象，并基于 `database()` 对象构建 `collection()` 对象。 然后就是使用 `collection()` 对象进行查询。

由于这里涉及到不同的页面逻辑，使用了一个 if 来判断数据。上下两种分别是获取单个数据和使用多个数据的方法。获取到数据以后，将数据更新，同步到 Vue 的 Data 中，完成相应的逻辑的调用。

### 云开发登录的坑

>
> 此部分代码位置：<https://github.com/LCTT/tldr.linux.cn/blob/master/src/main.js>
>
>
>

由于为用户提供的是快速查询功能，因此希望用户无论何时都是无感进行查询的。但实际测试的时候，发现用户如果直接通过命令行登录的时候，会导致报错。根据控制台返回的信息来看，是用户登录状态尚未完成，就进行了数据查询。

通过查询云开发的文档，发现云开发的 `auth` 对象在登录的时候，可以传入一个 `persistence` 参数来控制身份信息的持久化。

由于默认使用的是会话 ，所以导致用户的登录态丢失。为了确保应用的状态正常进行，将 `persistence` 设置为 `local`，确保应用在一次登录后可以将用户登录状态下放到用户的存储 中，这样可以避免用户总是会遭遇请求失败的问题。

```
// main.js
new Vue({
  router,
  vuetify,
  render: h => h(App),
  beforeCreate: async function(){
        const auth = this.$tcb.auth({ persistence: 'local' });
        await auth.signInAnonymously();
  }
}).$mount('#app')
```

### 总结

在实际开发中，如果你需要通过云开发的 Web SDK 调用相应的数据，则需要先行开启云开发的匿名登录并配置 Web 安全域名；在数据调用的部分和在小程序端调用云开发没有太大的区别；并通过设置 `presistence` 设置搞定了登录状态丢失的问题。


四大亮点带你看 Linux 内核 5.6

Summary: 说实话，Linux 5.6 比 5.5 更令人兴奋。

说实话，Linux 5.6 比 5.5 更令人兴奋。

# 四大亮点带你看 Linux 内核 5.6

当我们还在体验 Linux 5.5 稳定发行版带来更好的硬件支持时，Linux 5.6 已经来了。

说实话，Linux 5.6 比 5.5 更令人兴奋。即使即将发布的 Ubuntu 20.04 LTS 发行版将自带 Linux 5.5，你也需要切实了解一下 Linux 5.6 内核为我们提供了什么。

我将在本文中重点介绍 Linux 5.6 发布版中值得期待的关键更改和功能：

### Linux 5.6 功能亮点

<img src="content_image/album_202002_05_113205i65r862bfb88fz77.jpg" alt="" title="">

当 Linux 5.6 有新消息时，我会努力更新这份功能列表。但现在让我们先看一下当前已知的内容：

#### 1、支持 WireGuard

WireGuard 将被添加到 Linux 5.6，出于各种原因的考虑它可能将取代 [OpenVPN](https://openvpn.net/)。

你可以在官网上进一步了解 [WireGuard](https://www.wireguard.com/) 的优点。当然，如果你使用过它，那你可能已经知道它比 OpenVPN 更好的原因。

同样，[Ubuntu 20.04 LTS 将支持 WireGuard](https://www.phoronix.com/scan.php?page=news_item&px=Ubuntu-20.04-Adds-WireGuard)。

#### 2、支持 USB4

Linux 5.6 也将支持 **USB4**。

如果你不了解 USB 4.0 (USB4)，你可以阅读这份[文档](https://www.usb.org/sites/default/files/2019-09/USB-IF_USB4%20spec%20announcement_FINAL.pdf)。

根据文档，“USB4 将使 USB 的最大带宽增大一倍并支持<ruby> 多并发数据和显示协议 <rt>  multiple simultaneous data and display protocols </rt></ruby>。”

另外，虽然我们都知道 USB4 基于 Thunderbolt 接口协议，但它将向后兼容 USB 2.0、USB 3.0 以及 Thunderbolt 3，这将是一个好消息。

#### 3、使用 LZO/LZ4 压缩 F2FS 数据

Linux 5.6 也将支持使用 LZO/LZ4 算法压缩 F2FS 数据。

换句话说，这只是 Linux 文件系统的一种新压缩技术，你可以选择待定的文件扩展技术。

#### 4、解决 32 位系统的 2038 年问题

Unix 和 Linux 将时间值以 32 位有符号整数格式存储，其最大值为 2147483647。时间值如果超过这个数值则将由于整数溢出而存储为负数。

这意味着对于 32 位系统，时间值不能超过 1970 年 1 月 1 日后的 2147483647 秒。也就是说，在 UTC 时间 2038 年 1 月 19 日 03:14:07 时，由于整数溢出，时间将显示为 1901 年 12 月 13 日而不是 2038 年 1 月 19 日。

Linux kernel 5.6 解决了这个问题，因此 32 位系统也可以运行到 2038 年以后。

#### 5、改进硬件支持

很显然，在下一个发布版中，硬件支持也将继续提升。而支持新式无线外设的计划也同样是优先的。

新内核中将增加对 MX Master 3 鼠标以及罗技其他无线产品的支持。

除了罗技的产品外，你还可以期待获得许多不同硬件的支持（包括对 AMD GPU、NVIDIA GPU 和 Intel Tiger Lake 芯片组的支持）。

#### 6、其他更新

此外，Linux 5.6 中除了上述主要的新增功能或支持外，下一个内核版本也将进行其他一些改进：

* 改进 AMD Zen 的温度/功率报告
* 修复华硕飞行堡垒系列笔记本中 AMD CPU 过热
* 开源支持 NVIDIA RTX 2000 图灵系列显卡
* 内建 FSCRYPT 加密

[Phoronix](https://www.phoronix.com/scan.php?page=news_item&px=Linux-5.6-Spectacular) 跟踪了 Linux 5.6 带来的许多技术性更改。因此，如果你好奇 Linux 5.6 所涉及的全部更改，则可以亲自了解一下。

现在你已经了解了 Linux 5.6 发布版带来的新功能，对此有什么看法呢？在下方评论中留下你的看法。

---

via: <https://itsfoss.com/linux-kernel-5-6/>

作者：[Ankush Das](https://itsfoss.com/author/ankush/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[LazyWolfLin](https://github.com/LazyWolfLin) 校对：[wxy](https://github.com/wxy)

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


# 跟我学 “Linux” 小程序 Web 版开发（四）：引入统计及 Crash 收集

在完成了产品的基础开发以后，接下来需要进行一些周边的工作，这些周边工具将会帮助下一步优化产品。

### 为什么要加应用统计和 Crash 收集

不少开发者在开发的时候，很少会意识到需要添加应用统计和 Crash 收集。但对于一个合格的应用来说，这些是必须的。

* **应用统计**：应用统计会在后续进行产品迭代的时候给予数据的支持，让能够明确为什么要更新、要更新什么以及为什么这么做。
* **Crash 收集**：人无完人，很难开发出一个完美的应用，就随时有可能会出现应用报错的情况。出现报错以后由于用户的水平不同，有效的反馈其实很少。Crash 收集则可以帮助收集必要的 Crash 信息，从而在后续开发的过程中，有针对性的修复 Bug。

### 应该使用哪些工具？

在应用统计领域，可选项非常多，大部分人使用的是 [Google Analytics](http://analytics.google.com/) ，不过由于这个产品的面向用户主要是国内的用户，因此我更倾向选择加载速度更快的产品，最终我选择的是来自腾讯的[移动应用分析（MTA）](http://mta.qq.com/)，腾讯的移动应用分析中，提供了 HTML5 产品的接入，因此可以完成 TLDR 的统计。

在 Crash 收集方面，大家用的比较普遍的是 [Sentry.io](https://sentry.io/) ，不过因为 Linux 中国并没有足够多的产品业务需要使用 Sentry 来收集 Crash ，因此，一直使用的是官网的免费使用版本。同样因为网络加载速度的原因，选择使用了国内的竞品 —— [FunDebug](https://www.fundebug.com/)。

### 接入工具

#### 腾讯移动应用分析

腾讯移动应用分析的接入并不复杂，首先，你需要登陆其官网，创建一个 HTML5 应用。

并在创建完成后，根据你自己的需要配置相应的能力，这里我开启了所有的数据统计，用以支持后续的产品迭代决策。

配置完成后，你会获得具体的统计的代码，接下来就可以进行接入。

一个比较简单的方法是直接将代码复制，并粘贴到 `public/index.html` 中，从而实现统计。不过，这样嵌入会导致如果需要自定义统计时，会无法通过 ESLint 的规则，因此我选择了第二种方式，使用 Vue 插件的方式接入。

使用 Vue 插件接入时，需要使用 [mars-mta](https://github.com/apiaoqzh/mars-mta) 这个包。

先使用 npm 安装依赖，然后在 `main.js` 中加入相应的统计代码，就可以实现自动的统计。

```
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify';

// 以下为新增代码
import Mars from 'mars-mta'
Vue.use(Mars, {
    open: true, // 开关,若为false,则不会发出上报
    config: {
      sid: '500710182', // 必填，统计用的appid
      cid: '500710183', // 如果开启自定义事件，此项目为必填，否则不填
      autoReport: 1, // 是否开启自动上报(1:init完成则上报一次,0:使用pgv方法才上报)
      senseHash: 1, // hash锚点是否进入url统计
      senseQuery: 1, // url参数是否进入url统计
      performanceMonitor: 1, // 是否开启性能监控
    }
})
// 以上为新增代码

new Vue({
  router,
  vuetify,
  render: h => h(App),
  beforeCreate: async function(){
        const auth = this.$tcb.auth({ persistence: 'local' });
        await auth.signInAnonymously();
  }
}).$mount('#app')

```

在我的代码中，配置了 `sid` 和 `cid` ，这些信息你都需要在腾讯 MTA 的应用管理后台获取。

而下方的配置，则根据你自己的实际需求选择开启即可。

对应的提交：<https://github.com/LCTT/tldr.linux.cn/commit/61821aff4bf75fda3e81d96c6cd34a51efd00773>

#### FunDebug

Fundebug 是我之前在开发小程序的时候用过的 Crash 收集应用。这次刚好也用上了。

因为预算的问题，这里我使用的是免费版本，有几个 tab 是看不到的，但是基本的 Debug 也是够用了。

Fundebug 的安装也很简单， 访问 <https://www.fundebug.com/> ，注册账号， 并创建一个项目，你会获得一个 API Key，后续你可以使用这个 API Key 来初始化你的项目。

执行如下命令来安装依赖：

```
npm install fundebug-javascript fundebug-vue --save
```

并在 `main.js` 中添加如下代码（将 `API-KEY` 替换为你自己的 API KEY）并保存，就可以引入 Fundebug 来进行 Crash 收集了。

```
import * as fundebug from "fundebug-javascript";
import fundebugVue from "fundebug-vue";
fundebug.init({
    apikey: "API-KEY"
})
fundebugVue(fundebug, Vue);
```

对应的提交：<https://github.com/LCTT/tldr.linux.cn/commit/225ca9d38e80eb55defac6383f5b9c228bdab6fe>

### 优化

在开发的过程中经常会出现 Error，这个是难以避免的。在开启了 Crash 收集以后，这些 ERROR 也会被收集到 Fundebug 上，这样会浪费每个月 3000 条的免费额度，因此，需要一个方法在开发环境不启用这些拓展。类似的，也不希望 MTA 统计本地开发的 Page View ，会影响到后续的数据分析。因此，我使用了一些方法来避开这个问题。

```
if (process.env.NODE_ENV === 'production') {
    Vue.use(Mars, {
      open: true, // 开关,若为false,则不会发出上报
      config: {
        sid: 'xxx', // 必填，统计用的appid
      }
  })
  fundebug.apikey = "xxx"
  fundebugVue(fundebug, Vue);
}
```

通过将引用统计的代码包裹在环境的判断代码中，可以实现在渲染的时候，只有生产环境才会渲染出相应的统计代码，从而实现了在开发环境不调用 fundebug 和 mta 统计，避免了开发环境的数据干扰。

对应的提交：<https://github.com/LCTT/tldr.linux.cn/commit/62f87b51fabd7c25cd905560157a546fd62babf2>

### 总结

在这篇文章中，介绍了两个服务，分别是用于统计的腾讯移动分析 MTA 和用于做 Crash 收集的 fundebug，介绍了应该如何在 Vue 应用中接入这两种服务。此外，还根据实际的需求，优化了两个统计的位置，确保产品在开发环境不会工作，从而避免了影响到我们统计数据的准确性。

这篇文章涉及到的代码你都可以在 <https://github.com/LCTT/tldr.linux.cn/blob/master/src/main.js> 看到。


一个通过 IRC 管理所有聊天的开源聊天工具

好古老的方式。

# 一个通过 IRC 管理所有聊天的开源聊天工具

>
> BitlBee 将多个聊天应用集合到一个界面中。在我们的 20 个使用开源提升生产力的系列的第九篇文章中了解如何设置和使用 BitlBee。
>
>
>

去年，我在 19 天里给你介绍了 19 个新（对你而言）的生产力工具。今年，我换了一种方式：使用你在使用或者还没使用的工具，构建一个使你可以在新一年更加高效的环境。

### 将所有聊天都放到 BitlBee 中

即时消息和聊天已经成为网络世界的主要内容。如果你像我一样，你可能打开五六个不同的应用与你的朋友、同事和其他人交谈。关注所有聊天真的很痛苦。谢天谢地，你可以使用一个应用（好吧，是两个）将这些聊天整个到一个地方。

[BitlBee](https://www.bitlbee.org/) 是作为服务运行的应用，它可以将标准的 IRC 客户端与大量的消息服务进行桥接。而且，由于它本质上是 IRC 服务器，因此你可以选择很多客户端。

BitlBee 几乎包含在所有 Linux 发行版中。在 Ubuntu 上安装（我选择的 Linux 桌面），类似这样：

```
sudo apt install bitlbee-libpurple
```

在其他发行版上，包名可能略有不同，但搜索 “bitlbee” 应该就能看到。

你会注意到我用的 libpurple 版的 BitlBee。这个版本能让我使用 [libpurple](https://developer.pidgin.im/wiki/WhatIsLibpurple) 即时消息库中提供的所有协议，该库最初是为 [Pidgin](http://pidgin.im/) 开发的。

安装完成后，服务应会自动启动。现在，使用一个 IRC 客户端（图片中为 [XChat](http://xchat.org/)），我可以连接到端口 6667（标准 IRC 端口）上的服务。

你将自动连接到控制频道 &bitlbee。此频道对于你是独一无二的，在多用户系统上每个人都有一个自己的。在这里你可以配置该服务。

在控制频道中输入 `help`，你可以随时获得完整的文档。浏览它，然后使用 `register` 命令在服务器上注册帐户。

```
register <mypassword>
```

现在，你在服务器上所做的任何配置更改（IM 帐户、设置等）都将在输入 `save` 时保存。每当你连接时，使用 `identify <mypassword>` 连接到你的帐户并加载这些设置。

命令 `help purple` 将显示 libpurple 提供的所有可用协议。例如，我安装了 [telegram-purple](https://github.com/majn/telegram-purple) 包，它增加了连接到 Telegram 的能力。我可以使用 `account add` 命令将我的电话号码作为帐户添加。

```
account add telegram +15555555
```

BitlBee 将显示它已添加帐户。你可以使用 `account list` 列出你的帐户。因为我只有一个帐户，我可以通过 `account 0 on` 登录，它会进行 Telegram 登录，列出我所有的朋友和聊天，接下来就能正常聊天了。

但是，对于 Slack 这个最常见的聊天系统之一呢？你可以安装 [slack-libpurple](https://github.com/dylex/slack-libpurple) 插件，并且对 Slack 执行同样的操作。如果你不愿意编译和安装这些，这可能不适合你。

按照插件页面上的说明操作，安装后重新启动 BitlBee 服务。现在，当你运行 `help purple` 时，应该会列出 Slack。像其他协议一样添加一个 Slack 帐户。

```
account add slack ksonney@myslack.slack.com
account 1 set password my_legcay_API_token
account 1 on
```

你知道么，你已经连接到 Slack 中，你可以通过 `chat add` 命令添加你感兴趣的 Slack 频道。比如：

```
chat add 1 happyparty
```

将 Slack 频道 happyparty 添加为本地频道 #happyparty。现在可以使用标准 IRC `/join` 命令访问该频道。这很酷。

BitlBee 和 IRC 客户端帮助我的（大部分）聊天和即时消息保存在一个地方，并减少了我的分心，因为我不再需要查找并切换到任何一个刚刚找我的应用上。

---

via: <https://opensource.com/article/20/1/open-source-chat-tool>

作者：[Kevin Sonney](https://opensource.com/users/ksonney) 选题：[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/) 荣誉推出


Java 中的数据流和函数式编程

Summary: 学习如何使用 Java 8 中的流 API 和函数式编程结构。

学习如何使用 Java 8 中的流 API 和函数式编程结构。

# Java 中的数据流和函数式编程

>
> 学习如何使用 Java 8 中的流 API 和函数式编程结构。
>
>
>

<img src="content_image/album_202002_06_002505flazlb4cg4aavvb4.jpg" alt="" title="">

当 Java SE 8（又名核心 Java 8）在 2014 年被推出时，它引入了一些更改，从根本上影响了用它进行的编程。这些更改中有两个紧密相连的部分：流 API 和函数式编程构造。本文使用代码示例，从基础到高级特性，介绍每个部分并说明它们之间的相互作用。

### 基础特性

流 API 是在数据序列中迭代元素的简洁而高级的方法。包 `java.util.stream` 和 `java.util.function` 包含了用于流 API 和相关函数式编程构造的新库。当然，代码示例胜过千言万语。

下面的代码段用大约 2,000 个随机整数值填充了一个 `List`：

```
Random rand = new Random2();
List<Integer> list = new ArrayList<Integer>();           // 空 list
for (int i = 0; i < 2048; i++) list.add(rand.nextInt()); // 填充它
```

另外用一个 `for` 循环可用于遍历填充列表，以将偶数值收集到另一个列表中。

流 API 提供了一种更简洁的方法来执行此操作：

```
List <Integer> evens = list
    .stream()                      // 流化 list
    .filter(n -> (n & 0x1) == 0)   // 过滤出奇数值
    .collect(Collectors.toList()); // 收集偶数值
```

这个例子有三个来自流 API 的函数：

* `stream` 函数可以将**集合**转换为流，而流是一个每次可访问一个值的传送带。流化是惰性的（因此也是高效的），因为值是根据需要产生的，而不是一次性产生的。
* `filter` 函数确定哪些流的值（如果有的话）通过了处理管道中的下一个阶段，即 `collect` 阶段。`filter` 函数是 <ruby> 高阶的 <rt>  higher-order </rt></ruby>，因为它的参数是一个函数 —— 在这个例子中是一个 lambda 表达式，它是一个未命名的函数，并且是 Java 新的函数式编程结构的核心。

lambda 语法与传统的 Java 完全不同：

```
n -> (n & 0x1) == 0
```

箭头（一个减号后面紧跟着一个大于号）将左边的参数列表与右边的函数体分隔开。参数 `n` 虽未明确类型，但也可以明确。在任何情况下，编译器都会发现 `n` 是个 `Integer`。如果有多个参数，这些参数将被括在括号中，并用逗号分隔。

在本例中，函数体检查一个整数的最低位（最右）是否为零，这用来表示偶数。过滤器应返回一个布尔值。尽管可以，但该函数的主体中没有显式的 `return`。如果主体没有显式的 `return`，则主体的最后一个表达式即是返回值。在这个例子中，主体按照 lambda 编程的思想编写，由一个简单的布尔表达式 `(n & 0x1) == 0` 组成。

* `collect` 函数将偶数值收集到引用为 `evens` 的列表中。如下例所示，`collect` 函数是线程安全的，因此，即使在多个线程之间共享了过滤操作，该函数也可以正常工作。

### 方便的功能和轻松实现多线程

在生产环境中，数据流的源可能是文件或网络连接。为了学习流 API, Java 提供了诸如 `IntStream` 这样的类型，它可以用各种类型的元素生成流。这里有一个 `IntStream` 的例子：

```
IntStream                          // 整型流
    .range(1, 2048)                // 生成此范围内的整型流
    .parallel()                    // 为多个线程分区数据
    .filter(i -> ((i & 0x1) > 0))  // 奇偶校验 - 只允许奇数通过
    .forEach(System.out::println); // 打印每个值
```

`IntStream` 类型包括一个 `range` 函数，该函数在指定的范围内生成一个整数值流，在本例中，以 1 为增量，从 1 递增到 2048。`parallel` 函数自动划分该工作到多个线程中，在各个线程中进行过滤和打印。（线程数通常与主机系统上的 CPU 数量匹配。）函数 `forEach` 参数是一个*方法引用*，在本例中是对封装在 `System.out` 中的 `println` 方法的引用，方法输出类型为 `PrintStream`。方法和构造器引用的语法将在稍后讨论。

由于具有多线程，因此整数值整体上以任意顺序打印，但在给定线程中是按顺序打印的。例如，如果线程 T1 打印 409 和 411，那么 T1 将按照顺序 409-411 打印，但是其它某个线程可能会预先打印 2045。`parallel` 调用后面的线程是并发执行的，因此它们的输出顺序是不确定的。

### map/reduce 模式

*map/reduce* 模式在处理大型数据集方面变得很流行。一个 map/reduce 宏操作由两个微操作构成。首先，将数据分散（<ruby> 映射 <rt>  mapped </rt></ruby>）到各个工作程序中，然后将单独的结果收集在一起 —— 也可能收集统计起来成为一个值，即<ruby> 归约 <rt>  reduction </rt></ruby>。归约可以采用不同的形式，如以下示例所示。

下面 `Number` 类的实例用 `EVEN` 或 `ODD` 表示有奇偶校验的整数值：

```
public class Number {
    enum Parity { EVEN, ODD }
    private int value;
    public Number(int n) { setValue(n); }
    public void setValue(int value) { this.value = value; }
    public int getValue() { return this.value; }
    public Parity getParity() {
        return ((value & 0x1) == 0) ? Parity.EVEN : Parity.ODD;
    }
    public void dump() {
        System.out.format("Value: %2d (parity: %s)\n", getValue(),
                          (getParity() == Parity.ODD ? "odd" : "even"));
    }
}
```

下面的代码演示了用 `Number` 流进行 map/reduce 的情形，从而表明流 API 不仅可以处理 `int` 和 `float` 等基本类型，还可以处理程序员自定义的类类型。

在下面的代码段中，使用了 `parallelStream` 而不是 `stream` 函数对随机整数值列表进行流化处理。与前面介绍的 `parallel` 函数一样，`parallelStream` 变体也可以自动执行多线程。

```
final int howMany = 200;
Random r = new Random();
Number[] nums = new Number[howMany];
for (int i = 0; i < howMany; i++) nums[i] = new Number(r.nextInt(100));
List<Number> listOfNums = Arrays.asList(nums);  // 将数组转化为 list

Integer sum4All = listOfNums
    .parallelStream()           // 自动执行多线程
    .mapToInt(Number::getValue) // 使用方法引用，而不是 lambda
    .sum();                     // 将流值计算出和值
System.out.println("The sum of the randomly generated values is: " + sum4All);
```

高阶的 `mapToInt` 函数可以接受一个 lambda 作为参数，但在本例中，它接受一个方法引用，即 `Number::getValue`。`getValue` 方法不需要参数，它返回给定的 `Number` 实例的 `int` 值。语法并不复杂：类名 `Number` 后跟一个双冒号和方法名。回想一下先前的例子 `System.out::println`，它在 `System` 类中的 `static` 属性 `out` 后面有一个双冒号。

方法引用 `Number::getValue` 可以用下面的 lambda 表达式替换。参数 `n` 是流中的 `Number` 实例中的之一：

```
mapToInt(n -> n.getValue())
```

通常，lambda 表达式和方法引用是可互换的：如果像 `mapToInt` 这样的高阶函数可以采用一种形式作为参数，那么这个函数也可以采用另一种形式。这两个函数式编程结构具有相同的目的 —— 对作为参数传入的数据执行一些自定义操作。在两者之间进行选择通常是为了方便。例如，lambda 可以在没有封装类的情况下编写，而方法则不能。我的习惯是使用 lambda，除非已经有了适当的封装方法。

当前示例末尾的 `sum` 函数通过结合来自 `parallelStream` 线程的部分和，以线程安全的方式进行归约。但是，程序员有责任确保在 `parallelStream` 调用引发的多线程过程中，程序员自己的函数调用（在本例中为 `getValue`）是线程安全的。

最后一点值得强调。lambda 语法鼓励编写<ruby> 纯函数 <rt>  pure function </rt></ruby>，即函数的返回值仅取决于传入的参数（如果有）；纯函数没有副作用，例如更新一个类中的 `static` 字段。因此，纯函数是线程安全的，并且如果传递给高阶函数的函数参数（例如 `filter` 和 `map` ）是纯函数，则流 API 效果最佳。

对于更细粒度的控制，有另一个流 API 函数，名为 `reduce`，可用于对 `Number` 流中的值求和:

```
Integer sum4AllHarder = listOfNums
    .parallelStream()                           // 多线程
    .map(Number::getValue)                      // 每个 Number 的值
    .reduce(0, (sofar, next) -> sofar + next);  // 求和
```

此版本的 `reduce` 函数带有两个参数，第二个参数是一个函数：

* 第一个参数（在这种情况下为零）是*特征*值，该值用作求和操作的初始值，并且在求和过程中流结束时用作默认值。
* 第二个参数是*累加器*，在本例中，这个 lambda 表达式有两个参数：第一个参数（`sofar`）是正在运行的和，第二个参数（`next`）是来自流的下一个值。运行的和以及下一个值相加，然后更新累加器。请记住，由于开始时调用了 `parallelStream`，因此 `map` 和 `reduce` 函数现在都在多线程上下文中执行。

在到目前为止的示例中，流值被收集，然后被规约，但是，通常情况下，流 API 中的 `Collectors` 可以累积值，而不需要将它们规约到单个值。正如下一个代码段所示，收集活动可以生成任意丰富的数据结构。该示例使用与前面示例相同的 `listOfNums`：

```
Map<Number.Parity, List<Number>> numMap = listOfNums
    .parallelStream()
    .collect(Collectors.groupingBy(Number::getParity));

List<Number> evens = numMap.get(Number.Parity.EVEN);
List<Number> odds = numMap.get(Number.Parity.ODD);
```

第一行中的 `numMap` 指的是一个 `Map`，它的键是一个 `Number` 奇偶校验位（`ODD` 或 `EVEN`），其值是一个具有指定奇偶校验位值的 `Number` 实例的 `List`。同样，通过 `parallelStream` 调用进行多线程处理，然后 `collect` 调用（以线程安全的方式）将部分结果组装到 `numMap` 引用的 `Map` 中。然后，在 `numMap` 上调用 `get` 方法两次，一次获取 `evens`，第二次获取 `odds`。

实用函数 `dumpList` 再次使用来自流 API 的高阶 `forEach` 函数：

```
private void dumpList(String msg, List<Number> list) {
    System.out.println("\n" + msg);
    list.stream().forEach(n -> n.dump()); // 或者使用 forEach(Number::dump)
}
```

这是示例运行中程序输出的一部分：

```
The sum of the randomly generated values is: 3322
The sum again, using a different method:     3322

Evens:

Value: 72 (parity: even)
Value: 54 (parity: even)
...
Value: 92 (parity: even)

Odds:

Value: 35 (parity: odd)
Value: 37 (parity: odd)
...
Value: 41 (parity: odd)
```

### 用于代码简化的函数式结构

函数式结构（如方法引用和 lambda 表达式）非常适合在流 API 中使用。这些构造代表了 Java 中对高阶函数的主要简化。即使在糟糕的过去，Java 也通过 `Method` 和 `Constructor` 类型在技术上支持高阶函数，这些类型的实例可以作为参数传递给其它函数。由于其复杂性，这些类型在生产级 Java 中很少使用。例如，调用 `Method` 需要对象引用（如果方法是非**静态**的）或至少一个类标识符（如果方法是**静态**的）。然后，被调用的 `Method` 的参数作为**对象**实例传递给它，如果没有发生多态（那会出现另一种复杂性！），则可能需要显式向下转换。相比之下，lambda 和方法引用很容易作为参数传递给其它函数。

但是，新的函数式结构在流 API 之外具有其它用途。考虑一个 Java GUI 程序，该程序带有一个供用户按下的按钮，例如，按下以获取当前时间。按钮按下的事件处理程序可能编写如下：

```
JButton updateCurrentTime = new JButton("Update current time");
updateCurrentTime.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        currentTime.setText(new Date().toString());
    }
});
```

这个简短的代码段很难解释。关注第二行，其中方法 `addActionListener` 的参数开始如下：

```
new ActionListener() {
```

这似乎是错误的，因为 `ActionListener` 是一个**抽象**接口，而**抽象**类型不能通过调用 `new` 实例化。但是，事实证明，还有其它一些实例被实例化了：一个实现此接口的未命名内部类。如果上面的代码封装在名为 `OldJava` 的类中，则该未命名的内部类将被编译为 `OldJava$1.class`。`actionPerformed` 方法在这个未命名的内部类中被重写。

现在考虑使用新的函数式结构进行这个令人耳目一新的更改：

```
updateCurrentTime.addActionListener(e -> currentTime.setText(new Date().toString()));
```

lambda 表达式中的参数 `e` 是一个 `ActionEvent` 实例，而 lambda 的主体是对按钮上的 `setText` 的简单调用。

### 函数式接口和函数组合

到目前为止，使用的 lambda 已经写好了。但是，为了方便起见，我们可以像引用封装方法一样引用 lambda 表达式。以下一系列简短示例说明了这一点。

考虑以下接口定义：

```
@FunctionalInterface // 可选，通常省略
interface BinaryIntOp {
    abstract int compute(int arg1, int arg2); // abstract 声明可以被删除
}
```

注释 `@FunctionalInterface` 适用于声明*唯一*抽象方法的任何接口；在本例中，这个抽象接口是 `compute`。一些标准接口，（例如具有唯一声明方法 `run` 的 `Runnable` 接口）同样符合这个要求。在此示例中，`compute` 是已声明的方法。该接口可用作引用声明中的目标类型：

```
BinaryIntOp div = (arg1, arg2) -> arg1 / arg2;
div.compute(12, 3); // 4
```

包 `java.util.function` 提供各种函数式接口。以下是一些示例。

下面的代码段介绍了参数化的 `Predicate` 函数式接口。在此示例中，带有参数 `String` 的 `Predicate<String>` 类型可以引用具有 `String` 参数的 lambda 表达式或诸如 `isEmpty` 之类的 `String` 方法。通常情况下，Predicate 是一个返回布尔值的函数。

```
Predicate<String> pred = String::isEmpty; // String 方法的 predicate 声明
String[] strings = {"one", "two", "", "three", "four"};
Arrays.asList(strings)
   .stream()
   .filter(pred)                  // 过滤掉非空字符串
   .forEach(System.out::println); // 只打印空字符串
```

在字符串长度为零的情况下，`isEmpty` Predicate 判定结果为 `true`。 因此，只有空字符串才能进入管道的 `forEach` 阶段。

下一段代码将演示如何将简单的 lambda 或方法引用组合成更丰富的 lambda 或方法引用。考虑这一系列对 `IntUnaryOperator` 类型的引用的赋值，它接受一个整型参数并返回一个整型值：

```
IntUnaryOperator doubled = n -> n * 2;
IntUnaryOperator tripled = n -> n * 3;
IntUnaryOperator squared = n -> n * n;
```

`IntUnaryOperator` 是一个 `FunctionalInterface`，其唯一声明的方法为 `applyAsInt`。现在可以单独使用或以各种组合形式使用这三个引用 `doubled`、`tripled` 和 `squared`：

```
int arg = 5;
doubled.applyAsInt(arg); // 10
tripled.applyAsInt(arg); // 15
squared.applyAsInt(arg); // 25
```

以下是一些函数组合的样例：

```
int arg = 5;
doubled.compose(squared).applyAsInt(arg); // 5 求 2 次方后乘 2：50
tripled.compose(doubled).applyAsInt(arg); // 5 乘 2 后再乘 3：30
doubled.andThen(squared).applyAsInt(arg); // 5 乘 2 后求 2 次方：100
squared.andThen(tripled).applyAsInt(arg); // 5 求 2 次方后乘 3：75
```

函数组合可以直接使用 lambda 表达式实现，但是引用使代码更简洁。

### 构造器引用

构造器引用是另一种函数式编程构造，而这些引用在比 lambda 和方法引用更微妙的上下文中非常有用。再一次重申，代码示例似乎是最好的解释方式。

考虑这个 [POJO](https://en.wikipedia.org/wiki/Plain_old_Java_object) 类：

```
public class BedRocker { // 基岩的居民
    private String name;
    public BedRocker(String name) { this.name = name; }
    public String getName() { return this.name; }
    public void dump() { System.out.println(getName()); }
}
```

该类只有一个构造函数，它需要一个 `String` 参数。给定一个名字数组，目标是生成一个 `BedRocker` 元素数组，每个名字代表一个元素。下面是使用了函数式结构的代码段：

```
String[] names = {"Fred", "Wilma", "Peebles", "Dino", "Baby Puss"};

Stream<BedRocker> bedrockers = Arrays.asList(names).stream().map(BedRocker::new);
BedRocker[] arrayBR = bedrockers.toArray(BedRocker[]::new);

Arrays.asList(arrayBR).stream().forEach(BedRocker::dump);
```

在较高的层次上，这个代码段将名字转换为 `BedRocker` 数组元素。具体来说，代码如下所示。`Stream` 接口（在包 `java.util.stream` 中）可以被参数化，而在本例中，生成了一个名为 `bedrockers` 的 `BedRocker` 流。

`Arrays.asList` 实用程序再次用于流化一个数组 `names`，然后将流的每一项传递给 `map` 函数，该函数的参数现在是构造器引用 `BedRocker::new`。这个构造器引用通过在每次调用时生成和初始化一个 `BedRocker` 实例来充当一个对象工厂。在第二行执行之后，名为 `bedrockers` 的流由五项 `BedRocker` 组成。

这个例子可以通过关注高阶 `map` 函数来进一步阐明。在通常情况下，一个映射将一个类型的值（例如，一个 `int`）转换为另一个*相同*类型的值（例如，一个整数的后继）:

```
map(n -> n + 1) // 将 n 映射到其后继
```

然而，在 `BedRocker` 这个例子中，转换更加戏剧化，因为一个类型的值（代表一个名字的 `String`）被映射到一个*不同*类型的值，在这个例子中，就是一个 `BedRocker` 实例，这个字符串就是它的名字。转换是通过一个构造器调用来完成的，它是由构造器引用来实现的：

```
map(BedRocker::new) // 将 String 映射到 BedRocker
```

传递给构造器的值是 `names` 数组中的其中一项。

此代码示例的第二行还演示了一个你目前已经非常熟悉的转换：先将数组先转换成 `List`，然后再转换成 `Stream`：

```
Stream<BedRocker> bedrockers = Arrays.asList(names).stream().map(BedRocker::new);
```

第三行则是另一种方式 —— 流 `bedrockers` 通过使用*数组*构造器引用 `BedRocker[]::new` 调用 `toArray` 方法:

```
BedRocker[ ] arrayBR = bedrockers.toArray(BedRocker[]::new);
```

该构造器引用不会创建单个 `BedRocker` 实例，而是创建这些实例的整个数组：该构造器引用现在为 `BedRocker[]:new`，而不是 `BedRocker::new`。为了进行确认，将 `arrayBR` 转换为 `List`，再次对其进行流式处理，以便可以使用 `forEach` 来打印 `BedRocker` 的名字。

```
Fred
Wilma
Peebles
Dino
Baby Puss
```

该示例对数据结构的微妙转换仅用几行代码即可完成，从而突出了可以将 lambda，方法引用或构造器引用作为参数的各种高阶函数的功能。

### <ruby> 柯里化 <rt>  Currying </rt></ruby>

*柯里化*函数是指减少函数执行任何工作所需的显式参数的数量（通常减少到一个）。（该术语是为了纪念逻辑学家 Haskell Curry。）一般来说，函数的参数越少，调用起来就越容易，也更健壮。（回想一下一些需要半打左右参数的噩梦般的函数！）因此，应将柯里化视为简化函数调用的一种尝试。`java.util.function` 包中的接口类型适合于柯里化，如以下示例所示。

引用的 `IntBinaryOperator` 接口类型是为函数接受两个整型参数，并返回一个整型值：

```
IntBinaryOperator mult2 = (n1, n2) -> n1 * n2;
mult2.applyAsInt(10, 20); // 200
mult2.applyAsInt(10, 30); // 300
```

引用 `mult2` 强调了需要两个显式参数，在本例中是 10 和 20。

前面介绍的 `IntUnaryOperator` 比 `IntBinaryOperator` 简单，因为前者只需要一个参数，而后者则需要两个参数。两者均返回整数值。因此，目标是将名为 `mult2` 的两个参数 `IntBinraryOperator` 柯里化成一个单一的 `IntUnaryOperator` 版本 `curriedMult2`。

考虑 `IntFunction<R>` 类型。此类型的函数采用整型参数，并返回类型为 `R` 的结果，该结果可以是另一个函数 —— 更准确地说，是 `IntBinaryOperator`。让一个 lambda 返回另一个 lambda 很简单：

```
arg1 -> (arg2 -> arg1 * arg2) // 括号可以省略
```

完整的 lambda 以 `arg1` 开头，而该 lambda 的主体以及返回的值是另一个以 `arg2` 开头的 lambda。返回的 lambda 仅接受一个参数（`arg2`），但返回了两个数字的乘积（`arg1` 和 `arg2`）。下面的概述，再加上代码，应该可以更好地进行说明。

以下是如何柯里化 `mult2` 的概述：

* 类型为 `IntFunction<IntUnaryOperator>` 的 lambda 被写入并调用，其整型值为 10。返回的 `IntUnaryOperator` 缓存了值 10，因此变成了已柯里化版本的 `mult2`，在本例中为 `curriedMult2`。
* 然后使用单个显式参数（例如，20）调用 `curriedMult2` 函数，该参数与缓存的参数（在本例中为 10）相乘以生成返回的乘积。。

这是代码的详细信息：

```
// 创建一个接受一个参数 n1 并返回一个单参数 n2 -> n1 * n2 的函数，该函数返回一个（n1 * n2 乘积的）整型数。
IntFunction<IntUnaryOperator> curriedMult2Maker = n1 -> (n2 -> n1 * n2);
```

调用 `curriedMult2Maker` 生成所需的 `IntUnaryOperator` 函数：

```
// 使用 curriedMult2Maker 获取已柯里化版本的 mult2。
// 参数 10 是上面的 lambda 的 n1。
IntUnaryOperator curriedMult2 = curriedMult2Maker2.apply(10);
```

值 `10` 现在缓存在 `curriedMult2` 函数中，以便 `curriedMult2` 调用中的显式整型参数乘以 10：

```
curriedMult2.applyAsInt(20); // 200 = 10 * 20
curriedMult2.applyAsInt(80); // 800 = 10 * 80
```

缓存的值可以随意更改：

```
curriedMult2 = curriedMult2Maker.apply(50); // 缓存 50
curriedMult2.applyAsInt(101);               // 5050 = 101 * 50
```

当然，可以通过这种方式创建多个已柯里化版本的 `mult2`，每个版本都有一个 `IntUnaryOperator`。

柯里化充分利用了 lambda 的强大功能：可以很容易地编写 lambda 表达式来返回需要的任何类型的值，包括另一个 lambda。

### 总结

Java 仍然是基于类的面向对象的编程语言。但是，借助流 API 及其支持的函数式构造，Java 向函数式语言（例如 Lisp）迈出了决定性的（同时也是受欢迎的）一步。结果是 Java 更适合处理现代编程中常见的海量数据流。在函数式方向上的这一步还使以在前面的代码示例中突出显示的管道的方式编写清晰简洁的 Java 代码更加容易：

```
dataStream
   .parallelStream() // 多线程以提高效率
   .filter(...)      // 阶段 1
   .map(...)         // 阶段 2
   .filter(...)      // 阶段 3
   ...
   .collect(...);    // 或者，也可以进行归约：阶段 N
```

自动多线程，以 `parallel` 和 `parallelStream` 调用为例，建立在 Java 的 fork/join 框架上，该框架支持 <ruby> 任务窃取 <rt>  task stealing </rt></ruby> 以提高效率。假设 `parallelStream` 调用后面的线程池由八个线程组成，并且 `dataStream` 被八种方式分区。某个线程（例如，T1）可能比另一个线程（例如，T7）工作更快，这意味着应该将 T7 的某些任务移到 T1 的工作队列中。这会在运行时自动发生。

在这个简单的多线程世界中，程序员的主要职责是编写线程安全函数，这些函数作为参数传递给在流 API 中占主导地位的高阶函数。尤其是 lambda 鼓励编写纯函数（因此是线程安全的）函数。

---

via: <https://opensource.com/article/20/1/javastream>

作者：[Marty Kalin](https://opensource.com/users/mkalindepauledu) 选题：[lujun9972](https://github.com/lujun9972) 译者：[laingke](https://github.com/laingke) 校对：[wxy](https://github.com/wxy)

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


使用这个 Twitter 客户端在 Linux 终端中发推特

# 使用这个 Twitter 客户端在 Linux 终端中发推特

>
> 在我们的 20 个使用开源提升生产力的系列的第十篇文章中，使用 Rainbow Stream 跟上你的 Twitter 流而无需离开终端。
>
>
>

去年，我在 19 天里给你介绍了 19 个新（对你而言）的生产力工具。今年，我换了一种方式：使用你在使用或者还没使用的工具，构建一个使你可以在新一年更加高效的环境。

### 通过 Rainbow Stream 跟上Twitter

我喜欢社交网络和微博。它快速、简单，还有我可以与世界分享我的想法。当然，缺点是几乎所有非 Windows 的桌面客户端都对是网站的封装。[Twitter](https://twitter.com/home) 有很多客户端，但我真正想要的是轻量、易于使用，最重要的是吸引人的客户端。

[Rainbow Stream](https://rainbowstream.readthedocs.io/en/latest/) 是好看的 Twitter 客户端之一。它简单易用，并且可以通过 `pip3 install rainbowstream` 快速安装。第一次运行时，它将打开浏览器窗口，并让你通过 Twitter 授权。完成后，你将回到命令行，你的 Twitter 时间线将开始滚动。

要了解的最重要的命令是 `p` 暂停推流、`r` 继续推流、`h` 得到帮助，以及 `t` 发布新的推文。例如，`h tweets` 将提供发送和回复推文的所有选项。另一个有用的帮助页面是 `h messages`，它提供了处理直接消息的命令，这是我妻子和我经常使用的东西。还有很多其他命令，我会回头获得很多帮助。

随着时间线的滚动，你可以看到它有完整的 UTF-8 支持，并以正确的字体显示推文被转推以及喜欢的次数，图标和 emoji 也能正确显示。

关于 Rainbow Stream 的*最好*功能之一就是你不必放弃照片和图像。默认情况下，此功能是关闭的，但是你可以使用 `config` 命令尝试它。

```
config IMAGE_ON_TERM = true
```

此命令将任何图像渲染为 ASCII 艺术。如果你有大量照片流，它可能会有点多，但是我喜欢。它有非常复古的 1990 年代 BBS 感觉，我也确实喜欢 1990 年代的 BBS 场景。

你还可以使用 Rainbow Stream 管理列表、屏蔽某人、拉黑某人、关注、取消关注以及 Twitter API 的所有其他功能。它还支持主题，因此你可以用喜欢的颜色方案自定义流。

当我正在工作并且不想在浏览器上打开另一个选项卡时，Rainbow Stream 让我可以留在终端中。

---

via: <https://opensource.com/article/20/1/tweet-terminal-rainbow-stream>

作者：[Kevin Sonney](https://opensource.com/users/ksonney) 选题：[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/) 荣誉推出


跟我学 “Linux” 小程序 Web 版开发（五）：遇到的一些坑

Summary: 但出于用户体验和速度的考虑，我们选择了国内的七牛云作为页面的承载。

但出于用户体验和速度的考虑，我们选择了国内的七牛云作为页面的承载。

# 跟我学 “Linux” 小程序 Web 版开发（五）：遇到的一些坑

在[第一篇文章](/article-11844-1.html)中，我提到，项目的自动部署是放在 now.sh 上，以方便预览。但出于用户体验和速度的考虑，我们选择了国内的七牛云作为页面的承载。不过，七牛毕竟是一个对象存储，而不是一个专业的静态托管业务，在使用上遇到了一些坑，还好经过努力都得到了解决。

### 七牛的 Bucket 名规则

和绝大多数的云计算厂商一样，七牛也使用了 Bucket 来作为存储的单元。

由于这个项目要挂 Linux.cn 的二级域名，于是我便让老王创建了一个 Bucket，绑定域名，并通过七牛自带的权限控制机制，将其分发给我，让我来使用。

在我的个人控制台看到了这个 Bucket：

#### 发现问题

我通过控制台，手动上传了生成的文件后，确认没有问题，就将相应的功能写入到 Github Action 的 配置文件（[配置文件点这里](https://github.com/LCTT/tldr.linux.cn/blob/master/.github/workflows/nodejs.yml)）中，实现自动的部署。但在部署过程中，屡次报错，不知道为什么。在开启了 DEBUG 信息后发现，竟然是 Bucket 不存在（[点我查看 CI 的构建信息](https://github.com/LCTT/tldr.linux.cn/runs/421608751?check_suite_focus=true)）。

#### 解决问题

和老王沟通以后才发现，是七牛的 Bucket 名机制的问题。

在七牛中进行权限分配的时候，会要求你为 Bucket 设定一个别名，而且名字和已有的名字必须是不同的，这导致我看到的 Bucket 的名和老王创建的 Bucket 名是不相同的。

而我使用的 AK 和 SK 又是让老王设置在 Github 后台的 Secrets，Bucket 则是我自己设置的，所以就出现了问题。具体来说，是下面这张图。

由于我填写的 Bucket 是我自己看到的，而不是老王那边真正的 Bucket 名称，导致在上传的时候，无法找到 Bucket。在将 Bucket 名称替换为老王那边看到的 Bucket 名称后，问题得到解决。

### 七牛不支持 Vue Router 的 History 模式

在[第二篇文章](/article-11848-1.html)中，我提到了引入了 Vue 的 History 模式来优化体验。但是，七牛本身作为一个存储系统，没有转发的功能，也就导致其没有办法很好的支持 Vue History 模式。

在经过一番研究后，找到了解决方案，就是将 index 页面，同时作为 404 页面，这样就可以实现从某种意义上的将所有请求都转发给 Index 页面。

你需要做的，就是将 `index.html` 复制一份，并重命名为 `errno-404`，并和其他文件一同上传，这样用户请求一些不存在的文件时，会自动将请求转发给 `errno-404`， 又因为这个文件的内容是索引文件的内容，所以就可以实现了请求的转发。

相关代码的实现，你可以在 <https://github.com/LCTT/tldr.linux.cn/blob/master/.github/workflows/nodejs.yml> 这里找到。

### 总结

在这篇文章中，介绍了七牛的 Bucket 问题，以及 Vue Router History 模式在七牛下的解决方案。


MidnightBSD：或许是你通往 FreeBSD 的大门

# MidnightBSD：或许是你通往 FreeBSD 的大门

[FreeBSD](https://www.freebsd.org/) 是一个开源操作系统，衍生自著名的 <ruby>   伯克利软件套件  <rt>  Berkeley Software Distribution </rt></ruby>（BSD）。FreeBSD 的第一个版本发布于 1993 年，并且仍然在继续发展。2007 年左右，Lucas Holt 想要利用 OpenStep（现在是 Cocoa）的 Objective-C 框架、widget 工具包和应用程序开发工具的 [GnuStep](https://en.wikipedia.org/wiki/GNUstep) 实现，来创建一个 FreeBSD 的分支。为此，他开始开发 MidnightBSD 桌面发行版。

MidnightBSD（以 Lucas 的猫 Midnight 命名）仍然在积极地（尽管缓慢）开发。从 2017 年 8 月开始，可以获得最新的稳定发布版本（0.8.6）（LCTT 译注：截止至本译文发布时，当前是 2019/10/31 发布的 1.2 版）。尽管 BSD 发行版不是你所说的用户友好型发行版，但上手安装是熟悉如何处理 文本（ncurses）安装过程以及通过命令行完成安装的好方法。

这样，你最终会得到一个非常可靠的 FreeBSD 分支的桌面发行版。这需要花费一点精力，但是如果你是一名正在寻找扩展你的技能的 Linux 用户……这是一个很好的起点。

我将带你走过安装 MidnightBSD 的流程，如何添加一个图形桌面环境，然后如何安装应用程序。

### 安装

正如我所提到的，这是一个文本（ncurses）安装过程，因此在这里找不到可以用鼠标点击的地方。相反，你将使用你键盘的 `Tab` 键和箭头键。在你下载[最新的发布版本](http://www.midnightbsd.org/download/)后，将它刻录到一个 CD/DVD 或 USB 驱动器，并启动你的机器（或者在 [VirtualBox](https://www.virtualbox.org/) 中创建一个虚拟机）。安装程序将打开并给你三个选项（图 1）。使用你的键盘的箭头键选择 “Install”，并敲击回车键。

*图 1: 启动 MidnightBSD 安装程序。*

在这里要经历相当多的屏幕。其中很多屏幕是一目了然的：

1. 设置非默认键盘映射（是/否）
2. 设置主机名称
3. 添加可选系统组件（文档、游戏、32 位兼容性、系统源码代码）
4. 对硬盘分区
5. 管理员密码
6. 配置网络接口
7. 选择地区（时区）
8. 启用服务（例如 ssh）
9. 添加用户（图 2）

*图 2: 向系统添加一个用户。*

在你向系统添加用户后，你将被进入到一个窗口中（图 3），在这里，你可以处理任何你可能忘记配置或你想重新配置的东西。如果你不需要作出任何更改，选择 “Exit”，然后你的配置就会被应用。

*图 3: 应用你的配置。*

在接下来的窗口中，当出现提示时，选择 “No”，接下来系统将重启。在 MidnightBSD 重启后，你已经为下一阶段的安装做好了准备。

### 后安装阶段

当你最新安装的 MidnightBSD 启动时，你将发现你自己处于命令提示符当中。此刻，还没有图形界面。要安装应用程序，MidnightBSD 依赖于 `mport` 工具。比如说你想安装 Xfce 桌面环境。为此，登录到 MidnightBSD 中，并发出下面的命令：

```
sudo mport index
sudo mport install xorg
```

你现在已经安装好 Xorg 窗口服务器了，它允许你安装桌面环境。使用命令来安装 Xfce ：

```
sudo mport install xfce
```

现在 Xfce 已经安装好。不过，我们必须让它同命令 `startx` 一起启用。为此，让我们先安装 nano 编辑器。发出命令：

```
sudo mport install nano
```

随着 nano 安装好，发出命令：

```
nano ~/.xinitrc
```

这个文件仅包含一行内容：

```
exec startxfce4
```

保存并关闭这个文件。如果你现在发出命令 `startx`, Xfce 桌面环境将会启动。你应该会感到有点熟悉了吧（图 4）。

*图 4: Xfce 桌面界面已准备好服务。*

因为你不会总是想必须发出命令 `startx`，你希望启用登录守护进程。然而，它却没有安装。要安装这个子系统，发出命令：

```
sudo mport install mlogind
```

当完成安装后，通过在 `/etc/rc.conf` 文件中添加一个项目来在启动时启用 mlogind。在 `rc.conf` 文件的底部，添加以下内容：

```
mlogind_enable=”YES”
```

保存并关闭该文件。现在，当你启动（或重启）机器时，你应该会看到图形登录屏幕。在写这篇文章的时候，在登录后我最后得到一个空白屏幕和讨厌的 X 光标。不幸的是，目前似乎并没有这个问题的解决方法。所以，要访问你的桌面环境，你必须使用 `startx` 命令。

### 安装应用

默认情况下，你找不到很多能可用的应用程序。如果你尝试使用 `mport` 安装应用程序，你很快就会感到沮丧，因为只能找到很少的应用程序。为解决这个问题，我们需要使用 `svnlite` 命令来查看检出的可用 mport 软件列表。回到终端窗口，并发出命令：

```
svnlite co http://svn.midnightbsd.org/svn/mports/trunk mports
```

在你完成这些后，你应该看到一个命名为 `~/mports` 的新目录。使用命令 `cd ~/.mports` 更改到这个目录。发出 `ls` 命令，然后你应该看到许多的类别（图 5）。

*图 5: mport 现在可用的应用程序类别。*

你想安装 Firefox 吗？如果你查看 `www` 目录，你将看到一个 `linux-firefox` 列表。发出命令：

```
sudo mport install linux-firefox
```

现在你应该会在 Xfce 桌面菜单中看到一个 Firefox 项。翻找所有的类别，并使用 `mport` 命令来安装你需要的所有软件。

### 一个悲哀的警告

一个悲哀的小警告是，`mport` （通过 `svnlite`）仅能找到的一个办公套件的版本是 OpenOffice 3 。那是非常过时的。尽管在 `~/mports/editors` 目录中能找到 Abiword ，但是它看起来不能安装。甚至在安装 OpenOffice 3 后，它会输出一个执行格式错误。换句话说，你不能使用 MidnightBSD 在办公生产效率方面做很多的事情。但是，嘿嘿，如果你周围正好有一个旧的 Palm Pilot，你可以安装 pilot-link。换句话说，可用的软件不足以构成一个极其有用的桌面发行版……至少对普通用户不是。但是，如果你想在 MidnightBSD 上开发，你将找到很多可用的工具可以安装（查看 `~/mports/devel` 目录）。你甚至可以使用命令安装 Drupal ：

```
sudo mport install drupal7
```

当然，在此之后，你将需要创建一个数据库（MySQL 已经安装）、安装 Apache（`sudo mport install apache24`），并配置必要的 Apache 配置。

显然地，已安装的和可以安装的是一个应用程序、系统和服务的大杂烩。但是随着足够多的工作，你最终可以得到一个能够服务于特殊目的的发行版。

### 享受 \*BSD 优良

这就是如何使 MidnightBSD 启动，并使其运行某种有用的桌面发行版的方法。它不像很多其它的 Linux 发行版一样快速简便，但是如果你想要一个促使你思考的发行版，这可能正是你正在寻找的。尽管大多数竞争对手都准备了很多可以安装的应用软件，但 MidnightBSD 无疑是一个 Linux 爱好者或管理员应该尝试的有趣挑战。

---

via: <https://www.linux.com/learn/intro-to-linux/2018/5/midnightbsd-could-be-your-gateway-freebsd>

作者：[Jack Wallen](https://www.linux.com/users/jlwallen) 选题：[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 打扮的像个黑客屏幕

# 把你的 Linux 打扮的像个黑客屏幕

GNU/Linux 的好玩，真是有目共睹，世界上越来越多的用户已经使用和玩耍它很久啦。

今天 Linux 已经成为了一个庞大的生态链系统。从树莓派到超级计算机，从网络交换机到 SDN 都有 Linux 操作系统的身影。其实 GNU/Linux 系统本身指的是很多 GNU 自由开源软件和 Linux 内核的集合体。各种 Linux 发行版可以让你满足各种独特或者大众的功能。Linux 系统是当前系统管理员或者网络管理员尽量要熟悉的操作系统之一。当然，Linux 也提供了很多好玩的工具软件，我们以 Debian GNU/Linux 8.7 为例进行配置，将你的电脑终端命令行改变成一个就像电影里黑客的界面，装逼很有用哦。

### 配置 Vim，添加语法高亮及其它插件

因为 Debian GNU/Linux 系统已经默认使用了 Vim 7.4 版本，可以使用命令 `vi /etc/vim/vimrc`，添加如下内容：

```
syntax on
fileype plugin indent on
set hlsearch
```

这三个选项直接打开语法和搜索高亮显示，文件类型检测等功能。

安装 Vim 相关插件，可以使用命令：

```
apt-get install vim-addon-manager vim-addon-mw-utils \
  vim-gocomplete vim-erlang vim-erlang-syntax vim-gnome \
  vim-gocomplete vim-perl vim-python vim-ruby vim-scripts \
  vim-syntax-docker vim-syntax-go vim-syntax-gtk vim-tcl \
  vim-vimerl vim-vimerl-syntax vim-vimoutliner vim-youcompleteme
```

### 配置 root 用户本地 Bash 环境变量支持颜色显示

使用命令 `vi ~/.bashrc`，将如下内容：

```
alias ls='ls $LS_OPTIONS'
alias ll='ls $LS_OPTIONS -l'
alias l='ls $LS_OPTIONS -lA'
```

三行前的 `#` 号去掉。

### 安装 guake、screen 并进行配置

安装配置 guake 终端，使用命令 `apt-get install guake` 完成安装。

将其加入 gnome3 的开机启动程序，之后只要使用功能键 `F12` 就可以直接唤入唤出该终端进行命令行操作。`F12` 唤出后可右键点击首选项进行设置，建议用户参考如图配置：

安装 screen，使用命令 `apt-get install screen` 即可完成。

此时用户 guake 终端默认解释器列表中将出现 `/usr/bin/screen` 选项，用户可以直接指定。再次使用 `F12` 唤出 guake 终端就可以直接使用 screen 快捷键进行分屏操作了。

screen 常用快捷键如下：

* `Ctrl+a S` 水平分割当前窗口
* `Ctrl+a |` 垂直分割当前窗口
* `Ctrl+a c` 创建一个新的运行 shell 的窗口并切换到该窗口
* `Ctrl+a X` 关闭当前窗口
* `Ctrl+a x` 锁定当前窗口
* `Ctrl+a k` 杀死当前窗口

完成效果图如下：

### 修改提示符

使用命令 `vi /etc/bash.bashrc`， 将 `PS1` 内容修改如下：

```
PS1='\[\e[34;1m\]\t${debian_chroot:+($debian_chroot)}\[\e[32;1m\][\[\e[33;1m\]\u\[\e[31;1m\]@\[\e[33;1m\]\h \[\e[36;1m\]\w\[\e[32;1m\]]\[\e[34;1m\]\$ \[\e[0m\]'
```

使用命令 `vi ~/.bashrc`，查找 `if [ "$color_prompt" = yes ]; then`，并按照如下示例修改当前用户 `PS1` 内容。

```
if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
   #PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
   PS1='\[\e[34;1m\]\t${debian_chroot:+($debian_chroot)}\[\e[32;1m\][\[\e[33;1m\]\u\[\e[31;1m\]@\[\e[33;1m\]\h \[\e[36;1m\]\w\[\e[32;1m\]]\[\e[34;1m\]\$ \[\e[0m\]'
fi
```

试试看吧，你的 GNU/Linux 命令行会很惊艳哦！！！


COPR 仓库中 4 个很酷的新项目（2020.01）

Summary: 本文介绍了 COPR 中一些有趣的新项目。

本文介绍了 COPR 中一些有趣的新项目。

# COPR 仓库中 4 个很酷的新项目（2020.01）

COPR 是个人软件仓库[集合](https://copr.fedorainfracloud.org/)，它不在 Fedora 中。这是因为某些软件不符合轻松打包的标准；或者它可能不符合其他 Fedora 标准，尽管它是自由而开源的。COPR 可以在 Fedora 套件之外提供这些项目。COPR 中的软件不受 Fedora 基础设施的支持，或者是由项目自己背书的。但是，这是一种尝试新的或实验性的软件的一种巧妙的方式。

本文介绍了 COPR 中一些有趣的新项目。如果你第一次使用 COPR，请参阅 [COPR 用户文档](https://docs.pagure.org/copr.copr/user_documentation.html#)。

### Contrast

[Contrast](https://gitlab.gnome.org/World/design/contrast) 是一款小应用，用于检查两种颜色之间的对比度并确定其是否满足 [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) 中指定的要求。可以使用十六进制 RGB 代码或使用颜色选择器选择颜色。除了显示对比度之外，Contrast 还以选定的颜色为背景上显示短文本来显示比较。

#### 安装说明

[仓库](https://copr.fedorainfracloud.org/coprs/atim/contrast/)当前为 Fedora 31 和 Rawhide 提供了 Contrast。要安装 Contrast，请使用以下命令：

```
sudo dnf copr enable atim/contrast
sudo dnf install contrast
```

### Pamixer

[Pamixer](https://github.com/cdemoulins/pamixer) 是一个使用 PulseAudio 调整和监控声音设备音量的命令行工具。你可以显示设备的当前音量并直接增加/减小它，或静音/取消静音。Pamixer 可以列出所有源和接收器。

#### 安装说明

[仓库](https://copr.fedorainfracloud.org/coprs/atim/contrast/)当前为 Fedora 31 和 Rawhide 提供了 Pamixer。要安装 Pamixer，请使用以下命令：

```
sudo dnf copr enable opuk/pamixer
sudo dnf install pamixer
```

### PhotoFlare

[PhotoFlare](https://photoflare.io/) 是一款图像编辑器。它有简单且布局合理的用户界面，其中的大多数功能都可在工具栏中使用。尽管它不支持使用图层，但 PhotoFlare 提供了诸如各种颜色调整、图像变换、滤镜、画笔和自动裁剪等功能。此外，PhotoFlare 可以批量编辑图片，来对所有图片应用相同的滤镜和转换，并将结果保存在指定目录中。

#### 安装说明

[仓库](https://copr.fedorainfracloud.org/coprs/atim/contrast/)当前为 Fedora 31 提供了 PhotoFlare。要安装 PhotoFlare，请使用以下命令：

```
sudo dnf copr enable adriend/photoflare
sudo dnf install photoflare
```

### Tdiff

[Tdiff](https://github.com/F-i-f/tdiff) 是用于比较两个文件树的命令行工具。除了显示某些文件或目录仅存在于一棵树中之外，tdiff 还显示文件大小、类型和内容，所有者用户和组 ID、权限、修改时间等方面的差异。

#### 安装说明

[仓库](https://copr.fedorainfracloud.org/coprs/atim/contrast/)当前为 Fedora 29-31、Rawhide、EPEL 6-8 和其他发行版提供了 tdiff。要安装 tdiff，请使用以下命令：

```
sudo dnf copr enable fif/tdiff
sudo dnf install tdiff
```

---

via: <https://fedoramagazine.org/4-cool-new-projects-to-try-in-copr-for-january-2020/>

作者：[Dominik Turecek](https://fedoramagazine.org/author/dturecek/) 选题：[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/) 荣誉推出


Linux 命令行简介

# Linux 命令行简介

>
> 下面是一些针对刚开始使用 Linux 命令行的人的热身练习。警告：它可能会上瘾。
>
>
>

<img src="content_image/album_202002_06_230821ayy3x77ee3vx3iiv.jpg" alt="" title="">

如果你是 Linux 新手，或者从来没有花时间研究过命令行，你可能不会理解为什么这么多 Linux 爱好者坐在舒适的桌面前兴奋地输入命令来使用大量工具和应用。在这篇文章中，我们将快速浏览一下命令行的奇妙之处，看看能否让你着迷。

首先，要使用命令行，你必须打开一个命令工具（也称为“命令提示符”）。如何做到这一点将取决于你运行的 Linux 版本。例如，在 RedHat 上，你可能会在屏幕顶部看到一个 “Activities” 选项卡，它将打开一个选项列表和一个用于输入命令的小窗口（类似 “cmd” 为你打开的窗口）。在 Ubuntu 和其他一些版本中，你可能会在屏幕左侧看到一个小的终端图标。在许多系统上，你可以同时按 `Ctrl+Alt+t` 键打开命令窗口。

如果你使用 PuTTY 之类的工具登录 Linux 系统，你会发现自己已经处于命令行界面。

一旦你得到你的命令行窗口，你会发现自己坐在一个提示符面前。它可能只是一个 `$` 或者像 `user@system:~$` 这样的东西，但它意味着系统已经准备好为你运行命令了。

一旦你走到这一步，就应该开始输入命令了。下面是一些要首先尝试的命令，以及这里是一些特别有用的命令的 [PDF](https://www.networkworld.com/article/3391029/must-know-linux-commands.html) 和适合打印和做成卡片的双面命令手册。

| 命令 | 用途 |
| --- | --- |
| `pwd` | 显示我在文件系统中的位置（在最初进入系统时运行将显示主目录） |
| `ls` | 列出我的文件 |
| `ls -a` | 列出我更多的文件（包括隐藏文件） |
| `ls -al` | 列出我的文件，并且包含很多详细信息（包括日期、文件大小和权限） |
| `who` | 告诉我谁登录了（如果只有你，不要失望） |
| `date` | 日期提醒我今天是星期几（也显示时间） |
| `ps` | 列出我正在运行的进程（可能只是你的 shell 和 `ps` 命令） |

一旦你从命令行角度习惯了 Linux 主目录之后，就可以开始探索了。也许你会准备好使用以下命令在文件系统中闲逛：

| 命令 | 用途 |
| --- | --- |
| `cd /tmp` | 移动到其他文件夹（本例中，打开 `/tmp` 文件夹） |
| `ls` | 列出当前位置的文件 |
| `cd` | 回到主目录（不带参数的 `cd` 总是能将你带回到主目录） |
| `cat .bashrc` | 显示文件的内容（本例中显示 `.bashrc` 文件的内容） |
| `history` | 显示最近执行的命令 |
| `echo hello` | 跟自己说 “hello” |
| `cal` | 显示当前月份的日历 |

要了解为什么高级 Linux 用户如此喜欢命令行，你将需要尝试其他一些功能，例如重定向和管道。“重定向”是当你获取命令的输出并将其放到文件中而不是在屏幕上显示时。“管道”是指你将一个命令的输出发送给另一条将以某种方式对其进行操作的命令。这是可以尝试的命令：

| 命令 | 用途 |
| --- | --- |
| `echo "echo hello" > tryme` | 创建一个新的文件并将 “echo hello” 写入该文件 |
| `chmod 700 tryme` | 使新建的文件可执行 |
| `tryme` | 运行新文件（它应当运行文件中包含的命令并且显示 “hello” ） |
| `ps aux` | 显示所有运行中的程序 |
| `ps aux | grep $USER` | 显示所有运行中的程序，但是限制输出的内容包含你的用户名 |
| `echo $USER` | 使用环境变量显示你的用户名 |
| `whoami` | 使用命令显示你的用户名 |
| `who | wc -l` | 计数所有当前登录的用户数目 |

### 总结

一旦你习惯了基本命令，就可以探索其他命令并尝试编写脚本。 你可能会发现 Linux 比你想象的要强大并且好用得多.

---

via: <https://www.networkworld.com/article/3518440/intro-to-the-linux-command-line.html>

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

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


# Ubuntu 19.04 已经到期！现有用户必须升级到 Ubuntu 19.10

>
> Ubuntu 19.04 已在 2020 年 1 月 23 日到期，这意味着运行 Ubuntu 19.04 的系统将不再会接收到安全和维护更新，因此将使其容易受到攻击。
>
>
>

[Ubuntu 19.04](https://itsfoss.com/ubuntu-19-04-release/) 发布于 2019 年 4 月 18 日。由于它不是长期支持（LTS）版本，因此只有 9 个月的支持。完成它的发行周期后，Ubuntu 19.04 于 2020 年 1 月 23 日到期。

Ubuntu 19.04 带来了一些视觉和性能方面的改进，为时尚和美观的 Ubuntu 外观铺平了道路。与其他常规 Ubuntu 版本一样，它的生命周期为 9 个月。它如今结束了。

### Ubuntu 19.04 终止了吗？这是什么意思？

EOL（End of life）是指在某个日期之后操作系统版本将无法获得更新。你可能已经知道 Ubuntu（或其他操作系统）提供了安全性和维护升级，以使你的系统免受网络攻击。当发行版到期后，操作系统将停止接收这些重要更新。

如果你的操作系统版本到期后继续使用该系统，那么系统将容易受到网络和恶意软件的攻击。不仅如此。在 Ubuntu 中，你使用 APT 从软件中心下载的应用也不会更新。实际上，你将不再能够[使用 apt-get 命令安装新软件](https://itsfoss.com/apt-get-linux-guide/)（如果不是立即，那就是逐渐地）。

### 所有 Ubuntu 19.04 用户必须升级到 Ubuntu 19.10

从 2020 年 1 月 23 日开始，Ubuntu 19.04 将停止接收更新。你必须升级到 2020 年 7 月之前受支持的 Ubuntu 19.10。这也适用于其他[官方 Ubuntu 衍生版](https://itsfoss.com/which-ubuntu-install/)，例如 Lubuntu、Xubuntu、Kubuntu 等。

你可以在“设置 -> 细节” 或使用如下命令来[检查你的 Ubuntu 版本](https://itsfoss.com/how-to-know-ubuntu-unity-version/)：

```
lsb_release -a
```

#### 如何升级到 Ubuntu 19.10？

值得庆幸的是，Ubuntu 提供了简单的方法来将现有系统升级到新版本。实际上，Ubuntu 还会提示你有新的 Ubuntu 版本可用，你应该升级到该版本。

如果你的互联网连接良好，那么可以使用[和更新 Ubuntu 一样的 Software Updater 工具](https://itsfoss.com/update-ubuntu/)。在上图中，你只需单击 “Upgrade” 按钮并按照说明进行操作。我已经编写了有关使用此方法[升级到 Ubuntu 18.04](https://itsfoss.com/upgrade-ubuntu-version/)的文章。

如果你没有良好的互联网连接，那么有一种临时方案。在外部磁盘上备份家目录或重要数据。

然后，制作一个 Ubuntu 19.10 的 Live USB。下载 Ubuntu 19.10 ISO，并使用 Ubuntu 系统上已安装的启动磁盘创建器从该 ISO 创建 Live USB。

从该 Live USB 引导，然后继续“安装” Ubuntu 19.10。在安装过程中，你应该看到一个删除 Ubuntu 19.04 并将其替换为 Ubuntu 19.10 的选项。选择此选项，然后像重新[安装 Ubuntu](https://itsfoss.com/install-ubuntu/)一样进行下去。

#### 你是否仍在使用 Ubuntu 19.04、18.10、17.10 或其他不受支持的版本？

你应该注意，目前仅 Ubuntu 16.04、18.04 和 19.10（或更高版本）版本还受支持。如果你运行的不是这些 Ubuntu 版本，那么你必须升级到较新版本。

---

via: <https://itsfoss.com/ubuntu-19-04-end-of-life/>

作者：[Abhishek Prakash](https://itsfoss.com/author/abhishek/) 选题：[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/) 荣誉推出


你最喜欢哪个 Linux 发行版？

用 arch linux 相对其他发行版来说学习曲线平缓、能力提升更快，建议新手及入门后使用。省去了编译的繁杂，同时又展现了 linux 下各组件的细节。赞！

我选择Fedora

呵呵，2020年居然还有人问这个问题，你每个发行版用过一段时间就知道该选哪个了

用了10多年的ubuntu，开箱即用，上手太简单了，文档太多了。

来自120.244.111.97的 Chromium 79.0|Ubuntu 用户

我喜欢Fedora，因为新潮，软件比较新，总是在用新的技术，还算稳定。

讲真的，windows是最优秀的一个

来自广东广州的 Microsoft Edge 18.18363|Windows 10 用户

# 你最喜欢哪个 Linux 发行版？

你最喜欢哪个 Linux 发行版？虽然有所变化，但现在仍有数百种 [Linux 发行版](https://distrowatch.com/)保持活跃且运作良好。发行版、包管理器和桌面的组合为 Linux 用户创建了无数客制化系统环境。

我们询问了社区的作者们，哪个是他们的最爱以及原因。尽管回答中存在一些共性（由于各种原因，Fedora 和 Ubuntu 是最受欢迎的选择），但我们也听到一些惊奇的回答。以下是他们的一些回答：

>
> “我使用 Fedora 发行版！我喜欢这样的社区，成员们共同创建一个令人惊叹的操作系统，展现了开源软件世界最伟大的造物。”——Matthew Miller
>
>
>

>
> “我在家中使用 Arch。作为一名游戏玩家，我希望可以轻松使用最新版本的 Wine 和 GFX 驱动，同时最大限度地掌控我的系统。所以我选择一个滚动升级并且每个包都保持领先的发行版。”——Aimi Hobson
>
>
>

>
> “NixOS，在业余爱好者市场中没有比这更合适的。”——Alexander Sosedkin
>
>
>

>
> “我用过每个 Fedora 版本作为我的工作系统。这意味着我从第一个版本开始使用。从前，我问自己是否会忘记我使用的是哪一个版本。而这一天已经到来了，是从什么时候开始忘记了的呢？”——Hugh Brock
>
>
>

>
> “通常，在我的家里和办公室里都有运行 Ubuntu、CentOS 和 Fedora 的机器。我依赖这些发行版来完成各种工作。Fedora 速度很快，而且可以获取最新版本的应用和库。Ubuntu 有大型社区支持，可以轻松使用。CentOS 则当我们需要稳如磐石的服务器平台时。”——Steve Morris
>
>
>

>
> “我最喜欢？对于社区以及如何为发行版构建软件包（从源码构建而非二进制文件），我选择 Fedora。对于可用包的范围和包的定义和开发，我选择 Debian。对于文档，我选择 Arch。对于新手的提问，我以前会推荐 Ubuntu，而现在会推荐 Fedora。”——Al Stone
>
>
>

---

自从 2014 以来，我们一直向社区提出这一问题。除了 2015 年 PCLinuxOS 出乎意料的领先，Ubuntu 往往每年都获得粉丝们的青睐。其他受欢迎的竞争者还包括 Fedora、Debian、Mint 和 Arch。在新的十年里，哪个发行版更吸引你？如果我们的投票列表中没有你最喜欢的选择，请在评论中告诉我们。

下面是过去七年来你最喜欢的 Linux 发行版投票的总览。你可以在我们去年的年刊《[Opensource.com 上的十年最佳](https://opensource.com/downloads/2019-yearbook-special-edition)》中看到它。[点击这里](https://opensource.com/downloads/2019-yearbook-special-edition)下载完整版电子书！

---

via: <https://opensource.com/article/20/1/favorite-linux-distribution>

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

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


在 Linux 终端中阅读 Reddit

# 在 Linux 终端中阅读 Reddit

>
> 在我们的 20 个使用开源提升生产力的系列的第十一篇文章中使用 Reddit 客户端 Tuir 在工作中短暂休息一下。
>
>
>

去年，我在 19 天里给你介绍了 19 个新（对你而言）的生产力工具。今年，我换了一种方式：使用你在使用或者还没使用的工具，构建一个使你可以在新一年更加高效的环境。

### 使用 Tuir 阅读 Reddit

短暂休息对于保持生产力很重要。我休息时喜欢去的地方之一是 [Reddit](https://www.reddit.com/)，如果你愿意，这可能是一个很好的资源。我在那里发现了各种有关 DevOps、生产力、Emacs、鸡和 ChromeOS 项目的文章。这些讨论可能很有价值。我还关注了一些只有动物图片的子板，因为我喜欢动物（而不只是鸡）照片，有时经过长时间的工作后，我真正需要的是小猫照片。

当我阅读 Reddit（不仅仅是看动物宝宝的图片）时，我使用 [Tuir](https://gitlab.com/ajak/tuir)（Reddit 终端 UI）。Tuir 是功能齐全的 Reddit 客户端，可以在运行 Python 的任何系统上运行。安装是通过 `pip` 完成的，非常简单。

首次运行时，Tuir 会进入 Reddit 默认文章列表。屏幕的顶部和底部有列出不同命令的栏。顶部栏显示你在 Reddit 上的位置，第二行显示根据 Reddit “Hot/New/Controversial” 等类别筛选的命令。按下筛选器前面的数字触发筛选。

你可以使用箭头键或 `j`、`k`、`h` 和 `l` 键浏览列表，这与 Vi/Vim 使用的键相同。底部栏有用于应用导航的命令。如果要跳转到另一个子板，只需按 `/` 键打开提示，然后输入你要进入的子板名称。

某些东西除非你登录，否则无法访问。如果你尝试执行需要登录的操作，那么 Tuir 就会提示你，例如发布新文章 （`c`）或赞成/反对 （`a` 和 `z`）。要登录，请按 `u` 键。这将打开浏览器以通过 OAuth2 登录，Tuir 将保存令牌。之后，你的用户名应出现在屏幕的右上方。

Tuir 还可以打开浏览器来查看图像、加载链接等。稍作调整，它甚至可以在终端中显示图像（尽管我没有让它可以正常工作）。

总的来说，我对 Tuir 在我需要休息时能快速跟上 Reddit 感到很满意。

Tuir 是现已淘汰的 [RTV](https://github.com/michael-lazar/rtv) 的两个分叉之一。另一个是 [TTRV](https://github.com/tildeclub/ttrv)，它还无法通过 `pip` 安装，但功能相同。我期待看到它们随着时间的推移脱颖而出。

---

via: <https://opensource.com/article/20/1/open-source-reddit-client>

作者：[Kevin Sonney](https://opensource.com/users/ksonney) 选题：[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/) 荣誉推出