mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-03-09 01:30:10 +08:00
Merge branch 'master' of https://github.com/LCTT/TranslateProject into new
This commit is contained in:
commit
31f07fb7e1
@ -0,0 +1,155 @@
|
||||
用 Hugo 30 分钟搭建静态博客
|
||||
======
|
||||
> 了解 Hugo 如何使构建网站变得有趣。
|
||||
|
||||

|
||||
|
||||
你是不是强烈地想搭建博客来将自己对软件框架等的探索学习成果分享呢?你是不是面对缺乏指导文档而一团糟的项目就有一种想去改变它的冲动呢?或者换个角度,你是不是十分期待能创建一个属于自己的个人博客网站呢?
|
||||
|
||||
很多人在想搭建博客之前都有一些严重的迟疑顾虑:感觉自己缺乏内容管理系统(CMS)的相关知识,更缺乏时间去学习这些知识。现在,如果我说不用花费大把的时间去学习 CMS 系统、学习如何创建一个静态网站、更不用操心如何去强化网站以防止它受到黑客攻击的问题,你就可以在 30 分钟之内创建一个博客?你信不信?利用 Hugo 工具,就可以实现这一切。
|
||||
|
||||

|
||||
|
||||
Hugo 是一个基于 Go 语言开发的静态站点生成工具。也许你会问,为什么选择它?
|
||||
|
||||
* 无需数据库、无需需要各种权限的插件、无需跑在服务器上的底层平台,更没有额外的安全问题。
|
||||
* 都是静态站点,因此拥有轻量级、快速响应的服务性能。此外,所有的网页都是在部署的时候生成,所以服务器负载很小。
|
||||
* 极易操作的版本控制。一些 CMS 平台使用它们自己的版本控制软件(VCS)或者在网页上集成 Git 工具。而 Hugo,所有的源文件都可以用你所选的 VCS 软件来管理。
|
||||
|
||||
### 0-5 分钟:下载 Hugo,生成一个网站
|
||||
|
||||
直白的说,Hugo 使得写一个网站又一次变得有趣起来。让我们来个 30 分钟计时,搭建一个网站。
|
||||
|
||||
为了简化 Hugo 安装流程,这里直接使用 Hugo 可执行安装文件。
|
||||
|
||||
1. 下载和你操作系统匹配的 Hugo [版本][2];
|
||||
2. 压缩包解压到指定路径,例如 windows 系统的 `C:\hugo_dir` 或者 Linux 系统的 `~/hugo_dir` 目录;下文中的变量 `${HUGO_HOME}` 所指的路径就是这个安装目录;
|
||||
3. 打开命令行终端,进入安装目录:`cd ${HUGO_HOME}`;
|
||||
4. 确认 Hugo 已经启动:
|
||||
* Unix 系统:`${HUGO_HOME}/[hugo version]`;
|
||||
* Windows 系统:`${HUGO_HOME}\[hugo.exe version]`,例如:cmd 命令行中输入:`c:\hugo_dir\hugo version`。
|
||||
|
||||
为了书写上的简化,下文中的 `hugo` 就是指 hugo 可执行文件所在的路径(包括可执行文件),例如命令 `hugo version` 就是指命令 `c:\hugo_dir\hugo version` 。(LCTT 译注:可以把 hugo 可执行文件所在的路径添加到系统环境变量下,这样就可以直接在终端中输入 `hugo version`)
|
||||
|
||||
如果命令 `hugo version` 报错,你可能下载了错误的版本。当然,有很多种方法安装 Hugo,更多详细信息请查阅 [官方文档][3]。最稳妥的方法就是把 Hugo 可执行文件放在某个路径下,然后执行的时候带上路径名
|
||||
5. 创建一个新的站点来作为你的博客,输入命令:`hugo new site awesome-blog`;
|
||||
6. 进入新创建的路径下: `cd awesome-blog`;
|
||||
|
||||
恭喜你!你已经创建了自己的新博客。
|
||||
|
||||
### 5-10 分钟:为博客设置主题
|
||||
|
||||
Hugo 中你可以自己构建博客的主题或者使用网上已经有的一些主题。这里选择 [Kiera][4] 主题,因为它简洁漂亮。按以下步骤来安装该主题:
|
||||
|
||||
1. 进入主题所在目录:`cd themes`;
|
||||
2. 克隆主题:`git clone https://github.com/avianto/hugo-kiera kiera`。如果你没有安装 Git 工具:
|
||||
* 从 [Github][5] 上下载 hugo 的 .zip 格式的文件;
|
||||
* 解压该 .zip 文件到你的博客主题 `theme` 路径;
|
||||
* 重命名 `hugo-kiera-master` 为 `kiera`;
|
||||
3. 返回博客主路径:`cd awesome-blog`;
|
||||
4. 激活主题;通常来说,主题(包括 Kiera)都自带文件夹 `exampleSite`,里面存放了内容配置的示例文件。激活 Kiera 主题需要拷贝它提供的 `config.toml` 到你的博客下:
|
||||
* Unix 系统:`cp themes/kiera/exampleSite/config.toml .`;
|
||||
* Windows 系统:`copy themes\kiera\exampleSite\config.toml .`;
|
||||
* 选择 `Yes` 来覆盖原有的 `config.toml`;
|
||||
|
||||
5. ( 可选操作 )你可以选择可视化的方式启动服务器来验证主题是否生效:`hugo server -D` 然后在浏览器中输入 `http://localhost:1313`。可用通过在终端中输入 `Crtl+C` 来停止服务器运行。现在你的博客还是空的,但这也给你留了写作的空间。它看起来如下所示:
|
||||
|
||||

|
||||
|
||||
你已经成功的给博客设置了主题!你可以在官方 [Hugo 主题][4] 网站上找到上百种漂亮的主题供你使用。
|
||||
|
||||
### 10-20 分钟:给博客添加内容
|
||||
|
||||
对于碗来说,它是空的时候用处最大,可以用来盛放东西;但对于博客来说不是这样,空博客几乎毫无用处。在这一步,你将会给博客添加内容。Hugo 和 Kiera 主题都为这个工作提供了方便性。按以下步骤来进行你的第一次提交:
|
||||
|
||||
1. archetypes 将会是你的内容模板。
|
||||
2. 添加主题中的 archtypes 至你的博客:
|
||||
* Unix 系统: `cp themes/kiera/archetypes/* archetypes/`
|
||||
* Windows 系统:`copy themes\kiera\archetypes\* archetypes\`
|
||||
* 选择 `Yes` 来覆盖原来的 `default.md` 内容架构类型
|
||||
|
||||
3. 创建博客 posts 目录:
|
||||
* Unix 系统: `mkdir content/posts`
|
||||
* Windows 系统: `mkdir content\posts`
|
||||
|
||||
4. 利用 Hugo 生成你的 post:
|
||||
* Unix 系统:`hugo nes posts/first-post.md`;
|
||||
* Windows 系统:`hugo new posts\first-post.md`;
|
||||
|
||||
5. 在文本编辑器中打开这个新建的 post 文件:
|
||||
* Unix 系统:`gedit content/posts/first-post.md`;
|
||||
* Windows 系统:`notepadd content\posts\first-post.md`;
|
||||
|
||||
此刻,你可以疯狂起来了。注意到你的提交文件中包括两个部分。第一部分是以 `+++` 符号分隔开的。它包括了提交文档的主要数据,例如名称、时间等。在 Hugo 中,这叫做前缀。在前缀之后,才是正文。下面编辑第一个提交文件内容:
|
||||
|
||||
```
|
||||
+++
|
||||
title = "First Post"
|
||||
date = 2018-03-03T13:23:10+01:00
|
||||
draft = false
|
||||
tags = ["Getting started"]
|
||||
categories = []
|
||||
+++
|
||||
|
||||
Hello Hugo world! No more excuses for having no blog or documentation now!
|
||||
```
|
||||
|
||||
现在你要做的就是启动你的服务器:`hugo server -D`;然后打开浏览器,输入 `http://localhost:1313/`。
|
||||
|
||||

|
||||
|
||||
### 20-30 分钟:调整网站
|
||||
|
||||
前面的工作很完美,但还有一些问题需要解决。例如,简单地命名你的站点:
|
||||
|
||||
1. 终端中按下 `Ctrl+C` 以停止服务器。
|
||||
2. 打开 `config.toml`,编辑博客的名称,版权,你的姓名,社交网站等等。
|
||||
|
||||
当你再次启动服务器后,你会发现博客私人订制味道更浓了。不过,还少一个重要的基础内容:主菜单。快速的解决这个问题。返回 `config.toml` 文件,在末尾插入如下一段:
|
||||
|
||||
```
|
||||
[[menu.main]]
|
||||
name = "Home" #Name in the navigation bar
|
||||
weight = 10 #The larger the weight, the more on the right this item will be
|
||||
url = "/" #URL address
|
||||
[[menu.main]]
|
||||
name = "Posts"
|
||||
weight = 20
|
||||
url = "/posts/"
|
||||
```
|
||||
|
||||
上面这段代码添加了 `Home` 和 `Posts` 到主菜单中。你还需要一个 `About` 页面。这次是创建一个 `.md` 文件,而不是编辑 `config.toml` 文件:
|
||||
|
||||
1. 创建 `about.md` 文件:`hugo new about.md` 。注意它是 `about.md`,不是 `posts/about.md`。该页面不是博客提交内容,所以你不想它显示到博客内容提交当中吧。
|
||||
2. 用文本编辑器打开该文件,输入如下一段:
|
||||
|
||||
```
|
||||
+++
|
||||
title = "About"
|
||||
date = 2018-03-03T13:50:49+01:00
|
||||
menu = "main" #Display this page on the nav menu
|
||||
weight = "30" #Right-most nav item
|
||||
meta = "false" #Do not display tags or categories
|
||||
+++
|
||||
|
||||
> Waves are the practice of the water. Shunryu Suzuki
|
||||
```
|
||||
|
||||
当你启动你的服务器并输入:`http://localhost:1313/`,你将会看到你的博客。(访问我 Gihub 主页上的 [例子][6] )如果你想让文章的菜单栏和 Github 相似,给 `themes/kiera/static/css/styles.css` 打上这个 [补丁][7]。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/3/start-blog-30-minutes-hugo
|
||||
|
||||
作者:[Marek Czernek][a]
译者:[jrg](https://github.com/jrglinux)
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/mczernek
|
||||
[1]:https://gohugo.io/
|
||||
[2]:https://github.com/gohugoio/hugo/releases
|
||||
[3]:https://gohugo.io/getting-started/installing/
|
||||
[4]:https://themes.gohugo.io/
|
||||
[5]:https://github.com/avianto/hugo-kiera
|
||||
[6]:https://m-czernek.github.io/awesome-blog/
|
||||
[7]:https://github.com/avianto/hugo-kiera/pull/18/files
|
@ -1,16 +1,17 @@
|
||||
如何在 Ubuntu 18.04 和其他 Linux 发行版中创建照片幻灯片
|
||||
如何在 Ubuntu 和其他 Linux 发行版中创建照片幻灯片
|
||||
======
|
||||
|
||||
创建照片幻灯片只需点击几下。以下是如何在 Ubuntu 18.04 和其他 Linux 发行版中制作照片幻灯片。
|
||||
|
||||
![How to create slideshow of photos in Ubuntu Linux][1]
|
||||
|
||||
想象一下,你的朋友和亲戚正在拜访你,并请求你展示最近的活动/旅行照片。
|
||||
想象一下,你的朋友和亲戚正在拜访你,并请你展示最近的活动/旅行照片。
|
||||
|
||||
你将照片保存在计算机上,并整齐地放在单独的文件夹中。你邀请计算机附近的所有人。你进入该文件夹,单击其中一张图片,然后按箭头键逐个显示照片。
|
||||
|
||||
但那太累了!如果这些图片每隔几秒自动更改一次,那将会好很多。
|
||||
|
||||
这称之为为幻灯片,我将向你展示如何在 Ubuntu 中创建照片幻灯片。这能让你在文件夹中循环播放图片并以全屏模式显示它们。
|
||||
这称之为幻灯片,我将向你展示如何在 Ubuntu 中创建照片幻灯片。这能让你在文件夹中循环播放图片并以全屏模式显示它们。
|
||||
|
||||
### 在 Ubuntu 18.04 和其他 Linux 发行版中创建照片幻灯片
|
||||
|
||||
@ -20,19 +21,19 @@
|
||||
|
||||
如果你在 Ubuntu 18.04 或任何其他发行版中使用 GNOME,那么你很幸运。Gnome 的默认图像浏览器,Eye of GNOME,能够在当前文件夹中显示图片的幻灯片。
|
||||
|
||||
只需单击其中一张图片,你将在程序的右上角菜单中看到设置选项。它看起来像三条横栏堆在彼此的顶部。
|
||||
只需单击其中一张图片,你将在程序的右上角菜单中看到设置选项。它看起来像堆叠在一起的三条横栏。
|
||||
|
||||
你会在这里看到几个选项。勾选幻灯片选项,它将全屏显示图像。
|
||||
|
||||
![How to create slideshow of photos in Ubuntu Linux][2]
|
||||
|
||||
默认情况下,图像以 5 秒的间隔变化。你可以进入 Preferences->Slideshow 来更改幻灯片放映间隔。
|
||||
默认情况下,图像以 5 秒的间隔变化。你可以进入 “Preferences -> Slideshow” 来更改幻灯片放映间隔。
|
||||
|
||||
![change slideshow interval in Ubuntu][3]Changing slideshow interval
|
||||
![change slideshow interval in Ubuntu][3]
|
||||
|
||||
#### 方法 2:使用 Shotwell Photo Manager 进行照片幻灯片放映
|
||||
|
||||
[Shotwell][4] 是一种流行的[ Linux 照片管理程序][5]。适用于所有主要的 Linux 发行版。
|
||||
[Shotwell][4] 是一款流行的 [Linux 照片管理程序][5]。适用于所有主要的 Linux 发行版。
|
||||
|
||||
如果尚未安装,请在你的发行版软件中心中搜索 Shotwell 并安装。
|
||||
|
||||
@ -55,7 +56,7 @@ via: https://itsfoss.com/photo-slideshow-ubuntu/
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,24 +1,24 @@
|
||||
用 zsh 提高生产力的5个 tips
|
||||
用 zsh 提高生产力的 5 个技巧
|
||||
======
|
||||
> zsh 提供了数之不尽的功能和特性,这里有五个可以让你在命令行暴增效率的方法。
|
||||
|
||||

|
||||
|
||||
Z shell (亦称 zsh) 是 *unx 系统中的命令解析器 。 它跟 `sh` (Bourne shell) 家族的其他解析器 ( 如 `bash` 和 `ksh` ) 有着相似的特点,但它还提供了大量的高级特性以及强大的命令行编辑功能(选项?),如增强版tab补全。
|
||||
Z shell([zsh][1])是 Linux 和类 Unix 系统中的一个[命令解析器][2]。 它跟 sh (Bourne shell) 家族的其它解析器(如 bash 和 ksh)有着相似的特点,但它还提供了大量的高级特性以及强大的命令行编辑功能,如增强版 Tab 补全。
|
||||
|
||||
由于 zsh 有好几百页的文档去描述他的特性,所以我无法在这里阐明 zsh 的所有功能。在本文,我会列出5个 tips,让你通过使用 zsh 来提高你的生产力。
|
||||
在这里不可能涉及到 zsh 的所有功能,[描述][3]它的特性需要好几百页。在本文中,我会列出 5 个技巧,让你通过在命令行使用 zsh 来提高你的生产力。
|
||||
|
||||
### 1\. 主题和插件
|
||||
### 1、主题和插件
|
||||
|
||||
多年来,开源社区已经为 zsh 开发了数不清的主题和插件。主题是预定义提示符的配置,而插件则是一组常用的别名命令和功能,让你更方便的使用一种特定的命令或者编程语言。
|
||||
多年来,开源社区已经为 zsh 开发了数不清的主题和插件。主题是一个预定义提示符的配置,而插件则是一组常用的别名命令和函数,可以让你更方便的使用一种特定的命令或者编程语言。
|
||||
|
||||
如果你现在想开始用 zsh 的主题和插件,那么使用 zsh 的配置框架 (configuiration framework) 是你最快的入门方式。在众多的配置框架中,最受欢迎的则是 [Oh My Zsh][4]。在默认配置中,他就已经为 zsh 启用了一些合理的配置,同时它也自带多个主题和插件。
|
||||
如果你现在想开始用 zsh 的主题和插件,那么使用一种 zsh 的配置框架是你最快的入门方式。在众多的配置框架中,最受欢迎的则是 [Oh My Zsh][4]。在默认配置中,它就已经为 zsh 启用了一些合理的配置,同时它也自带上百个主题和插件。
|
||||
|
||||
由于主题会在你的命令行提示符之前添加一些常用的信息,比如你 Git 仓库的状态,或者是当前使用的 Python 虚拟环境,所以它会让你的工作更高效。只需要看到这些信息,你就不用再敲命令去重新获取它们,而且这些提示也相当酷炫。
|
||||
下图就是我(作者)选用的主题 [Powerlevel9k][5]
|
||||
主题会在你的命令行提示符之前添加一些有用的信息,比如你 Git 仓库的状态,或者是当前使用的 Python 虚拟环境,所以它会让你的工作更高效。只需要看到这些信息,你就不用再敲命令去重新获取它们,而且这些提示也相当酷炫。下图就是我选用的主题 [Powerlevel9k][5]:
|
||||
|
||||
![zsh Powerlevel9K theme][7]
|
||||
|
||||
zsh 主题 Powerlevel9k
|
||||
*zsh 主题 Powerlevel9k*
|
||||
|
||||
除了主题,Oh my Zsh 还自带了大量常用的 zsh 插件。比如,通过启用 Git 插件,你可以用一组简便的命令别名操作 Git, 比如
|
||||
|
||||
@ -36,39 +36,37 @@ gcs='git commit -S'
|
||||
glg='git log --stat'
|
||||
```
|
||||
|
||||
zsh 还有许多插件是用于多种编程语言,打包系统和一些平时在命令行中常用的工具。
|
||||
以下是我(作者) Ferdora 工作站中用到的插件表:
|
||||
zsh 还有许多插件可以用于许多编程语言、打包系统和一些平时在命令行中常用的工具。以下是我 Ferdora 工作站中用到的插件表:
|
||||
|
||||
```
|
||||
git golang fedora docker oc sudo vi-mode virtualenvwrapper
|
||||
```
|
||||
|
||||
### 2\. 智能的命令别名
|
||||
### 2、智能的命令别名
|
||||
|
||||
命令别名在 zsh 中十分常用。为你常用的命令定义别名可以节省你的打字时间。Oh My Zsh 默认配置了一些常用的命令别名,包括目录导航命令别名,为常用的命令添加额外的选项,比如:
|
||||
命令别名在 zsh 中十分有用。为你常用的命令定义别名可以节省你的打字时间。Oh My Zsh 默认配置了一些常用的命令别名,包括目录导航命令别名,为常用的命令添加额外的选项,比如:
|
||||
|
||||
```
|
||||
ls='ls --color=tty'
|
||||
grep='grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}'
|
||||
```
|
||||
|
||||
除了命令别名以外, zsh 还自带两种额外常用的别名类型:后缀别名和全局别名。
|
||||
|
||||
除了命令别名意外, zsh 还自带两种额外常用的别名类型:后缀别名和全局别名。
|
||||
|
||||
后缀别名可以让你在基于文件后缀的前提下,在命令行中利用指定程序打开这个文件。比如,要用 vim 打开 YAML 文件,可以定义以下命令行别名:
|
||||
后缀别名可以让你基于文件后缀,在命令行中利用指定程序打开这个文件。比如,要用 vim 打开 YAML 文件,可以定义以下命令行别名:
|
||||
|
||||
```
|
||||
alias -s {yml,yaml}=vim
|
||||
```
|
||||
|
||||
现在,如果你在命令行中输入任何后缀名为 `yml` 或 `yaml` 文件, zsh 都会用 vim 打开这个文件
|
||||
现在,如果你在命令行中输入任何后缀名为 `yml` 或 `yaml` 文件, zsh 都会用 vim 打开这个文件。
|
||||
|
||||
```
|
||||
$ playbook.yml
|
||||
# Opens file playbook.yml using vim
|
||||
```
|
||||
|
||||
全局别名可以让你在使用命令行的任何时刻创建命令别名,而不仅仅是在开始的时候。这个在你想替换常用文件名或者管道命令的时候就显得非常有用了。比如
|
||||
全局别名可以让你创建一个可在命令行的任何地方展开的别名,而不仅仅是在命令开始的时候。这个在你想替换常用文件名或者管道命令的时候就显得非常有用了。比如:
|
||||
|
||||
```
|
||||
alias -g G='| grep -i'
|
||||
@ -84,9 +82,9 @@ drwxr-xr-x. 6 rgerardi rgerardi 4096 Aug 24 14:51 Downloads
|
||||
|
||||
接着,我们就来看看 zsh 是如何导航文件系统的。
|
||||
|
||||
### 3\. 便捷的目录导航
|
||||
### 3、便捷的目录导航
|
||||
|
||||
当你使用命令行的时候, 在不同的目录之间切换访问是最常见的工作了。 zsh 提供了一些十分有用的目录导航功能来简化这个操作。这些功能已经集成到 Oh My Zsh 中了, 而你可以用以下命令来启用它
|
||||
当你使用命令行的时候,在不同的目录之间切换访问是最常见的工作了。 zsh 提供了一些十分有用的目录导航功能来简化这个操作。这些功能已经集成到 Oh My Zsh 中了, 而你可以用以下命令来启用它
|
||||
|
||||
```
|
||||
setopt autocd autopushd \ pushdignoredups
|
||||
@ -104,7 +102,7 @@ $ pwd
|
||||
|
||||
如果想要回退,只要输入 `-`:
|
||||
|
||||
Zsh 会记录你访问过的目录,这样下次你就可以快速切换到这些目录中。如果想要看这个目录列表,只要输入 `dirs -v`:
|
||||
zsh 会记录你访问过的目录,这样下次你就可以快速切换到这些目录中。如果想要看这个目录列表,只要输入 `dirs -v`:
|
||||
|
||||
```
|
||||
$ dirs -v
|
||||
@ -168,7 +166,7 @@ $ pwd
|
||||
/tmp
|
||||
```
|
||||
|
||||
最后,你可以在 zsh 中利用 Tab 来自动补全目录名称。你可以先输入目录的首字母,然后用 `TAB` 来补全它们:
|
||||
最后,你可以在 zsh 中利用 Tab 来自动补全目录名称。你可以先输入目录的首字母,然后按 `TAB` 键来补全它们:
|
||||
|
||||
```
|
||||
$ pwd
|
||||
@ -179,22 +177,22 @@ $ Projects/Opensource.com/zsh-5tips/
|
||||
|
||||
以上仅仅是 zsh 强大的 Tab 补全系统中的一个功能。接来下我们来探索它更多的功能。
|
||||
|
||||
### 4\. 先进的 Tab 补全
|
||||
### 4、先进的 Tab 补全
|
||||
|
||||
Zsh 强大的补全系统是它其中一个卖点。为了简便起见,我称它为 Tab 补全,然而在系统底层,它不仅仅只做一件事。这里通常包括扩展以及命令的补全,我会在这里同时讨论它们。如果想了解更多,详见 [用户手册][8] ( [User's Guide][8] )。
|
||||
zsh 强大的补全系统是它的卖点之一。为了简便起见,我称它为 Tab 补全,然而在系统底层,它起到了几个作用。这里通常包括展开以及命令补全,我会在这里用讨论它们。如果想了解更多,详见 [用户手册][8]。
|
||||
|
||||
在 Oh My Zsh 中,命令补全是默认启用的。要启用它,你只要在 `.zshrc` 文件中添加以下命令:
|
||||
|
||||
在 Oh My Zsh 中,命令补全是默认可用的。要启用它,你只要在 `.zshrc` 文件中添加以下命令:
|
||||
```
|
||||
autoload -U compinit
|
||||
compinit
|
||||
```
|
||||
|
||||
Zsh 的补全系统非常智能。他会根据当前上下文来进行命令的提示——比如,你输入了 `cd` 和 `TAB`,zsh 只会为你提示目录名,因为它知道
|
||||
当前的 `cd` 没有任何作用。
|
||||
zsh 的补全系统非常智能。它会尝试唯一提示可用在当前上下文环境中的项目 —— 比如,你输入了 `cd` 和 `TAB`,zsh 只会为你提示目录名,因为它知道其它的项目放在 `cd` 后面没用。
|
||||
|
||||
反之,如果你使用 `ssh` 或者 `ping` 这类与用户或者主机相关的命令, zsh 便会提示用户名。
|
||||
反之,如果你使用与用户相关的命令便会提示用户名,而 `ssh` 或者 `ping` 这类则会提示主机名。
|
||||
|
||||
`zsh` 拥有一个巨大而又完整的库,因此它能识别许多不同的命令。比如,如果你使用 `tar` 命令, 你可以按 Tab 键,他会为你展示一个可以用于解压的文件列表:
|
||||
zsh 拥有一个巨大而又完整的库,因此它能识别许多不同的命令。比如,如果你使用 `tar` 命令, 你可以按 `TAB` 键,它会为你展示一个可以用于解压的文件列表:
|
||||
|
||||
```
|
||||
$ tar -xzvf test1.tar.gz test1/file1 (TAB)
|
||||
@ -221,7 +219,7 @@ $ git add (TAB)
|
||||
$ git add zsh-5tips.md
|
||||
```
|
||||
|
||||
zsh 还能识别命令行选项,同时他只会提示与选中子命令相关的命令列表:
|
||||
zsh 还能识别命令行选项,同时它只会提示与选中子命令相关的命令列表:
|
||||
|
||||
```
|
||||
$ git commit - (TAB)
|
||||
@ -243,27 +241,27 @@ $ git commit - (TAB)
|
||||
... TRUNCATED ...
|
||||
```
|
||||
|
||||
在按 `TAB` 键之后,你可以使用方向键来选择你想用的命令。现在你就不用记住所有的 Git 命令项了。
|
||||
在按 `TAB` 键之后,你可以使用方向键来选择你想用的命令。现在你就不用记住所有的 `git` 命令项了。
|
||||
|
||||
zsh 还有很多有用的功能。当你用它的时候,你就知道哪些对你才是最有用的。
|
||||
|
||||
### 5\. 命令行编辑与历史记录
|
||||
### 5、命令行编辑与历史记录
|
||||
|
||||
Zsh 的命令行编辑功能也十分有效。默认条件下,他是模拟 emacs 编辑器的。如果你是跟我一样更喜欢用 vi/vim,你可以用以下命令启用 vi 编辑。
|
||||
zsh 的命令行编辑功能也十分有用。默认条件下,它是模拟 emacs 编辑器的。如果你是跟我一样更喜欢用 vi/vim,你可以用以下命令启用 vi 的键绑定。
|
||||
|
||||
```
|
||||
$ bindkey -v
|
||||
```
|
||||
|
||||
如果你使用 Oh My Zsh,`vi-mode` 插件可以启用额外的绑定,同时会在你的命令提示符上增加 vi 的模式提示--这个非常有用。
|
||||
如果你使用 Oh My Zsh,`vi-mode` 插件可以启用额外的绑定,同时会在你的命令提示符上增加 vi 的模式提示 —— 这个非常有用。
|
||||
|
||||
当启用 vi 的绑定后,你可以再命令行中使用 vi 命令进行编辑。比如,输入 `ESC+/` 来查找命令行记录。在查找的时候,输入 `n` 来找下一个匹配行,输入 `N` 来找上一个。输入 `ESC` 后,最常用的 vi 命令有以下几个,如输入 `0` 跳转到第一行,输入 `$` 跳转到最后一行,输入 `i` 来插入文本,输入 `a` 来追加文本等等,一些直接操作的命令也同样有效,比如输入 `cw` 来修改单词。
|
||||
当启用 vi 的绑定后,你可以在命令行中使用 vi 命令进行编辑。比如,输入 `ESC+/` 来查找命令行记录。在查找的时候,输入 `n` 来找下一个匹配行,输入 `N` 来找上一个。输入 `ESC` 后,常用的 vi 命令都可以使用,如输入 `0` 跳转到行首,输入 `$` 跳转到行尾,输入 `i` 来插入文本,输入 `a` 来追加文本等等,即使是跟随的命令也同样有效,比如输入 `cw` 来修改单词。
|
||||
|
||||
除了命令行编辑,如果你想修改或重新执行之前使用过的命令,zsh 还提供几个常用的命令行历史功能。比如,你打错了一个命令,输入 `fc`,你可以在你偏好的编辑器中修复最后一条命令。使用哪个编辑是参照 `$EDITOR` 变量的,而默认是使用 vi。
|
||||
|
||||
另外一个有用的命令是 `r`, 他会重新执行上一条命令;而 `r <WORD>` 则会执行上一条包含 `WORD` 的命令。
|
||||
另外一个有用的命令是 `r`, 它会重新执行上一条命令;而 `r <WORD>` 则会执行上一条包含 `WORD` 的命令。
|
||||
|
||||
最后,输入两个感叹号( `!!` ),可以在命令行中回溯最后一条命令。这个十分有用,比如,当你忘记使用 `sudo` 去执行需要权限的命令时:
|
||||
最后,输入两个感叹号(`!!`),可以在命令行中回溯最后一条命令。这个十分有用,比如,当你忘记使用 `sudo` 去执行需要权限的命令时:
|
||||
|
||||
```
|
||||
$ less /var/log/dnf.log
|
||||
@ -274,19 +272,16 @@ $ sudo less /var/log/dnf.log
|
||||
|
||||
这个功能让查找并且重新执行之前命令的操作更加方便。
|
||||
|
||||
### 何去何从?
|
||||
### 下一步呢?
|
||||
|
||||
这里仅仅介绍了几个可以让你提高生产率的 zsh 特性;其实还有更多功能带你发掘;想知道更多的信息,你可以访问以下的资源:
|
||||
这里仅仅介绍了几个可以让你提高生产率的 zsh 特性;其实还有更多功能有待你的发掘;想知道更多的信息,你可以访问以下的资源:
|
||||
|
||||
[An Introduction to the Z Shell][9]
|
||||
- [An Introduction to the Z Shell][9]
|
||||
- [A User's Guide to ZSH][10]
|
||||
- [Archlinux Wiki][11]
|
||||
- [zsh-lovers][12]
|
||||
|
||||
[A User's Guide to ZSH][10]
|
||||
|
||||
[Archlinux Wiki][11]
|
||||
|
||||
[zsh-lovers][12]
|
||||
|
||||
你有使用 zsh 提高生产力的tips可以分享吗?我(作者)很乐意在下方评论看到它们。
|
||||
你有使用 zsh 提高生产力的技巧可以分享吗?我很乐意在下方评论中看到它们。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -295,7 +290,7 @@ via: https://opensource.com/article/18/9/tips-productivity-zsh
|
||||
作者:[Ricardo Gerardi][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[tnuoccalanosrep](https://github.com/tnuoccalanosrep)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,44 +0,0 @@
|
||||
GraveAccent翻译中 Review: Algorithms to Live By
|
||||
======
|
||||

|
||||
|
||||
Another read for the work book club. This was my favorite to date, apart from the books I recommended myself.
|
||||
|
||||
One of the foundations of computer science as a field of study is research into algorithms: how do we solve problems efficiently using computer programs? This is a largely mathematical field, but it's often less about ideal or theoretical solutions and more about making the most efficient use of limited resources and arriving at an adequate, if not perfect, answer. Many of these problems are either day-to-day human problems or are closely related to them; after all, the purpose of computer science is to solve practical problems with computers. The question asked by Algorithms to Live By is "can we reverse this?": can we learn lessons from computer science's approach to problems that would help us make day-to-day decisions?
|
||||
|
||||
There's a lot of interesting material in the eleven chapters of this book, but there's also an amusing theme: humans are already very good at this. Many chapters start with an examination of algorithms and mathematical analysis of problems, dive into a discussion of how we can use those results to make better decisions, then talk about studies of the decisions humans actually make... and discover that humans are already applying ad hoc versions of the best algorithms we've come up with, given the constraints of typical life situations. It tends to undermine the stated goal of the book. Thankfully, it in no way undermines interesting discussion of general classes of problems, how computer science has tackled them, and what we've learned about the mathematical and technical shapes of those problems. There's a bit less self-help utility here than I think the authors had intended, but lots of food for thought.
|
||||
|
||||
(That said, it's worth considering whether this congruence is less because humans are already good at this and more because our algorithms are designed from human intuition. Maybe our best algorithms just reflect human thinking. In some cases we've checked our solutions against mathematical ideals, but in other cases they're still just our best guesses to date.)
|
||||
|
||||
This is the sort of a book where a chapter listing is an important part of the review. The areas of algorithms discussed here are optimal stopping, explore/exploit decisions (when to go with the best thing you've found and when to look for something better), sorting, caching, scheduling, Bayes's rule (and prediction in general), overfitting when building models, relaxation (solving an easier problem than your actual problem), randomized algorithms, a collection of networking algorithms, and finally game theory. Each of these has useful insights and thought-provoking discussion of how these sometimes-theoretical concepts map surprisingly well onto daily problems. The book concludes with a discussion of "computational kindness": an encouragement to reduce the required computation and complexity penalty for both yourself and the people you interact with.
|
||||
|
||||
If you have a computer science background (as I do), many of these will be familiar concepts, and you might be dubious that a popularization would tell you much that's new. Give this book a shot, though; the analogies are less stretched than you might fear, and the authors are both careful and smart about how they apply these principles. This book passes with flying colors a key sanity check: the chapters on topics that I know well or have thought about a lot make few or no obvious errors and say useful and important things. For example, the scheduling chapter, which unsurprisingly is about time management, surpasses more than half of the time management literature by jumping straight to the heart of most time management problems: if you're going to do everything on a list, it rarely matters the order in which you do it, so the hardest scheduling problems are about deciding what not to do rather than deciding order.
|
||||
|
||||
The point in the book where the authors won my heart completely was in the chapter on Bayes's rule. Much of the chapter is about Bayesian priors, and how one's knowledge of past events is a vital part of analysis of future probabilities. The authors then discuss the (in)famous marshmallow experiment, in which children are given one marshmallow and told that if they refrain from eating it until the researcher returns, they'll get two marshmallows. Refraining from eating the marshmallow (delayed gratification, in the psychological literature) was found to be associated with better life outcomes years down the road. This experiment has been used and abused for years for all sorts of propaganda about how trading immediate pleasure for future gains leads to a successful life, and how failure in life is because of inability to delay gratification. More evil analyses have (of course) tied that capability to ethnicity, with predictably racist results.
|
||||
|
||||
I have [kind of a thing][1] about the marshmallow experiment. It's a topic that reliably sends me off into angry rants.
|
||||
|
||||
Algorithms to Live By is the only book I have ever read to mention the marshmallow experiment and then apply the analysis that I find far more convincing. This is not a test of innate capability in the children; it's a test of their Bayesian priors. When does it make perfect sense to eat the marshmallow immediately instead of waiting for a reward? When their past experience tells them that adults are unreliable, can't be trusted, disappear for unpredictable lengths of time, and lie. And, even better, the authors supported this analysis with both a follow-up study I hadn't heard of before and with the observation that some children would wait for some time and then "give in." This makes perfect sense if they were subconsciously using a Bayesian model with poor priors.
|
||||
|
||||
This is a great book. It may try a bit too hard in places (applicability of the math of optimal stopping to everyday life is more contingent and strained than I think the authors want to admit), and some of this will be familiar if you've studied algorithms. But the writing is clear, succinct, and very well-edited. No part of the book outlives its welcome; the discussion moves right along. If you find yourself going "I know all this already," you'll still probably encounter a new concept or neat explanation in a few more pages. And sometimes the authors make connections that never would have occurred to me but feel right in retrospect, such as relating exponential backoff in networking protocols to choosing punishments in the criminal justice system. Or the realization that our modern communication world is not constantly connected, it's constantly buffered, and many of us are suffering from the characteristic signs of buffer bloat.
|
||||
|
||||
I don't think you have to be a CS major, or know much about math, to read this book. There is a lot of mathematical details in the end notes if you want to dive in, but the main text is almost always readable and clear, at least so far as I could tell (as someone who was a CS major and has taken a lot of math, so a grain of salt may be indicated). And it still has a lot to offer even if you've studied algorithms for years.
|
||||
|
||||
The more I read of this book, the more I liked it. Definitely recommended if you like reading this sort of analysis of life.
|
||||
|
||||
Rating: 9 out of 10
|
||||
|
||||
Reviewed: 2017-10-22
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.eyrie.org/~eagle/reviews/books/1-62779-037-3.html
|
||||
|
||||
作者:[Brian Christian;Tom Griffiths][a]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.eyrie.org
|
||||
[1]:https://www.eyrie.org1-59184-679-X.html
|
@ -1,206 +0,0 @@
|
||||
Conditional Rendering in React using Ternaries and Logical AND
|
||||
============================================================
|
||||
|
||||
|
||||

|
||||
Photo by [Brendan Church][1] on [Unsplash][2]
|
||||
|
||||
There are several ways that your React component can decide what to render. You can use the traditional `if` statement or the `switch` statement. In this article, we’ll explore a few alternatives. But be warned that some come with their own gotchas, if you’re not careful.
|
||||
|
||||
### Ternary vs if/else
|
||||
|
||||
Let’s say we have a component that is passed a `name` prop. If the string is non-empty, we display a greeting. Otherwise we tell the user they need to sign in.
|
||||
|
||||
Here’s a Stateless Function Component (SFC) that does just that.
|
||||
|
||||
```
|
||||
const MyComponent = ({ name }) => {
|
||||
if (name) {
|
||||
return (
|
||||
<div className="hello">
|
||||
Hello {name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="hello">
|
||||
Please sign in
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Pretty straightforward. But we can do better. Here’s the same component written using a conditional ternary operator.
|
||||
|
||||
```
|
||||
const MyComponent = ({ name }) => (
|
||||
<div className="hello">
|
||||
{name ? `Hello ${name}` : 'Please sign in'}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
Notice how concise this code is compared to the example above.
|
||||
|
||||
A few things to note. Because we are using the single statement form of the arrow function, the `return` statement is implied. Also, using a ternary allowed us to DRY up the duplicate `<div className="hello">` markup. 🎉
|
||||
|
||||
### Ternary vs Logical AND
|
||||
|
||||
As you can see, ternaries are wonderful for `if/else` conditions. But what about simple `if` conditions?
|
||||
|
||||
Let’s look at another example. If `isPro` (a boolean) is `true`, we are to display a trophy emoji. We are also to render the number of stars (if not zero). We could go about it like this.
|
||||
|
||||
```
|
||||
const MyComponent = ({ name, isPro, stars}) => (
|
||||
<div className="hello">
|
||||
<div>
|
||||
Hello {name}
|
||||
{isPro ? '🏆' : null}
|
||||
</div>
|
||||
{stars ? (
|
||||
<div>
|
||||
Stars:{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
But notice the “else” conditions return `null`. This is becasue a ternary expects an else condition.
|
||||
|
||||
For simple `if` conditions, we could use something a little more fitting: the logical AND operator. Here’s the same code written using a logical AND.
|
||||
|
||||
```
|
||||
const MyComponent = ({ name, isPro, stars}) => (
|
||||
<div className="hello">
|
||||
<div>
|
||||
Hello {name}
|
||||
{isPro && '🏆'}
|
||||
</div>
|
||||
{stars && (
|
||||
<div>
|
||||
Stars:{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
Not too different, but notice how we eliminated the `: null` (i.e. else condition) at the end of each ternary. Everything should render just like it did before.
|
||||
|
||||
|
||||
Hey! What gives with John? There is a `0` when nothing should be rendered. That’s the gotcha that I was referring to above. Here’s why.
|
||||
|
||||
[According to MDN][3], a Logical AND (i.e. `&&`):
|
||||
|
||||
> `expr1 && expr2`
|
||||
|
||||
> Returns `expr1` if it can be converted to `false`; otherwise, returns `expr2`. Thus, when used with Boolean values, `&&` returns `true` if both operands are true; otherwise, returns `false`.
|
||||
|
||||
OK, before you start pulling your hair out, let me break it down for you.
|
||||
|
||||
In our case, `expr1` is the variable `stars`, which has a value of `0`. Because zero is falsey, `0` is returned and rendered. See, that wasn’t too bad.
|
||||
|
||||
I would write this simply.
|
||||
|
||||
> If `expr1` is falsey, returns `expr1`, else returns `expr2`.
|
||||
|
||||
So, when using a logical AND with non-boolean values, we must make the falsey value return something that React won’t render. Say, like a value of `false`.
|
||||
|
||||
There are a few ways that we can accomplish this. Let’s try this instead.
|
||||
|
||||
```
|
||||
{!!stars && (
|
||||
<div>
|
||||
{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
Notice the double bang operator (i.e. `!!`) in front of `stars`. (Well, actually there is no “double bang operator”. We’re just using the bang operator twice.)
|
||||
|
||||
The first bang operator will coerce the value of `stars` into a boolean and then perform a NOT operation. If `stars` is `0`, then `!stars` will produce `true`.
|
||||
|
||||
Then we perform a second NOT operation, so if `stars` is 0, `!!stars` would produce `false`. Exactly what we want.
|
||||
|
||||
If you’re not a fan of `!!`, you can also force a boolean like this (which I find a little wordy).
|
||||
|
||||
```
|
||||
{Boolean(stars) && (
|
||||
```
|
||||
|
||||
Or simply give a comparator that results in a boolean value (which some might say is even more semantic).
|
||||
|
||||
```
|
||||
{stars > 0 && (
|
||||
```
|
||||
|
||||
#### A word on strings
|
||||
|
||||
Empty string values suffer the same issue as numbers. But because a rendered empty string is invisible, it’s not a problem that you will likely have to deal with, or will even notice. However, if you are a perfectionist and don’t want an empty string on your DOM, you should take similar precautions as we did for numbers above.
|
||||
|
||||
### Another solution
|
||||
|
||||
A possible solution, and one that scales to other variables in the future, would be to create a separate `shouldRenderStars` variable. Then you are dealing with boolean values in your logical AND.
|
||||
|
||||
```
|
||||
const shouldRenderStars = stars > 0;
|
||||
```
|
||||
|
||||
```
|
||||
return (
|
||||
<div>
|
||||
{shouldRenderStars && (
|
||||
<div>
|
||||
{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
Then, if in the future, the business rule is that you also need to be logged in, own a dog, and drink light beer, you could change how `shouldRenderStars` is computed, and what is returned would remain unchanged. You could also place this logic elsewhere where it’s testable and keep the rendering explicit.
|
||||
|
||||
```
|
||||
const shouldRenderStars =
|
||||
stars > 0 && loggedIn && pet === 'dog' && beerPref === 'light`;
|
||||
```
|
||||
|
||||
```
|
||||
return (
|
||||
<div>
|
||||
{shouldRenderStars && (
|
||||
<div>
|
||||
{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### Conclusion
|
||||
|
||||
I’m of the opinion that you should make best use of the language. And for JavaScript, this means using conditional ternary operators for `if/else`conditions and logical AND operators for simple `if` conditions.
|
||||
|
||||
While we could just retreat back to our safe comfy place where we use the ternary operator everywhere, you now possess the knowledge and power to go forth AND prosper.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Managing Editor at the American Express Engineering Blog http://aexp.io and Director of Engineering @AmericanExpress. MyViews !== ThoseOfMyEmployer.
|
||||
|
||||
----------------
|
||||
|
||||
via: https://medium.freecodecamp.org/conditional-rendering-in-react-using-ternaries-and-logical-and-7807f53b6935
|
||||
|
||||
作者:[Donavon West][a]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://medium.freecodecamp.org/@donavon
|
||||
[1]:https://unsplash.com/photos/pKeF6Tt3c08?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
|
||||
[2]:https://unsplash.com/search/photos/road-sign?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
|
||||
[3]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators
|
@ -1,3 +1,4 @@
|
||||
Translating by way-ww
|
||||
Manipulating Directories in Linux
|
||||
======
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
How to create shortcuts in vi
|
||||
【sd886393认领翻译中】How to create shortcuts in vi
|
||||
======
|
||||
|
||||

|
||||
|
@ -1,223 +0,0 @@
|
||||
[翻译中]translating by jrg!
|
||||
|
||||
Automating backups on a Raspberry Pi NAS
|
||||
======
|
||||

|
||||
|
||||
In the [first part][1] of this three-part series using a Raspberry Pi for network-attached storage (NAS), we covered the fundamentals of the NAS setup, attached two 1TB hard drives (one for data and one for backups), and mounted the data drive on a remote device via the network filesystem (NFS). In part two, we will look at automating backups. Automated backups allow you to continually secure your data and recover from a hardware defect or accidental file removal.
|
||||
|
||||

|
||||
|
||||
### Backup strategy
|
||||
|
||||
Let's get started by coming up with with a backup strategy for our small NAS. I recommend creating daily backups of your data and scheduling them for a time they won't interfere with other NAS activities, including when you need to access or store your files. For example, you could trigger the backup activities each day at 2am.
|
||||
|
||||
You also need to decide how long you'll keep each backup, since you would quickly run out of storage if you kept each daily backup indefinitely. Keeping your daily backups for one week allows you to travel back into your recent history if you realize something went wrong over the previous seven days. But what if you need something from further in the past? Keeping each Monday backup for a month and one monthly backup for a longer period of time should be sufficient. Let's keep the monthly backups for a year and one backup every year for long-distance time travels, e.g., for the last five years.
|
||||
|
||||
This results in a bunch of backups on your backup drive over a five-year period:
|
||||
|
||||
* 7 daily backups
|
||||
* 4 (approx.) weekly backups
|
||||
* 12 monthly backups
|
||||
* 5 annual backups
|
||||
|
||||
|
||||
|
||||
You may recall that your backup drive and your data drive are of equal size (1TB each). How will more than 10 backups of 1TB from your data drive fit onto a 1TB backup disk? If you create full backups, they won't. Instead, you will create incremental backups, reusing the data from the last backup if it didn't change and creating replicas of new or changed files. That way, the backup doesn't double every night, but only grows a little bit depending on the changes that happen to your data over a day.
|
||||
|
||||
Here is my situation: My NAS has been running since August 2016, and 20 backups are on the backup drive. Currently, I store 406GB of files on the data drive. The backups take up 726GB on my backup drive. Of course, this depends heavily on your data's change frequency, but as you can see, the incremental backups don't consume as much space as 20 full backups would. Nevertheless, over time the 1TB disk will probably become insufficient for your backups. Once your data grows close to the 1TB limit (or whatever your backup drive capacity), you should choose a bigger backup drive and move your data there.
|
||||
|
||||
### Creating backups with rsync
|
||||
|
||||
To create a full backup, you can use the rsync command line tool. Here is an example command to create the initial full backup.
|
||||
```
|
||||
pi@raspberrypi:~ $ rsync -a /nas/data/ /nas/backup/2018-08-01
|
||||
|
||||
```
|
||||
|
||||
This command creates a full replica of all data stored on the data drive, mounted on `/nas/data`, on the backup drive. There, it will create the folder `2018-08-01` and create the backup inside it. The `-a` flag starts rsync in archive-mode, which means it preserves all kinds of metadata, like modification dates, permissions, and owners, and copies soft links as soft links.
|
||||
|
||||
Now that you have created your full, initial backup as of August 1, on August 2, you will create your first daily incremental backup.
|
||||
```
|
||||
pi@raspberrypi:~ $ rsync -a --link-dest /nas/backup/2018-08-01/ /nas/data/ /nas/backup/2018-08-02
|
||||
|
||||
```
|
||||
|
||||
This command tells rsync to again create a backup of `/nas/data`. The target directory this time is `/nas/backup/2018-08-02`. The script also specified the `--link-dest` option and passed the location of the last backup as an argument. With this option specified, rsync looks at the folder `/nas/backup/2018-08-01` and checks what data files changed compared to that folder's content. Unchanged files will not be copied, rather they will be hard-linked to their counterparts in yesterday's backup folder.
|
||||
|
||||
When using a hard-linked file from a backup, you won't notice any difference between the initial copy and the link. They behave exactly the same, and if you delete either the link or the initial file, the other will still exist. You can imagine them as two equal entry points to the same file. Here is an example:
|
||||
|
||||

|
||||
|
||||
The left box reflects the state shortly after the second backup. The box in the middle is yesterday's replica. The `file2.txt` didn't exist yesterday, but the image `file1.jpg` did and was copied to the backup drive. The box on the right reflects today's incremental backup. The incremental backup command created `file2.txt`, which didn't exist yesterday. Since `file1.jpg` didn't change since yesterday, today a hard link is created so it doesn't take much additional space on the disk.
|
||||
|
||||
### Automate your backups
|
||||
|
||||
You probably don't want to execute your daily backup command by hand at 2am each day. Instead, you can automate your backup by using a script like the following, which you may want to start with a cron job.
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
DATADIR=/nas/data/
|
||||
|
||||
BACKUPDIR=/nas/backup/
|
||||
|
||||
SCRIPTDIR=/nas/data/backup_scripts
|
||||
|
||||
LASTDAYPATH=${BACKUPDIR}/$(ls ${BACKUPDIR} | tail -n 1)
|
||||
|
||||
TODAYPATH=${BACKUPDIR}/${TODAY}
|
||||
|
||||
if [[ ! -e ${TODAYPATH} ]]; then
|
||||
|
||||
mkdir -p ${TODAYPATH}
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
rsync -a --link-dest ${LASTDAYPATH} ${DATADIR} ${TODAYPATH} $@
|
||||
|
||||
|
||||
|
||||
${SCRIPTDIR}/deleteOldBackups.sh
|
||||
|
||||
```
|
||||
|
||||
The first block calculates the last backup's folder name to use for links and the name of today's backup folder. The second block has the rsync command (as described above). The last block executes a `deleteOldBackups.sh` script. It will clean up the old, unnecessary backups based on the backup strategy outlined above. You could also execute the cleanup script independently from the backup script if you want it to run less frequently.
|
||||
|
||||
The following script is an example implementation of the backup strategy in this how-to article.
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
BACKUPDIR=/nas/backup/
|
||||
|
||||
|
||||
|
||||
function listYearlyBackups() {
|
||||
|
||||
for i in 0 1 2 3 4 5
|
||||
|
||||
do ls ${BACKUPDIR} | egrep "$(date +%Y -d "${i} year ago")-[0-9]{2}-[0-9]{2}" | sort -u | head -n 1
|
||||
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listMonthlyBackups() {
|
||||
|
||||
for i in 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
|
||||
do ls ${BACKUPDIR} | egrep "$(date +%Y-%m -d "${i} month ago")-[0-9]{2}" | sort -u | head -n 1
|
||||
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listWeeklyBackups() {
|
||||
|
||||
for i in 0 1 2 3 4
|
||||
|
||||
do ls ${BACKUPDIR} | grep "$(date +%Y-%m-%d -d "last monday -${i} weeks")"
|
||||
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listDailyBackups() {
|
||||
|
||||
for i in 0 1 2 3 4 5 6
|
||||
|
||||
do ls ${BACKUPDIR} | grep "$(date +%Y-%m-%d -d "-${i} day")"
|
||||
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getAllBackups() {
|
||||
|
||||
listYearlyBackups
|
||||
|
||||
listMonthlyBackups
|
||||
|
||||
listWeeklyBackups
|
||||
|
||||
listDailyBackups
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listUniqueBackups() {
|
||||
|
||||
getAllBackups | sort -u
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listBackupsToDelete() {
|
||||
|
||||
ls ${BACKUPDIR} | grep -v -e "$(echo -n $(listUniqueBackups) |sed "s/ /\\\|/g")"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
cd ${BACKUPDIR}
|
||||
|
||||
listBackupsToDelete | while read file_to_delete; do
|
||||
|
||||
rm -rf ${file_to_delete}
|
||||
|
||||
done
|
||||
|
||||
```
|
||||
|
||||
This script will first list all the backups to keep (according to our backup strategy), then it will delete all the backup folders that are not necessary anymore.
|
||||
|
||||
To execute the scripts every night to create daily backups, schedule the backup script by running `crontab -e` as the root user. (You need to be in root to make sure it has permission to read all the files on the data drive, no matter who created them.) Add a line like the following, which starts the script every night at 2am.
|
||||
```
|
||||
0 2 * * * /nas/data/backup_scripts/daily.sh
|
||||
|
||||
```
|
||||
|
||||
For more information, read about [scheduling tasks with cron][2].
|
||||
|
||||
* Unmount your backup drive or mount it as read-only when no backups are running
|
||||
* Attach the backup drive to a remote server and sync the files over the internet
|
||||
|
||||
|
||||
|
||||
There are additional things you can do to fortify your backups against accidental removal or damage, including the following:
|
||||
|
||||
This example backup strategy enables you to back up your valuable data to make sure it won't get lost. You can also easily adjust this technique for your personal needs and preferences.
|
||||
|
||||
In part three of this series, we will talk about [Nextcloud][3], a convenient way to store and access data on your NAS system that also provides offline access as it synchronizes your data to the client devices.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/8/automate-backups-raspberry-pi
|
||||
|
||||
作者:[Manuel Dewald][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/ntlx
|
||||
[1]:https://opensource.com/article/18/7/network-attached-storage-Raspberry-Pi
|
||||
[2]:https://opensource.com/article/17/11/how-use-cron-linux
|
||||
[3]:https://nextcloud.com/
|
@ -1,3 +1,5 @@
|
||||
translating---geekpi
|
||||
|
||||
[Solved] “sub process usr bin dpkg returned an error code 1″ Error in Ubuntu
|
||||
======
|
||||
If you are encountering “sub process usr bin dpkg returned an error code 1” while installing software on Ubuntu Linux, here is how you can fix it.
|
||||
|
@ -1,3 +1,5 @@
|
||||
Translating by jlztan
|
||||
|
||||
Top 10 Raspberry Pi blogs to follow
|
||||
======
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
HankChow translating
|
||||
|
||||
Linux tricks that can save you time and trouble
|
||||
======
|
||||
Some command line tricks can make you even more productive on the Linux command line.
|
||||
|
@ -1,113 +0,0 @@
|
||||
[翻译中]translating by jrg!
|
||||
|
||||
Host your own cloud with Raspberry Pi NAS
|
||||
======
|
||||
|
||||
Protect and secure your data with a self-hosted cloud powered by your Raspberry Pi.
|
||||
|
||||
In the first two parts of this series, we discussed the [hardware and software fundamentals][1] for building network-attached storage (NAS) on a Raspberry Pi. We also put a proper [backup strategy][2] in place to secure the data on the NAS. In this third part, we will talk about a convenient way to store, access, and share your data with [Nextcloud][3].
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To use Nextcloud conveniently, you have to meet a few prerequisites. First, you should have a domain you can use for the Nextcloud instance. For the sake of simplicity in this how-to, we'll use **nextcloud.pi-nas.com**. This domain should be directed to your Raspberry Pi. If you want to run it on your home network, you probably need to set up dynamic DNS for this domain and enable port forwarding of ports 80 and 443 (if you go for an SSL setup, which is highly recommended; otherwise port 80 should be sufficient) from your router to the Raspberry Pi.
|
||||
|
||||
You can automate dynamic DNS updates from the Raspberry Pi using [ddclient][4].
|
||||
|
||||
### Install Nextcloud
|
||||
|
||||
To run Nextcloud on your Raspberry Pi (using the setup described in the [first part][1] of this series), install the following packages as dependencies to Nextcloud using **apt**.
|
||||
|
||||
```
|
||||
sudo apt install unzip wget php apache2 mysql-server php-zip php-mysql php-dom php-mbstring php-gd php-curl
|
||||
```
|
||||
|
||||
The next step is to download Nextcloud. [Get the latest release's URL][5] and copy it to download via **wget** on the Raspberry Pi. In the first article in this series, we attached two disk drives to the Raspberry Pi, one for current data and one for backups. Install Nextcloud on the data drive to make sure data is backed up automatically every night.
|
||||
|
||||
```
|
||||
sudo mkdir -p /nas/data/nextcloud
|
||||
sudo chown pi /nas/data/nextcloud
|
||||
cd /nas/data/
|
||||
wget https://download.nextcloud.com/server/releases/nextcloud-14.0.0.zip -O /nas/data/nextcloud.zip
|
||||
unzip nextcloud.zip
|
||||
sudo ln -s /nas/data/nextcloud /var/www/nextcloud
|
||||
sudo chown -R www-data:www-data /nas/data/nextcloud
|
||||
```
|
||||
|
||||
When I wrote this, the latest release (as you see in the code above) was 14. Nextcloud is under heavy development, so you may find a newer version when installing your copy of Nextcloud onto your Raspberry Pi.
|
||||
|
||||
### Database setup
|
||||
|
||||
When we installed Nextcloud above, we also installed MySQL as a dependency to use it for all the metadata Nextcloud generates (for example, the users you create to access Nextcloud). If you would rather use a Postgres database, you'll need to adjust some of the modules installed above.
|
||||
|
||||
To access the MySQL database as root, start the MySQL client as root:
|
||||
|
||||
```
|
||||
sudo mysql
|
||||
```
|
||||
|
||||
This will open a SQL prompt where you can insert the following commands—substituting the placeholder with the password you want to use for the database connection—to create a database for Nextcloud.
|
||||
|
||||
```
|
||||
CREATE USER nextcloud IDENTIFIED BY '<insert-password-here>';
|
||||
CREATE DATABASE nextcloud;
|
||||
GRANT ALL ON nextcloud.* TO nextcloud;
|
||||
```
|
||||
|
||||
You can exit the SQL prompt by pressing **Ctrl+D** or entering **quit**.
|
||||
|
||||
### Web server configuration
|
||||
|
||||
Nextcloud can be configured to run using Nginx or other web servers, but for this how-to, I decided to go with the Apache web server on my Raspberry Pi NAS. (Feel free to try out another alternative and let me know if you think it performs better.)
|
||||
|
||||
To set it up, configure a virtual host for the domain you created for your Nextcloud instance **nextcloud.pi-nas.com**. To create a virtual host, create the file **/etc/apache2/sites-available/001-nextcloud.conf** with content similar to the following. Make sure to adjust the ServerName to your domain and paths, if you didn't use the ones suggested earlier in this series.
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName nextcloud.pi-nas.com
|
||||
ServerAdmin admin@pi-nas.com
|
||||
DocumentRoot /var/www/nextcloud/
|
||||
|
||||
<Directory /var/www/nextcloud/>
|
||||
AllowOverride None
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
To enable this virtual host, run the following two commands.
|
||||
|
||||
```
|
||||
a2ensite 001-nextcloud
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
With this configuration, you should now be able to reach the web server with your domain via the web browser. To secure your data, I recommend using HTTPS instead of HTTP to access Nextcloud. A very easy (and free) way is to obtain a [Let's Encrypt][6] certificate with [Certbot][7] and have a cron job automatically refresh it. That way you don't have to mess around with self-signed or expiring certificates. Follow Certbot's simple how-to [instructions to install it on your Raspberry Pi][8]. During Certbot configuration, you can even decide to automatically forward HTTP to HTTPS, so visitors to **<http://nextcloud.pi-nas.com>** will be redirected to **<https://nextcloud.pi-nas.com>**. Please note, if your Raspberry Pi is running behind your home router, you must have port forwarding enabled for ports 443 and 80 to obtain Let's Encrypt certificates.
|
||||
|
||||
### Configure Nextcloud
|
||||
|
||||
The final step is to visit your fresh Nextcloud instance in a web browser to finish the configuration. To do so, open your domain in a browser and insert the database details from above. You can also set up your first Nextcloud user here, the one you can use for admin tasks. By default, the data directory should be inside the Nextcloud folder, so you don't need to change anything for the backup mechanisms from the [second part of this series][2] to pick up the data stored by users in Nextcloud.
|
||||
|
||||
Afterward, you will be directed to your Nextcloud and can log in with the admin user you created previously. To see a list of recommended steps to ensure a performant and secure Nextcloud installation, visit the Basic Settings tab in the Settings page (in our example: <https://nextcloud.pi-nas.com/>settings/admin) and see the Security & Setup Warnings section.
|
||||
|
||||
Congratulations! You've set up your own Nextcloud powered by a Raspberry Pi. Go ahead and [download a Nextcloud client][9] from the Nextcloud page to sync data with your client devices and access it offline. Mobile clients even provide features like instant upload of pictures you take, so they'll automatically sync to your desktop PC without wondering how to get them there.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/9/host-cloud-nas-raspberry-pi
|
||||
|
||||
作者:[Manuel Dewald][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/ntlx
|
||||
[1]: https://opensource.com/article/18/7/network-attached-storage-Raspberry-Pi
|
||||
[2]: https://opensource.com/article/18/8/automate-backups-raspberry-pi
|
||||
[3]: https://nextcloud.com/
|
||||
[4]: https://sourceforge.net/p/ddclient/wiki/Home/
|
||||
[5]: https://nextcloud.com/install/#instructions-server
|
||||
[6]: https://letsencrypt.org/
|
||||
[7]: https://certbot.eff.org/
|
||||
[8]: https://certbot.eff.org/lets-encrypt/debianother-apache
|
||||
[9]: https://nextcloud.com/install/#install-clients
|
@ -1,62 +0,0 @@
|
||||
HankChow translating
|
||||
|
||||
Understand Fedora memory usage with top
|
||||
======
|
||||
|
||||

|
||||
|
||||
Have you used the top utility in a terminal to see memory usage on your Fedora system? If so, you might be surprised to see some of the numbers there. It might look like a lot more memory is consumed than your system has available. This article will explain a little more about memory usage, and how to read these numbers.
|
||||
|
||||
### Memory usage in real terms
|
||||
|
||||
The way the operating system (OS) uses memory may not be self-evident. In fact, some ingenious, behind-the-scenes techniques are at play. They help your OS use memory more efficiently, without involving you.
|
||||
|
||||
Most applications are not self contained. Instead, each relies on sets of functions collected in libraries. These libraries are also installed on the system. In Fedora, the RPM packaging system ensures that when you install an app, any libraries on which it relies are installed, too.
|
||||
|
||||
When an app runs, the OS doesn’t necessarily load all the information it uses into real memory. Instead, it builds a map to the storage where that code is stored, called virtual memory. The OS then loads only the parts it needs. When it no longer needs portions of memory, it might release or swap them out as appropriate.
|
||||
|
||||
This means an app can map a very large amount of virtual memory, while using less real memory on the system at one time. It might even map more RAM than the system has available! In fact, across a whole OS that’s often the case.
|
||||
|
||||
In addition, related applications may rely on the same libraries. The Linux kernel in your Fedora system often shares memory between applications. It doesn’t need to load multiple copies of the same library for related apps. This works similarly for separate instances of the same app, too.
|
||||
|
||||
Without understanding these details, the output of the top application can be confusing. The following example will clarify this view into memory usage.
|
||||
|
||||
### Viewing memory usage in top
|
||||
|
||||
If you haven’t tried yet, open a terminal and run the top command to see some output. Hit **Shift+M** to see the list sorted by memory usage. Your display may look slightly different than this example from a running Fedora Workstation:
|
||||
|
||||
<https://fedoramagazine.org/wp-content/uploads/2018/09/Screenshot-from-2018-09-17-14-23-17.png>
|
||||
|
||||
There are three columns showing memory usage to examine: VIRT, RES, and SHR. The measurements are currently shown in kilobytes (KB).
|
||||
|
||||
The VIRT column is the virtual memory mapped for this process. Recall from the earlier description that virtual memory is not actual RAM consumed. For example, the GNOME Shell process gnome-shell is not actually consuming over 3.1 gigabytes of actual RAM. However, it’s built on a number of lower and higher level libraries. The system must map each of those to ensure they can be loaded when necessary.
|
||||
|
||||
The RES column shows you how much actual (resident) memory is consumed by the app. In the case of GNOME Shell, that’s about 180788 KB. The example system has roughly 7704 MB of physical memory, which is why the memory usage shows up as 2.3%.
|
||||
|
||||
However, of that number, at least 88212 KB is shared memory, shown in the SHR column. This memory might be, for example, library functions that other apps also use. This means the GNOME Shell is using about 92 MB on its own not shared with other processes. Notice that other apps in the example share an even higher percentage of their resident memory. In some apps, the shared portion is the vast majority of the memory usage.
|
||||
|
||||
There is a wrinkle here, which is that sometimes processes communicate with each other via memory. That memory is also shared, but can’t necessarily be detected by a utility like top. So yes — even the above clarifications still have some uncertainty!
|
||||
|
||||
### A note about swap
|
||||
|
||||
Your system has another facility it uses to store information, which is swap. Typically this is an area of slower storage (like a hard disk). If the physical memory on the system fills up as needs increase, the OS looks for portions of memory that haven’t been needed in a while. It writes them out to the swap area, where they sit until needed later.
|
||||
|
||||
Therefore, prolonged, high swap usage usually means a system is suffering from too little memory for its demands. Sometimes an errant application may be at fault. Or, if you see this often on your system, consider upgrading your machine’s memory, or restricting what you run.
|
||||
|
||||
Photo courtesy of [Stig Nygaard][1], via [Flickr][2] (CC BY 2.0).
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://fedoramagazine.org/understand-fedora-memory-usage-top/
|
||||
|
||||
作者:[Paul W. Frields][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://fedoramagazine.org/author/pfrields/
|
||||
[1]: https://www.flickr.com/photos/stignygaard/
|
||||
[2]: https://www.flickr.com/photos/stignygaard/3138001676/
|
@ -0,0 +1,44 @@
|
||||
书评|算法之美
|
||||
======
|
||||

|
||||
|
||||
又一次为了工作图书俱乐部而读书。除了其它我亲自推荐的书,这是我至今最喜爱的书。
|
||||
|
||||
作为计算机科学基础之一的研究领域是算法:我们如何高效地用计算机程序解决问题?这基本上属于数学领域,但是这很少关于理想的或理论上的解决方案,而是更在于最高效地利用有限的资源获得一个充足(如果不能完美)的答案。其中许多问题要么是日常的生活问题,要么与人们密切相关。毕竟,计算机科学的目的是为了用计算机解决实际问题。《算法之美》提出的问题是:“我们可以反过来吗”--我们可以通过学习计算机科学解决问题的方式来帮助我们做出日常决定吗?
|
||||
|
||||
本书的十一个章节有很多有趣的内容,但也有一个有趣的主题:人类早已擅长这一点。很多章节以一个算法研究和对问题的数学分析作为开始,接着深入到探讨如何利用这些结果做出更好的决策,然后讨论关于人类真正会做出的决定的研究,之后,考虑到典型生活情境的限制,会发现人类早就在应用我们提出的最佳算法的特殊版本了。这往往会破坏本书的既定目标,值得庆幸的是,它决不会破坏对一般问题的有趣讨论,即计算机科学如何解决它们,以及我们对这些问题的数学和技术形态的了解。我认为这本书的自助效用比作者打算的少一些,但有很多可供思考的东西。
|
||||
|
||||
(也就是说,值得考虑这种一致性是否太少了,因为人类已经擅长这方面了,更因为我们的算法是根据人类直觉设计的。可能我们的最佳算法只是反映了人类的思想。在某些情况下,我们发现我们的方案和数学上的典范不一样, 但是在另一些情况下,它们仍然是我们当下最好的猜想。)
|
||||
|
||||
这是那种章节列表是书评里重要部分的书。这里讨论的算法领域有最优停止、探索和利用决策(什么时候带着你发现的最好东西走以及什么时候寻觅更好的东西),以及排序、缓存、调度、贝叶斯定理(一般还有预测)、创建模型时的过拟合、放松(解决容易的问题而不是你的实际问题)、随机算法、一系列网络算法,最后还有游戏理论。其中每一项都有有用的见解和发人深省的讨论--这些有时显得十分理论化的概念令人吃惊地很好地映射到了日常生活。这本书以一段关于“可计算的善意”的讨论结束:鼓励减少你自己和你交往的人所需的计算和复杂性惩罚。
|
||||
|
||||
如果你有计算机科学背景(就像我一样),其中许多都是熟悉的概念,而且你因为被普及了很多新东西或许会有疑惑。然而,请给这本书一个机会,类比法没你担忧的那么令人紧张。作者既小心又聪明地应用了这些原则。这本书令人惊喜地通过了一个重要的合理性检查:涉及到我知道或反复思考过的主题的章节很少有或没有明显的错误,而且能讲出有用和重要的事情。比如,调度的那一章节毫不令人吃惊地和时间管理有关,通过直接跳到时间管理问题的核心而胜过了半数时间管理类书籍:如果你要做一个清单上的所有事情,你做这些事情的顺序很少要紧,所以最难的调度问题是决定不做哪些事情而不是做这些事情的顺序。
|
||||
|
||||
作者在贝叶斯定理这一章节中的观点完全赢得了我的心。本章的许多内容都是关于贝叶斯先验的,以及一个人对过去事件的了解为什么对分析未来的概率很重要。作者接着讨论了著名的棉花糖实验。即给了儿童一个棉花糖以后,儿童被研究者告知如果他们能够克制自己不吃这个棉花糖,等到研究者回来时,会给他们两个棉花糖。克制自己不吃棉花糖(在心理学文献中叫作“延迟满足”)被发现与未来几年更好的生活有关。这个实验多年来一直被引用和滥用于各种各样的宣传,关于选择未来的收益放弃即时的快乐从而拥有成功的生活,以及生活中的失败是因为无法延迟满足。更多的邪恶分析(当然)将这种能力与种族联系在一起,带有可想而知的种族主义结论。
|
||||
|
||||
我对棉花糖实验有点兴趣。这是一个百分百让我愤怒咆哮的话题。
|
||||
|
||||
《算法之美》是我读过的唯一提到了棉花糖实验并应用了我认为更有说服力的分析的书。这不是一个关于儿童天赋的实验,这是一个关于他们的贝叶斯先验的实验。什么时候立即吃棉花糖而不是等待奖励是完全合理的?当他们过去的经历告诉他们成年人不可靠,不可信任,会在不可预测的时间内消失并且撒谎的时候。而且,更好的是,作者用我之前没有听说过的后续研究和观察支持了这一分析,观察到的内容是,一些孩子会等待一段时间然后“放弃”。如果他们下意识地使用具有较差先验的贝叶斯模型,这就完全合情合理。
|
||||
|
||||
这是一本很好的书。它可能在某些地方的尝试有点太勉强(数学上最优停止对于日常生活的适用性比我认为作者想要表现的更加偶然和牵强附会),如果你学过算法,其中一些内容会感到熟悉,但是它的行文思路清晰,简洁,而且编辑得非常好。这本书没有哪一部分对不起它所受的欢迎,书中的讨论贯穿始终。如果你发现自己“已经知道了这一切”,你可能还会在接下来几页中遇到一个新的概念或一个简洁的解释。有时作者会做一些我从没想到但是回想起来正确的联系,比如将网络协议中的指数退避和司法系统中的选择惩罚联系起来。还有意识到我们的现代通信世界并不是一直联系的,它是不断缓冲的,我们中的许多人正深受缓冲膨胀这一独特现象的苦恼。
|
||||
|
||||
我认为你并不必须是计算机科学专业或者精通数学才能读这本书。如果你想深入,每章的结尾都有许多数学上的细节,但是正文总是易读而清晰,至少就我所知是这样(作为一个以计算机科学为专业并学到了很多数学知识的人,你至少可以有保留地相信我)。即使你已经钻研了多年的算法,这本书仍然可以提供很多东西。
|
||||
|
||||
这本书我读得越多越喜欢。如果你喜欢阅读这种对生活的分析,我当然是赞成的。
|
||||
|
||||
Rating: 9 out of 10
|
||||
|
||||
Reviewed: 2017-10-22
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.eyrie.org/~eagle/reviews/books/1-62779-037-3.html
|
||||
|
||||
作者:[Brian Christian;Tom Griffiths][a]
|
||||
译者:[GraveAccent](https://github.com/GraveAccent)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.eyrie.org
|
||||
[1]:https://www.eyrie.org1-59184-679-X.html
|
@ -0,0 +1,205 @@
|
||||
在 React 条件渲染中使用三元表达式和 “&&”
|
||||
============================================================
|
||||
|
||||

|
||||
Photo by [Brendan Church][1] on [Unsplash][2]
|
||||
|
||||
React 组件可以通过多种方式决定渲染内容。你可以使用传统的 if 语句或 switch 语句。在本文中,我们将探讨一些替代方案。但要注意,如果你不小心,有些方案会带来自己的陷阱。
|
||||
|
||||
### 三元表达式 vs if/else
|
||||
|
||||
假设我们有一个组件被传进来一个 `name` prop。 如果这个字符串非空,我们会显示一个问候语。否则,我们会告诉用户他们需要登录。
|
||||
|
||||
这是一个只实现了如上功能的无状态函数式组件。
|
||||
|
||||
```
|
||||
const MyComponent = ({ name }) => {
|
||||
if (name) {
|
||||
return (
|
||||
<div className="hello">
|
||||
Hello {name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="hello">
|
||||
Please sign in
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
这个很简单但是我们可以做得更好。这是使用三元运算符编写的相同组件。
|
||||
|
||||
```
|
||||
const MyComponent = ({ name }) => (
|
||||
<div className="hello">
|
||||
{name ? `Hello ${name}` : 'Please sign in'}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
请注意这段代码与上面的例子相比是多么简洁。
|
||||
|
||||
有几点需要注意。因为我们使用了箭头函数的单语句形式,所以隐含了return语句。另外,使用三元运算符允许我们省略掉重复的 `<div className="hello">` 标记。🎉
|
||||
|
||||
### 三元表达式 vs &&
|
||||
|
||||
正如您所看到的,三元表达式用于表达 if/else 条件式非常好。但是对于简单的 if 条件式怎么样呢?
|
||||
|
||||
让我们看另一个例子。如果 isPro(一个布尔值)为真,我们将显示一个奖杯表情符号。我们也要渲染星星的数量(如果不是0)。我们可以这样写。
|
||||
|
||||
```
|
||||
const MyComponent = ({ name, isPro, stars}) => (
|
||||
<div className="hello">
|
||||
<div>
|
||||
Hello {name}
|
||||
{isPro ? '🏆' : null}
|
||||
</div>
|
||||
{stars ? (
|
||||
<div>
|
||||
Stars:{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
请注意 “else” 条件返回 null 。 这是因为三元表达式要有"否则"条件。
|
||||
|
||||
对于简单的 “if” 条件式,我们可以使用更合适的东西:&& 运算符。这是使用 “&&” 编写的相同代码。
|
||||
|
||||
```
|
||||
const MyComponent = ({ name, isPro, stars}) => (
|
||||
<div className="hello">
|
||||
<div>
|
||||
Hello {name}
|
||||
{isPro && '🏆'}
|
||||
</div>
|
||||
{stars && (
|
||||
<div>
|
||||
Stars:{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
没有太多区别,但是注意我们消除了每个三元表达式最后面的 `: null` (else 条件式)。一切都应该像以前一样渲染。
|
||||
|
||||
|
||||
嘿!约翰得到了什么?当什么都不应该渲染时,只有一个0。这就是我上面提到的陷阱。这里有解释为什么。
|
||||
|
||||
[根据 MDN][3],一个逻辑运算符“和”(也就是`&&`):
|
||||
|
||||
> `expr1 && expr2`
|
||||
|
||||
> 如果 `expr1` 可以被转换成 `false` ,返回 `expr1`;否则返回 `expr2`。 如此,当与布尔值一起使用时,如果两个操作数都是 true,`&&` 返回 `true` ;否则,返回 `false`。
|
||||
|
||||
好的,在你开始拔头发之前,让我为你解释它。
|
||||
|
||||
在我们这个例子里, `expr1` 是变量 `stars`,它的值是 `0`,因为0是 falsey 的值, `0` 会被返回和渲染。看,这还不算太坏。
|
||||
|
||||
我会简单地这么写。
|
||||
|
||||
> 如果 `expr1` 是 falsey,返回 `expr1` ,否则返回 `expr2`
|
||||
|
||||
所以,当对非布尔值使用 “&&” 时,我们必须让 falsy 的值返回 React 无法渲染的东西,比如说,`false` 这个值。
|
||||
|
||||
我们可以通过几种方式实现这一目标。让我们试试吧。
|
||||
|
||||
```
|
||||
{!!stars && (
|
||||
<div>
|
||||
{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
注意 `stars` 前的双感叹操作符( `!!`)(呃,其实没有双感叹操作符。我们只是用了感叹操作符两次)。
|
||||
|
||||
第一个感叹操作符会强迫 `stars` 的值变成布尔值并且进行一次“非”操作。如果 `stars` 是 `0` ,那么 `!stars` 会 是 `true`。
|
||||
|
||||
然后我们执行第二个`非`操作,所以如果 `stars` 是0,`!!stars` 会是 `false`。正好是我们想要的。
|
||||
|
||||
如果你不喜欢 `!!`,那么你也可以强制转换出一个布尔数比如这样(这种方式我觉得有点冗长)。
|
||||
|
||||
```
|
||||
{Boolean(stars) && (
|
||||
```
|
||||
|
||||
或者只是用比较符产生一个布尔值(有些人会说这样甚至更加语义化)。
|
||||
|
||||
```
|
||||
{stars > 0 && (
|
||||
```
|
||||
|
||||
#### 关于字符串
|
||||
|
||||
空字符串与数字有一样的毛病。但是因为渲染后的空字符串是不可见的,所以这不是那种你很可能会去处理的难题,甚至可能不会注意到它。然而,如果你是完美主义者并且不希望DOM上有空字符串,你应采取我们上面对数字采取的预防措施。
|
||||
|
||||
### 其它解决方案
|
||||
|
||||
一种可能的将来可扩展到其他变量的解决方案,是创建一个单独的 `shouldRenderStars` 变量。然后你用“&&”处理布尔值。
|
||||
|
||||
```
|
||||
const shouldRenderStars = stars > 0;
|
||||
```
|
||||
|
||||
```
|
||||
return (
|
||||
<div>
|
||||
{shouldRenderStars && (
|
||||
<div>
|
||||
{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
之后,在将来,如果业务规则要求你还需要已登录,拥有一条狗以及喝淡啤酒,你可以改变 `shouldRenderStars` 的得出方式,而返回的内容保持不变。你还可以把这个逻辑放在其它可测试的地方,并且保持渲染明晰。
|
||||
|
||||
```
|
||||
const shouldRenderStars =
|
||||
stars > 0 && loggedIn && pet === 'dog' && beerPref === 'light`;
|
||||
```
|
||||
|
||||
```
|
||||
return (
|
||||
<div>
|
||||
{shouldRenderStars && (
|
||||
<div>
|
||||
{'⭐️'.repeat(stars)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### 结论
|
||||
|
||||
我认为你应该充分利用这种语言。对于 JavaScript,这意味着为 `if/else` 条件式使用三元表达式,以及为 `if` 条件式使用 `&&` 操作符。
|
||||
|
||||
我们可以回到每处都使用三元运算符的舒适区,但你现在消化了这些知识和力量,可以继续前进 && 取得成功了。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
美国运通工程博客的执行编辑 http://aexp.io 以及 @AmericanExpress 的工程总监。MyViews !== ThoseOfMyEmployer.
|
||||
|
||||
----------------
|
||||
|
||||
via: https://medium.freecodecamp.org/conditional-rendering-in-react-using-ternaries-and-logical-and-7807f53b6935
|
||||
|
||||
作者:[Donavon West][a]
|
||||
译者:[GraveAccent](https://github.com/GraveAccent)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://medium.freecodecamp.org/@donavon
|
||||
[1]:https://unsplash.com/photos/pKeF6Tt3c08?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
|
||||
[2]:https://unsplash.com/search/photos/road-sign?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
|
||||
[3]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators
|
@ -1,179 +0,0 @@
|
||||
Hugo,30分钟搭建博客,一个Go语言开发的静态站点生成工具
|
||||
======
|
||||

|
||||
|
||||
你是不是强烈的想搭建博客来将自己对软件框架等的探索学习成果分享呢?
|
||||
|
||||
你是不是面对缺乏指导文档而一团糟的项目就有一种想去改变它的冲动呢?
|
||||
|
||||
或者换个角度,你是不是十分期待能创建一个属于自己的个人博客网站呢?
|
||||
|
||||
很多人在想搭建博客之前都有一些严重的迟疑顾虑:感觉自己缺乏内容管理系统(CMS)的相关知识,更缺乏时间去学习这些知识。现在,如果我说不用花费大把的时间去学习 CMS 系统、学习如何创建一个静态网站、更不用操心如何去强化网站以防止它受到黑客攻击的问题,你就可以在 30 分钟之内创建一个博客?你信不信?利用 Hugo 工具,就可以实现这一切。
|
||||
|
||||

|
||||
|
||||
Hugo 是一个基于 Go 语言开发的静态站点生成工具。也许你会问,为什么选择它?
|
||||
* 无需数据库、无需需要各种权限的插件、无需跑在服务器上的底层平台,更没有额外的安全问题。
|
||||
* 都是静态站点,因此拥有轻量级、快速响应的服务性能。此外,所有的网页都是在部署的时候呈现,所以服务器负载很小。
|
||||
* 极易操作的版本控制。一些 CMS 平台使用它们自己的版本控制软件(VCS)或者在网页上集成 Git 工具。而 Hugo,所有的源文件都可以用你所选的 VCS 软件来管理。
|
||||
|
||||
### 0-5 分钟:下载 Hugo,生成一个网站
|
||||
|
||||
直白的说,Hugo 使得写一个网站又一次变得有趣起来。让我们来个 30 分钟计时,搭建一个网站。
|
||||
|
||||
为了简化 Hugo 安装流程,这里直接使用 Hugo 可执行安装文件。
|
||||
|
||||
1. 下载和你操作系统匹配的 Hugo [版本][2];
|
||||
|
||||
2. 压缩包解压到指定路径,例如 windows 系统的 `C:\hugo_dir` 或者 Linux 系统的 `~/hugo_dir` 目录;下文中的变量 `${HUGO_HOME}` 所指的路径就是这个安装目录;
|
||||
|
||||
3. 打开命令行终端,进入安装目录:`cd ${HUGO_HOME}`;
|
||||
|
||||
4. 确认 Hugo 已经启动:
|
||||
* Unix 系统:`${HUGO_HOME}/[hugo version]`;
|
||||
* Windows 系统:`${HUGO_HOME}\[hugo.exe version]`;
|
||||
|
||||
例如:Windows 系统下,cmd 命令行中输入:`c:\hugo_dir\hugo version`。
|
||||
|
||||
为了书写上的简化,下文中的 `hugo` 就是指 hugo 可执行文件所在的路径(包括可执行文件),例如命令 `hugo version` 就是指命令 `c:\hugo_dir\hugo version` 。(译者注:可以把 hugo 可执行文件所在的路径添加到系统环境变量下,这样就可以直接在终端中输入 `hugo version`)
|
||||
如果命令 `hugo version` 报错,你可能下载了错误的版本。当然,有很多种方法安装 Hugo,更多详细信息请查阅 [官方文档][3]。最稳妥的方法就是把 Hugo 可执行文件放在某个路径下,然后执行的时候带上路径名
|
||||
|
||||
5. 创建一个新的站点来作为你的博客,输入命令:`hugo new site awesome-blog`;
|
||||
|
||||
6. 进入新创建的路径下: `cd awesome-blog`;
|
||||
|
||||
恭喜你!你已经创建了自己的新博客。
|
||||
|
||||
### 5-10 分钟:为博客设置主题
|
||||
|
||||
Hugo 中你可以自己构建博客的主题或者使用网上已经有的一些主题。这里选择 [Kiera][4] 主题,因为它简洁漂亮。按以下步骤来安装该主题:
|
||||
|
||||
1. 进入主题所在目录:`cd themes`;
|
||||
|
||||
2. 克隆主题:`git clone https://github.com/avianto/hugo-kiera kiera`。如果你没有安装 Git 工具:
|
||||
* 从 [Github][5] 上下载 hugo 的 .zip 格式的文件;
|
||||
* 解压该 .zip 文件到你的博客主题 `theme` 路径;
|
||||
* 重命名 `hugo-kiera-master` 为 `kiera`;
|
||||
|
||||
3. 返回博客主路径:`cd awesome-blog`;
|
||||
|
||||
4. 激活主题;通常来说,主题(包括 Kiera )都自带文件夹 `exampleSite`,里面存放了内容配置的示例文件。激活 Kiera 主题需要拷贝它提供的 `config.toml` 到你的博客下:
|
||||
* Unix 系统:`cp themes/kiera/exampleSite/config.toml .`;
|
||||
* Windows 系统:`copy themes\kiera\exampleSite\config.toml .`;
|
||||
* 选择 `Yes` 来覆盖原有的 `config.toml`;
|
||||
|
||||
5. ( 可选操作 )你可以选择可视化的方式启动服务器来验证主题是否生效:`hugo server -D` 然后在浏览器中输入 `http://localhost:1313`。可用通过在终端中输入 `Crtl+C` 来停止服务器运行。现在你的博客还是空的,但这也给你留了写作的空间。它看起来如下所示:
|
||||
|
||||

|
||||
|
||||
你已经成功的给博客设置了主题!你可以在官方 [Hugo 主题][4] 网站上找到上百种漂亮的主题供你使用。
|
||||
|
||||
### 10-20 分钟:给博客添加内容
|
||||
|
||||
对于碗来说,它是空的时候用处最大,可以用来盛放东西;但对于博客来说不是这样,空博客几乎毫无用处。在这一步,你将会给博客添加内容。Hugo 和 Kiera 主题都为这个工作提供了方便性。按以下步骤来进行你的第一次提交:
|
||||
|
||||
1. archetypes 将会是你的内容模板。
|
||||
|
||||
2. 添加主题中的 archtypes 至你的博客:
|
||||
* Unix 系统: `cp themes/kiera/archetypes/* archetypes/`
|
||||
* Windows 系统:`copy themes\kiera\archetypes\* archetypes\`
|
||||
* 选择 `Yes` 来覆盖原来的 `default.md` 内容架构类型
|
||||
|
||||
3. 创建博客 posts 目录:
|
||||
* Unix 系统: `mkdir content/posts`
|
||||
* Windows 系统: `mkdir content\posts`
|
||||
|
||||
4. 利用 Hugo 生成你的 post:
|
||||
* Unix 系统:`hugo nes posts/first-post.md`;
|
||||
* Windows 系统:`hugo new posts\first-post.md`;
|
||||
|
||||
5. 在文本编辑器中打开这个新建的 post 文件:
|
||||
* Unix 系统:`gedit content/posts/first-post.md`;
|
||||
* Windows 系统:`notepadd content\posts\first-post.md`;
|
||||
|
||||
此刻,你可以疯狂起来了。注意到你的提交文件中包括两个部分。第一部分是以 `+++` 符号分隔开的。它包括了提交文档的主要数据,例如名称、时间等。在 Hugo 中,这叫做前缀。在前缀之后,才是正文。下面编辑第一个提交文件内容:
|
||||
|
||||
```
|
||||
+++
|
||||
title = "First Post"
|
||||
date = 2018-03-03T13:23:10+01:00
|
||||
draft = false
|
||||
tags = ["Getting started"]
|
||||
categories = []
|
||||
+++
|
||||
Hello Hugo world! No more excuses for having no blog or documentation now!
|
||||
```
|
||||
|
||||
现在你要做的就是启动你的服务器:`hugo server -D`;然后打开浏览器,输入 `http://localhost:1313/`。
|
||||
|
||||

|
||||
|
||||
### 20-30 分钟:调整网站
|
||||
|
||||
前面的工作很完美,但还有一些问题需要解决。例如,简单地命名你的站点:
|
||||
|
||||
1. 终端中按下 `Ctrl+C` 以停止服务器。
|
||||
|
||||
2. 打开 `config.toml`,编辑博客的名称,版权,你的姓名,社交网站等等。
|
||||
|
||||
当你再次启动服务器后,你会发现博客私人订制味道更浓了。不过,还少一个重要的基础内容:主菜单。快速的解决这个问题。返回 `config.toml` 文件,在末尾插入如下一段:
|
||||
|
||||
```
|
||||
[[menu.main]]
|
||||
name = "Home" #Name in the navigation bar
|
||||
weight = 10 #The larger the weight, the more on the right this item will be
|
||||
url = "/" #URL address
|
||||
[[menu.main]]
|
||||
name = "Posts"
|
||||
weight = 20
|
||||
url = "/posts/"
|
||||
```
|
||||
|
||||
上面这段代码添加了 `Home` 和 `Posts` 到主菜单中。你还需要一个 `About` 页面。这次是创建一个 `.md` 文件,而不是编辑 `config.toml` 文件:
|
||||
|
||||
1. 创建 `about.md` 文件:`hugo new about.md` 。注意它是 `about.md`,不是 `posts/about.md`。该页面不是博客提交内容,所以你不想它显示到博客内容提交当中吧。
|
||||
|
||||
2. 用文本编辑器打开该文件,输入如下一段:
|
||||
|
||||
```
|
||||
+++
|
||||
title = "About"
|
||||
date = 2018-03-03T13:50:49+01:00
|
||||
menu = "main" #Display this page on the nav menu
|
||||
weight = "30" #Right-most nav item
|
||||
meta = "false" #Do not display tags or categories
|
||||
+++
|
||||
> Waves are the practice of the water. Shunryu Suzuki
|
||||
|
||||
```
|
||||
|
||||
当你启动你的服务器并输入:`http://localhost:1313/`,你将会看到你的博客。(访问我 Gihub 主页上的 [例子][6] )如果你想让文章的菜单栏和 Github 相似,给 `themes/kiera/static/css/styles.css` 打上这个 [补丁][7]。
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/3/start-blog-30-minutes-hugo
|
||||
|
||||
作者:[Marek Czernek][a]
|
||||
|
||||
译者:[jrg](https://github.com/jrglinux)
|
||||
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/mczernek
|
||||
|
||||
[1]:https://gohugo.io/
|
||||
|
||||
[2]:https://github.com/gohugoio/hugo/releases
|
||||
|
||||
[3]:https://gohugo.io/getting-started/installing/
|
||||
|
||||
[4]:https://themes.gohugo.io/
|
||||
|
||||
[5]:https://github.com/avianto/hugo-kiera
|
||||
|
||||
[6]:https://m-czernek.github.io/awesome-blog/
|
||||
|
||||
[7]:https://github.com/avianto/hugo-kiera/pull/18/files
|
@ -0,0 +1,229 @@
|
||||
Part-II 树莓派自建 NAS 云盘之数据自动备份
|
||||
======
|
||||
|
||||

|
||||
|
||||
在《树莓派自建 NAS 云盘》系列的 [第一篇][1] 文章中,我们讨论了建立 NAS 的一些基本步骤,添加了两块 1TB 的存储硬盘驱动(一个用于数据存储,一个用于数据备份),并且通过 网络文件系统(NFS)将数据存储盘挂载到远程终端上。本文是此系列的第二篇文章,我们将探讨数据自动备份。数据自动备份保证了数据的安全,为硬件损坏后的数据恢复提供便利以及减少了文件误操作带来的不必要的麻烦。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 备份策略
|
||||
|
||||
我们就从为小型 NAS 构想一个备份策略着手开始吧。我建议每天有时间节点有计划的去备份数据,以防止干扰到我们正常的访问 NAS,比如备份时间点避开正在访问 NAS 并写入文件的时间点。举个例子,你可以每天凌晨 2 点去进行数据备份。
|
||||
|
||||
另外,你还得决定每天的备份需要被保留的时间长短,因为如果没有时间限制,存储空间很快就会被用完。一般每天的备份保留一周便可以,如果数据出了问题,你便可以很方便的从备份中恢复出来原数据。但是如果需要恢复数据到更久之前怎么办?可以将每周一的备份文件保留一个月、每个月的备份保留更长时间。让我们把每月的备份保留一年时间,每一年的备份保留更长时间、例如五年。
|
||||
|
||||
这样,五年内在备份盘上产生大量备份:
|
||||
|
||||
* 每周 7 个日备份
|
||||
* 每月 4 个周备份
|
||||
* 每年 12 个月备份
|
||||
* 每五年 5 个年备份
|
||||
|
||||
|
||||
你应该还记得,我们搭建的备份盘和数据盘大小相同(每个 1 TB)。如何将不止 10 个 1TB 数据的备份从数据盘存放到只有 1TB 大小的备份盘呢?如果你创建的是完整备份,这显然不可能。因此,你需要创建增量备份,它是每一份备份都基于上一份备份数据而创建的。增量备份方式不会每隔一天就成倍的去占用存储空间,它每天只会增加一点占用空间。
|
||||
|
||||
以下是我的情况:我的 NAS 自 2016 年 8 月开始运行,备份盘上有 20 个备份。目前,我在数据盘上存储了 406GB 的文件。我的备份盘用了 726GB。当然,备份盘空间使用率在很大程度上取决于数据的更改频率,但正如你所看到的,增量备份不会占用 20 个完整备份所需的空间。然而,随着时间的推移,1TB 空间也可能不足以进行备份。一旦数据增长接近 1TB 限制(或任何备份盘容量),应该选择更大的备份盘空间并将数据移动转移过去。
|
||||
|
||||
### 利用 rsync 进行数据备份
|
||||
|
||||
利用 rsync 命令行工具可以生成完整备份。
|
||||
|
||||
```
|
||||
pi@raspberrypi:~ $ rsync -a /nas/data/ /nas/backup/2018-08-01
|
||||
|
||||
```
|
||||
|
||||
这段命令将挂载在 /nas/data/ 目录下的数据盘中的数据进行了完整的复制备份。备份文件保存在 /nas/backup/2018-08-01 目录下。`-a` 参数是以归档模式进行备份,这将会备份所有的元数据,例如文件的修改日期、权限、拥有者以及软连接文件。
|
||||
|
||||
现在,你已经在 8 月 1 日创建了完整的初始备份,你将在 8 月 2 日创建第一个增量备份。
|
||||
|
||||
```
|
||||
pi@raspberrypi:~ $ rsync -a --link-dest /nas/backup/2018-08-01/ /nas/data/ /nas/backup/2018-08-02
|
||||
|
||||
```
|
||||
|
||||
上面这行代码又创建了一个关于 `/nas/data` 目录中数据的备份。备份路径是 `/nas/backup/2018-08-02`。这里的参数 `--link-dest` 指定了一个备份文件所在的路径。这样,这次备份会与 `/nas/backup/2018-08-01` 的备份进行比对,只备份已经修改过的文件,未做修改的文件将不会被复制,而是创建一个到上一个备份文件中它们的硬链接。
|
||||
|
||||
使用备份文件中的硬链接文件时,你一般不会注意到硬链接和初始拷贝之间的差别。它们表现的完全一样,如果删除其中一个硬链接或者文件,其他的依旧存在。你可以把它们看做是同一个文件的两个不同入口。下面就是一个例子:
|
||||
|
||||

|
||||
|
||||
左侧框是在进行了第二次备份后的原数据状态。中间的盒子是昨天的备份。昨天的备份中只有图片 `file1.jpg` 并没有 `file2.txt` 。右侧的框反映了今天的增量备份。增量备份命令创建昨天不存在的 `file2.txt`。由于 `file1.jpg` 自昨天以来没有被修改,所以今天创建了一个硬链接,它不会额外占用磁盘上的空间。
|
||||
|
||||
### 自动化备份
|
||||
|
||||
你肯定也不想每天凌晨去输入命令进行数据备份吧。你可以创建一个任务定时去调用下面的脚本让它自动化备份
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
DATADIR=/nas/data/
|
||||
|
||||
BACKUPDIR=/nas/backup/
|
||||
|
||||
SCRIPTDIR=/nas/data/backup_scripts
|
||||
|
||||
LASTDAYPATH=${BACKUPDIR}/$(ls ${BACKUPDIR} | tail -n 1)
|
||||
|
||||
TODAYPATH=${BACKUPDIR}/${TODAY}
|
||||
|
||||
if [[ ! -e ${TODAYPATH} ]]; then
|
||||
|
||||
mkdir -p ${TODAYPATH}
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
rsync -a --link-dest ${LASTDAYPATH} ${DATADIR} ${TODAYPATH} $@
|
||||
|
||||
|
||||
|
||||
${SCRIPTDIR}/deleteOldBackups.sh
|
||||
|
||||
```
|
||||
|
||||
第一段代码指定了数据路径、备份路劲、脚本路径以及昨天和今天的备份路径。第二段代码调用 rsync 命令。最后一段代码执行 `deleteOldBackups.sh` 脚本,它会清除一些过期的没有必要的备份数据。如果不想频繁的调用 `deleteOldBackups.sh`,你也可以手动去执行它。
|
||||
|
||||
下面是今天讨论的备份策略的一个简单完整的示例脚本。
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
BACKUPDIR=/nas/backup/
|
||||
|
||||
|
||||
|
||||
function listYearlyBackups() {
|
||||
|
||||
for i in 0 1 2 3 4 5
|
||||
|
||||
do ls ${BACKUPDIR} | egrep "$(date +%Y -d "${i} year ago")-[0-9]{2}-[0-9]{2}" | sort -u | head -n 1
|
||||
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listMonthlyBackups() {
|
||||
|
||||
for i in 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
|
||||
do ls ${BACKUPDIR} | egrep "$(date +%Y-%m -d "${i} month ago")-[0-9]{2}" | sort -u | head -n 1
|
||||
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listWeeklyBackups() {
|
||||
|
||||
for i in 0 1 2 3 4
|
||||
|
||||
do ls ${BACKUPDIR} | grep "$(date +%Y-%m-%d -d "last monday -${i} weeks")"
|
||||
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listDailyBackups() {
|
||||
|
||||
for i in 0 1 2 3 4 5 6
|
||||
|
||||
do ls ${BACKUPDIR} | grep "$(date +%Y-%m-%d -d "-${i} day")"
|
||||
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getAllBackups() {
|
||||
|
||||
listYearlyBackups
|
||||
|
||||
listMonthlyBackups
|
||||
|
||||
listWeeklyBackups
|
||||
|
||||
listDailyBackups
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listUniqueBackups() {
|
||||
|
||||
getAllBackups | sort -u
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function listBackupsToDelete() {
|
||||
|
||||
ls ${BACKUPDIR} | grep -v -e "$(echo -n $(listUniqueBackups) |sed "s/ /\\\|/g")"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
cd ${BACKUPDIR}
|
||||
|
||||
listBackupsToDelete | while read file_to_delete; do
|
||||
|
||||
rm -rf ${file_to_delete}
|
||||
|
||||
done
|
||||
|
||||
```
|
||||
|
||||
这段脚本会首先根据你的备份策略列出所有需要保存的备份文件,然后它会删除那些再也不需要了的备份目录。
|
||||
|
||||
下面创建一个定时任务去执行上面这段代码。以 root 用户权限打开 `crontab -e`,输入以下这段命令,它将会创建一个每天凌晨 2 点去执行 `/nas/data/backup_scripts/daily.sh` 的定时任务。
|
||||
|
||||
```
|
||||
0 2 * * * /nas/data/backup_scripts/daily.sh
|
||||
|
||||
```
|
||||
|
||||
有关创建定时任务请参考 [cron 创建定时任务][2]。
|
||||
|
||||
* 当没有备份任务时,卸载你的备份盘或者将它挂载为只读盘;
|
||||
* 利用远程服务器作为你的备份盘,这样就可以通过互联网同步数据
|
||||
|
||||
你也可用下面的方法来加强你的备份策略,以防止备份数据的误删除或者被破坏:
|
||||
|
||||
本文中备份策略示例是备份一些我觉得有价值的数据,你也可以根据个人需求去修改这些策略。
|
||||
|
||||
我将会在 《树莓派自建 NAS 云盘》 系列的第三篇文章中讨论 [Nextcloud][3]。Nextcloud 提供了更方便的方式去访问 NAS 云盘上的数据并且它还提供了离线操作,你还可以在客户端中同步你的数据。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/8/automate-backups-raspberry-pi
|
||||
|
||||
作者:[Manuel Dewald][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[jrg](https://github.com/jrglinux)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/ntlx
|
||||
[1]: https://opensource.com/article/18/7/network-attached-storage-Raspberry-Pi
|
||||
[2]: https://opensource.com/article/17/11/how-use-cron-linux
|
||||
[3]: https://nextcloud.com/
|
||||
|
@ -0,0 +1,112 @@
|
||||
Part-III 树莓派自建 NAS 云盘之云盘构建
|
||||
======
|
||||
|
||||
用树莓派 NAS 云盘来保护数据的安全!
|
||||
|
||||
在前面两篇文章中(译注:文章链接 [Part-I][1],[Part-II][2]),我们讨论了用树莓派搭建一个 NAS(network-attached storage) 所需要的一些 [软硬件环境及其操作步骤][1]。我们还制定了适当的 [备份策略][2] 来保护NAS上的数据。本文中,我们将介绍讨论利用 [Nestcloud][3] 来方便快捷的存储、获取以及分享你的数据。
|
||||
|
||||
### 必要的准备工作
|
||||
|
||||
想要方便的使用 Nextcloud,需要一些必要的准备工作。首先,你需要一个指向 Nextcloud 的域名。方便起见,本文将使用 **nextcloud.pi-nas.com** 。如果你是在家庭网络里运行,你需要为该域名配置 DNS 服务(动态域名解析服务)并在路由器中开启 80 端口和 443 端口转发功能(如果需要使用 https,则需要开启 443 端口转发,如果只用 http,80 端口足以)。
|
||||
|
||||
你可以使用 [ddclient][4] 在树莓派中自动更新 DNS。
|
||||
|
||||
### 安装 Nextcloud
|
||||
|
||||
为了在树莓派(参考 [Part-I][1] 中步骤设置)中运行 Nextcloud,首先用命令 **apt** 安装 以下的一些依赖软件包。
|
||||
|
||||
```
|
||||
sudo apt install unzip wget php apache2 mysql-server php-zip php-mysql php-dom php-mbstring php-gd php-curl
|
||||
```
|
||||
|
||||
其次,下载 Nextcloud。在树莓派中利用 **wget** 下载其 [最新的版本][5]。在 [Part-I] 文章中,我们将两个磁盘驱动器连接到树莓派,一个用于存储当前数据,另一个用于备份。这里在数据存储盘上安装 Nextcloud,以确保每晚自动备份数据。
|
||||
|
||||
```
|
||||
sudo mkdir -p /nas/data/nextcloud
|
||||
sudo chown pi /nas/data/nextcloud
|
||||
cd /nas/data/
|
||||
wget https://download.nextcloud.com/server/releases/nextcloud-14.0.0.zip -O /nas/data/nextcloud.zip
|
||||
unzip nextcloud.zip
|
||||
sudo ln -s /nas/data/nextcloud /var/www/nextcloud
|
||||
sudo chown -R www-data:www-data /nas/data/nextcloud
|
||||
```
|
||||
|
||||
截止到写作本文时,Nextcloud 最新版更新到如上述代码中所示的 14.0.0 版本。Nextcloud 正在快速的迭代更新中,所以你可以在你的树莓派中安装更新一点的版本。
|
||||
|
||||
### 配置数据库
|
||||
|
||||
如上所述,Nextcloud 安装完毕。之前安装依赖软件包时就已经安装了 MySQL 数据库来存储 Nextcloud 的一些重要数据(例如,那些你创建的可以访问 Nextcloud 的用户的信息)。如果你更愿意使用 Pstgres 数据库,则上面的依赖软件包需要做一些调整。
|
||||
|
||||
以 root 权限启动 MySQL:
|
||||
|
||||
```
|
||||
sudo mysql
|
||||
```
|
||||
|
||||
这将会打开 SQL 提示符界面,在那里可以插入如下指令--使用数据库连接密码替换其中的占位符--为 Nextcloud 创建一个数据库。
|
||||
|
||||
```
|
||||
CREATE USER nextcloud IDENTIFIED BY '<insert-password-here>';
|
||||
CREATE DATABASE nextcloud;
|
||||
GRANT ALL ON nextcloud.* TO nextcloud;
|
||||
```
|
||||
|
||||
按 **Ctrl+D** 或输入 **quit** 退出 SQL 提示符界面。
|
||||
|
||||
### Web 服务器配置
|
||||
|
||||
Nextcloud 可以配置以适配于 Nginx 服务器或者其他 Web 服务器运行的环境。但本文中,我决定在我的树莓派 NAS 中运行 Apache 服务器(如果你有其他效果更好的服务器选择方案,不妨也跟我分享一下)。
|
||||
|
||||
首先为你的 Nextcloud 域名创建一个虚拟主机,创建配置文件 **/etc/apache2/sites-available/001-netxcloud.conf**,在其中输入下面的参数内容。修改其中 ServerName 为你的域名。
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName nextcloud.pi-nas.com
|
||||
ServerAdmin admin@pi-nas.com
|
||||
DocumentRoot /var/www/nextcloud/
|
||||
|
||||
<Directory /var/www/nextcloud/>
|
||||
AllowOverride None
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
使用下面的命令来启动该虚拟主机。
|
||||
|
||||
```
|
||||
a2ensite 001-nextcloud
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
现在,你应该可以通过浏览器中输入域名访问到 web 服务器了。这里我推荐使用 HTTPS 协议而不是 HTTP 协议来访问 Nextcloud。一个简单而且免费的方法就是利用 [Certbot][7] 下载 [Let's Encrypt][6] 证书,然后设置定时任务自动刷新。这样就避免了自签证书等的麻烦。参考 [如何在树莓派中安装][8] Certbot 。在配置 Certbot 的时候,你甚至可以配置将 HTTP 自动转到 HTTPS ,例如访问 **<http://nextcloud.pi-nas.com>** 自动跳转到 **<https://nextcloud.pi-nas.com>**。注意,如果你的树莓派 NAS 运行在家庭路由器的下面,别忘了设置路由器的 443 端口和 80 端口转发。
|
||||
|
||||
### 配置 Nextcloud
|
||||
|
||||
最后一步,通过浏览器访问 Nextcloud 来配置它。在浏览器中输入域名地址,插入上文中的数据库设置信息。这里,你可以创建 Nextcloud 管理员用户。默认情况下,数据保存目录在在 Nextcloud 目录下,所以你也无需修改我们在 [Part-II][2] 一文中设置的备份策略。
|
||||
|
||||
然后,页面会跳转到 Nextcloud 登陆界面,用刚才创建的管理员用户登陆。在设置页面中会有基础操作教程和安全安装教程(这里是访问 <https://nextcloud.pi-nas.com/>settings/admin)。
|
||||
|
||||
恭喜你,到此为止,你已经成功在树莓派中安装了你自己的云 Nextcloud。去 Nextcloud 主页 [下载 Nextcloud 客户端][9],客户端可以同步数据并且离线访问服务器。移动端甚至可以上传图片等资源,然后电脑桌面都可以去访问它们。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/9/host-cloud-nas-raspberry-pi
|
||||
|
||||
作者:[Manuel Dewald][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[jrg](https://github.com/jrglinux)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/ntlx
|
||||
[1]: https://opensource.com/article/18/7/network-attached-storage-Raspberry-Pi
|
||||
[2]: https://opensource.com/article/18/8/automate-backups-raspberry-pi
|
||||
[3]: https://nextcloud.com/
|
||||
[4]: https://sourceforge.net/p/ddclient/wiki/Home/
|
||||
[5]: https://nextcloud.com/install/#instructions-server
|
||||
[6]: https://letsencrypt.org/
|
||||
[7]: https://certbot.eff.org/
|
||||
[8]: https://certbot.eff.org/lets-encrypt/debianother-apache
|
||||
[9]: https://nextcloud.com/install/#install-clients
|
||||
|
@ -0,0 +1,60 @@
|
||||
使用 `top` 命令了解 Fedora 的内存使用情况
|
||||
======
|
||||
|
||||

|
||||
|
||||
如果你使用过 `top` 命令来查看 Fedora 系统中的内存使用情况,你可能会惊讶,显示的数值看起来比系统可用的内存消耗更多。下面会详细介绍内存使用情况以及如何理解这些数据。
|
||||
|
||||
### 内存实际使用情况
|
||||
|
||||
操作系统对内存的使用方式并不是太通俗易懂,而是有很多不为人知的巧妙方式。通过这些方式,可以在无需用户干预的情况下,让操作系统更有效地使用内存。
|
||||
|
||||
大多数应用程序都不是系统自带的,但每个应用程序都依赖于安装在系统中的库中的一些函数集。在 Fedora 中,RPM 包管理系统能够确保在安装应用程序时也会安装所依赖的库。
|
||||
|
||||
当应用程序运行时,操作系统并不需要将它要用到的所有信息都加载到物理内存中。而是会为存放代码的存储构建一个映射,称为虚拟内存。操作系统只把需要的部分加载到内存中,当某一个部分不再需要后,这一部分内存就会被释放掉。
|
||||
|
||||
这意味着应用程序可以映射大量的虚拟内存,而使用较少的系统物理内存。特殊情况下,映射的虚拟内存甚至可以比系统实际可用的物理内存更多!而且在操作系统中这种情况也并不少见。
|
||||
|
||||
另外,不同的应用程序可能会对同一个库都有依赖。Fedora 中的 Linux 内核通常会在各个应用程序之间共享内存,而不需要为不同应用分别加载同一个库的多个副本。类似地,对于同一个应用程序的不同实例也是采用这种方式共享内存。
|
||||
|
||||
如果不首先了解这些细节,`top` 命令显示的数据可能会让人摸不着头脑。下面就举例说明如何正确查看内存使用量。
|
||||
|
||||
### 使用 `top` 命令查看内存使用量
|
||||
|
||||
如果你还没有使用过 `top` 命令,可以打开终端直接执行查看。使用 **Shift + M** 可以按照内存使用量来进行排序。下图是在 Fedora Workstation 中执行的结果,在你的机器上显示的结果可能会略有不同:
|
||||
|
||||

|
||||
|
||||
主要通过一下三列来查看内存使用情况:VIRT,RES 和 SHR。目前以 KB 为单位显示相关数值。
|
||||
|
||||
VIRT 列代表该进程映射的虚拟内存。如上所述,虚拟内存不是实际消耗的物理内存。例如, GNOME Shell 进程 gnome-shell 实际上没有消耗超过 3.1 GB 的物理内存,但它对很多更低或更高级的库都有依赖,系统必须对每个库都进行映射,以确保在有需要时可以加载这些库。
|
||||
|
||||
RES 列代表应用程序消耗了多少实际(驻留)内存。对于 GNOME Shell 大约是 180788 KB。例子中的系统拥有大约 7704 MB 的物理内存,因此内存使用率显示为 2.3%。
|
||||
|
||||
但根据 SHR 列显示,其中至少有 88212 KB 是共享内存,这部分内存可能是其它应用程序也在使用的库函数。这意味着 GNOME Shell 本身大约有 92 MB 内存不与其他进程共享。需要注意的是,上述例子中的其它程序也共享了很多内存。在某些应用程序中,共享内存在内存使用量中会占很大的比例。
|
||||
|
||||
值得一提的是,有时进程之间通过内存通信,这些内存也是共享的,但 `top` 工具却不一定能检测到,所以以上的说明也不一定准确。(这一句不太会翻译出来,烦请校对大佬帮忙看看,谢谢)
|
||||
|
||||
### 关于交换分区
|
||||
|
||||
系统还可以通过交换分区来存储数据(例如硬盘),但读写的速度相对较慢。当物理内存渐渐用满,操作系统就会查找内存中暂时不会使用的部分,将其写出到交换区域等待需要的时候使用。
|
||||
|
||||
因此,如果交换内存的使用量一直偏高,表明系统的物理内存已经供不应求了。尽管错误的内存申请也有可能导致出现这种情况,但如果这种现象经常出现,就需要考虑提升物理内存或者限制某些程序的运行了。
|
||||
|
||||
感谢 [Stig Nygaard][1] 在 [Flickr][2] 上提供的图片(CC BY 2.0)。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://fedoramagazine.org/understand-fedora-memory-usage-top/
|
||||
|
||||
作者:[Paul W. Frields][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[HankChow](https://github.com/HankChow)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://fedoramagazine.org/author/pfrields/
|
||||
[1]: https://www.flickr.com/photos/stignygaard/
|
||||
[2]: https://www.flickr.com/photos/stignygaard/3138001676/
|
||||
|
Loading…
Reference in New Issue
Block a user