分类 技术 下的文章

本文是 24 天 Linux 桌面特别系列的一部分。Openbox 窗口管理器占用很小的系统资源、易于配置、使用愉快。

你可能不知道你使用过 Openbox 桌面:尽管 Openbox 本身是一个出色的窗口管理器,但它还是 LXDE 和 LXQT 等桌面环境的窗口管理器“引擎”,它甚至可以管理 KDE 和 GNOME。除了作为多个桌面的基础之外,Openbox 可以说是最简单的窗口管理器之一,可以为那些不想学习那么多配置选项的人配置。通过使用基于菜单的 obconf 的配置应用,可以像在 GNOME 或 KDE 这样的完整桌面中一样轻松地设置所有常用首选项。

安装 Openbox

你可能会在 Linux 发行版的软件仓库中找到 Openbox,也可以在 Openbox.org 中找到它。如果你已经在运行其他桌面,那么可以安全地在同一系统上安装 Openbox,因为 Openbox 除了几个配置面板之外,不包括任何捆绑的应用。

安装后,退出当前桌面会话,以便你可以登录 Openbox 桌面。默认情况下,会话管理器(KDM、GDM、LightDM 或 XDM,这取决于你的设置)将继续登录到以前的桌面,因此你必须在登录之前覆盖该选择。

要使用 GDM 覆盖它:

 title=

要使用 SDDM 覆盖它:

 title=

配置 Openbox 桌面

默认情况下,Openbox 包含 obconf 应用,你可以使用它来选择和安装主题、修改鼠标行为、设置桌面首选项等。你可能会在仓库中发现其他配置应用,如 obmenu,用于配置窗口管理器的其他部分。

 title=

构建你自己的桌面环境相对容易。它有一些所有常见的桌面组件,例如系统托盘 stalonetray、任务栏 Tint2Xfce4-panel 等几乎你能想到的。任意组合应用,直到拥有梦想的开源桌面为止。

 title=

为何使用 Openbox

Openbox 占用的系统资源很小、易于配置、使用起来很愉悦。它基本不会让你感觉到阻碍,会是一个容易熟悉的系统。你永远不会知道你面前的桌面环境秘密使用了 Openbox 作为窗口管理器(知道如何自定义它会不会很高兴?)。如果开源吸引你,那么试试看 Openbox。


via: https://opensource.com/article/19/12/openbox-linux-desktop

作者:Seth Kenlon 选题:lujun9972 译者:geekpi 校对:wxy

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

Python 2 气数将尽,是时候将你的项目从 Python 2 迁移到 Python 3 了。

Python 2.x 很快就要失去官方支持了,尽管如此,从 Python 2 迁移到 Python 3 却并没有想象中那么难。我在上周用了一个晚上的时间将一个 3D 渲染器的前端代码及其对应的 PySide 迁移到 Python 3,回想起来,尽管在迁移过程中无可避免地会遇到一些牵一发而动全身的修改,但整个过程相比起痛苦的重构来说简直是出奇地简单。

每个人都别无选择地有各种必须迁移的原因:或许是觉得已经拖延太久了,或许是依赖了某个在 Python 2 下不再维护的模块。但如果你仅仅是想通过做一些事情来对开源做贡献,那么把一个 Python 2 应用迁移到 Python 3 就是一个简单而又有意义的做法。

无论你从 Python 2 迁移到 Python 3 的原因是什么,这都是一项重要的任务。按照以下三个步骤,可以让你把任务完成得更加清晰。

1、使用 2to3

从几年前开始,Python 在你或许还不知道的情况下就已经自带了一个名叫 2to3 的脚本,它可以帮助你实现大部分代码从 Python 2 到 Python 3 的自动转换。

下面是一段使用 Python 2.6 编写的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
mystring = u'abcdé'
print ord(mystring[-1])

对其执行 2to3 脚本:

$ 2to3 example.py
RefactoringTool: Refactored example.py
--- example.py     (original)
+++ example.py     (refactored)
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-mystring = u'abcdé'
-print ord(mystring[-1])
+mystring = 'abcdé'
+print(ord(mystring[-1]))
RefactoringTool: Files that need to be modified:
RefactoringTool: example.py

在默认情况下,2to3 只会对迁移到 Python 3 时必须作出修改的代码进行标示,在输出结果中显示的 Python 3 代码是直接可用的,但你可以在 2to3 加上 -w 或者 --write 参数,这样它就可以直接按照给出的方案修改你的 Python 2 代码文件了。

$ 2to3 -w example.py
[...]
RefactoringTool: Files that were modified:
RefactoringTool: example.py

2to3 脚本不仅仅对单个文件有效,你还可以把它用于一个目录下的所有 Python 文件,同时它也会递归地对所有子目录下的 Python 文件都生效。

2、使用 Pylint 或 Pyflakes

有一些不良的代码在 Python 2 下运行是没有异常的,在 Python 3 下运行则会或多或少报出错误,这种情况并不鲜见。因为这些不良代码无法通过语法转换来修复,所以 2to3 对它们没有效果,但一旦使用 Python 3 来运行就会产生报错。

要找出这种问题,你需要使用 PylintPyflakes(或 flake8 封装器)这类工具。其中我更喜欢 Pyflakes,它会忽略代码风格上的差异,在这一点上它和 Pylint 不同。尽管代码优美是 Python 的一大特点,但在代码迁移的层面上,“让代码功能保持一致”无疑比“让代码风格保持一致”重要得多。

以下是 Pyflakes 的输出样例:

$ pyflakes example/maths
example/maths/enum.py:19: undefined name 'cmp'
example/maths/enum.py:105: local variable 'e' is assigned to but never used
example/maths/enum.py:109: undefined name 'basestring'
example/maths/enum.py:208: undefined name 'EnumValueCompareError'
example/maths/enum.py:208: local variable 'e' is assigned to but never used

上面这些由 Pyflakes 输出的内容清晰地给出了代码中需要修改的问题。相比之下,Pylint 会输出多达 143 行的内容,而且多数是诸如代码缩进这样无关紧要的问题。

值得注意的是第 19 行这个容易产生误导的错误。从输出来看你可能会以为 cmp 是一个在使用前未定义的变量,实际上 cmp 是 Python 2 的一个内置函数,而它在 Python 3 中被移除了。而且这段代码被放在了 try 语句块中,除非认真检查这段代码的输出值,否则这个问题很容易被忽略掉。

    try:
        result = cmp(self.index, other.index)
    except:
        result = 42
       
    return result

在代码迁移过程中,你会发现很多原本在 Python 2 中能正常运行的函数都发生了变化,甚至直接在 Python 3 中被移除了。例如 PySide 的绑定方式发生了变化、importlib 取代了 imp 等等。这样的问题只能见到一个解决一个,而涉及到的功能需要重构还是直接放弃,则需要你自己权衡。但目前来说,大多数问题都是已知的,并且有完善的文档记录。所以难的不是修复问题,而是找到问题,从这个角度来说,使用 Pyflake 是很有必要的。

3、修复被破坏的 Python 2 代码

尽管 2to3 脚本能够帮助你把代码修改成兼容 Python 3 的形式,但对于一个完整的代码库,它就显得有点无能为力了,因为一些老旧的代码在 Python 3 中可能需要不同的结构来表示。在这样的情况下,只能人工进行修改。

例如以下代码在 Python 2.6 中可以正常运行:

class CLOCK_SPEED:
        TICKS_PER_SECOND = 16
        TICK_RATES = [int(i * TICKS_PER_SECOND)
                      for i in (0.5, 1, 2, 3, 4, 6, 8, 11, 20)]

class FPS:
        STATS_UPDATE_FREQUENCY = CLOCK_SPEED.TICKS_PER_SECOND

类似 2to3 和 Pyflakes 这些自动化工具并不能发现其中的问题,但如果上述代码使用 Python 3 来运行,解释器会认为 CLOCK_SPEED.TICKS_PER_SECOND 是未被明确定义的。因此就需要把代码改成面向对象的结构:

class CLOCK_SPEED:
        def TICKS_PER_SECOND():
                TICKS_PER_SECOND = 16
                TICK_RATES = [int(i * TICKS_PER_SECOND)
                        for i in (0.5, 1, 2, 3, 4, 6, 8, 11, 20)]
                return TICKS_PER_SECOND

class FPS:
        STATS_UPDATE_FREQUENCY = CLOCK_SPEED.TICKS_PER_SECOND()

你也许会认为如果把 TICKS_PER_SECOND() 改写为一个构造函数(用 __init__ 函数设置默认值)能让代码看起来更加简洁,但这样就需要把这个方法的调用形式从 CLOCK_SPEED.TICKS_PER_SECOND() 改为 CLOCK_SPEED() 了,这样的改动或多或少会对整个库造成一些未知的影响。如果你对整个代码库的结构烂熟于心,那么你确实可以随心所欲地作出这样的修改。但我通常认为,只要我做出了修改,都可能会影响到其它代码中的至少三处地方,因此我更倾向于不使代码的结构发生改变。

坚持信念

如果你正在尝试将一个大项目从 Python 2 迁移到 Python 3,也许你会觉得这是一个漫长的过程。你可能会费尽心思也找不到一条有用的报错信息,这种情况下甚至会有将代码推倒重建的冲动。但从另一个角度想,代码原本在 Python 2 中就可以运行,要让它能在 Python 3 中继续运行,你需要做的只是对它稍加转换而已。

但只要你完成了迁移,你就得到了这个模块或者整个应用程序的 Python 3 版本,外加 Python 官方的长期支持。


via: https://opensource.com/article/19/12/update-apps-python-3

作者:Seth Kenlon 选题:lujun9972 译者:HankChow 校对:wxy

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

每当需要在线共享代码片段时,我们想到的第一个便是 Pastebin.com,这是 Paul Dixon 于 2002 年推出的在线文本共享网站。现在,有几种可供选择的文本共享服务可以上传和共享文本片段、错误日志、配置文件、命令输出或任何类型的文本文件。如果你碰巧经常使用各种类似于 Pastebin 的服务来共享代码,那么这对你来说确实是个好消息。向 Wgetpaste 打个招呼吧,它是一个命令行 BASH 实用程序,可轻松地将文本摘要上传到类似 Pastebin 的服务中。使用 Wgetpaste 脚本,任何人都可以与自己的朋友、同事或想在类似 Unix 的系统中的命令行中查看/使用/审查代码的人快速共享文本片段。

安装 Wgetpaste

Wgetpaste 在 Arch Linux [Community] 存储库中可用。要将其安装在 Arch Linux 及其变体(如 Antergos 和 Manjaro Linux)上,只需运行以下命令:

$ sudo pacman -S wgetpaste

对于其他发行版,请从 Wgetpaste 网站 获取源代码,并按如下所述手动安装。

首先下载最新的 Wgetpaste tar 文件:

$ wget http://wgetpaste.zlin.dk/wgetpaste-2.28.tar.bz2

提取它:

$ tar -xvjf wgetpaste-2.28.tar.bz2

它将 tar 文件的内容提取到名为 wgetpaste-2.28 的文件夹中。

转到该目录:

$ cd wgetpaste-2.28/

wgetpaste 二进制文件复制到 $PATH 中,例如 /usr/local/bin/

$ sudo cp wgetpaste /usr/local/bin/

最后,使用命令使其可执行:

$ sudo chmod +x /usr/local/bin/wgetpaste

将文本片段上传到类似 Pastebin 的服务中

使用 Wgetpaste 上传文本片段很简单。让我向你展示一些示例。

1、上传文本文件

要使用 Wgetpaste 上传任何文本文件,只需运行:

$ wgetpaste mytext.txt

此命令将上传 mytext.txt 文件的内容。

示例输出:

Your paste can be seen here: https://paste.pound-python.org/show/eO0aQjTgExP0wT5uWyX7/

你可以通过邮件、短信、whatsapp 或 IRC 等任何媒体共享 pastebin 的 URL。拥有此 URL 的人都可以访问它,并在他们选择的 Web 浏览器中查看文本文件的内容。

这是 Web 浏览器中 mytext.txt 文件的内容:

你也可以使用 tee 命令显示粘贴的内容,而不是盲目地上传它们。

为此,请使用如下的 -t 选项。

$ wgetpaste -t mytext.txt

2、将文字片段上传到其他服务

默认情况下,Wgetpaste 会将文本片段上传到 poundpython(https://paste.pound-python.org/)服务。

要查看支持的服务列表,请运行:

$ wgetpaste -S

示例输出:

Services supported: (case sensitive):
Name: | Url:
=============|=================
bpaste | https://bpaste.net/
codepad | http://codepad.org/
dpaste | http://dpaste.com/
gists | https://api.github.com/gists
*poundpython | https://paste.pound-python.org/

在这里,* 表示默认服务。

如你所见,Wgetpaste 当前支持五种文本共享服务。我并没有全部尝试,但是我相信所有服务都可以使用。

要将内容上传到其他服务,例如 bpaste.net,请使用如下所示的 -s 选项。

$ wgetpaste -s bpaste mytext.txt
Your paste can be seen here: https://bpaste.net/show/5199e127e733

3、从标准输入读取输入

Wgetpaste 也可以从标准输入读取。

$ uname -a | wgetpaste

此命令将上传 uname -a 命令的输出。

4、上传命令及命令的输出

有时,你可能需要粘贴命令及其输出。为此,请在如下所示的引号内指定命令的内容。

$ wgetpaste -c 'ls -l'

这会将命令 ls -l 及其输出上传到 pastebin 服务。

当你想让其他人清楚地知道你刚运行的确切命令及其输出时,此功能很有用。

如你在输出中看到的,我运行了 ls -l 命令。

5、上载系统日志文件、配置文件

就像我已经说过的,我们可以上载你的系统中任何类型的文本文件,而不仅仅是普通的文本文件,例如日志文件、特定命令的输出等。例如,你刚刚更新了 Arch Linux 机器,最后系统损坏了。你问你的同事该如何解决此问题,他(她)想阅读 pacman.log 文件。 这是上传 pacman.log 文件内容的命令:

$ wgetpaste /var/log/pacman.log

与你的同事共享 pastebin URL,以便他/她可以查看 pacman.log,并通过查看日志文件来帮助你解决问题。

通常,日志文件的内容可能太长,你不希望全部共享它们。在这种情况下,只需使用 cat 命令读取输出,然后使用 tail -n 命令定义要共享的行数,最后将输出通过管道传递到 Wgetpaste,如下所示。

$ cat /var/log/pacman.log | tail -n 50 | wgetpaste

上面的命令将仅上传 pacman.log 文件的“最后 50 行”。

6、将输入网址转换为短链接

默认情况下,Wgetpaste 将在输出中显示完整的 pastebin URL。如果要将输入 URL 转换为短链接,只需使用 -u 选项。

$ wgetpaste -u mytext.txt
Your paste can be seen here: http://tinyurl.com/y85d8gtz

7、设定语言

默认情况下,Wgetpaste 将上传“纯文本”中的文本片段。

要列出指定服务支持的语言,请使用 -L 选项。

$ wgetpaste -L

该命令将列出默认服务(poundpython https://paste.pound-python.org/)支持的所有语言。

我们可以使用 -l 选项来改变它。

$ wgetpaste -l Bash mytext.txt

8、在输出中禁用语法突出显示或 html

如上所述,文本片段将以特定的语言格式(纯文本、Bash 等)显示。

但是,你可以更改此行为,以使用 -r 选项显示原始文本摘要。

$ wgetpaste -r mytext.txt
Your raw paste can be seen here: https://paste.pound-python.org/raw/CUJhQ3jEmr2UvfmD2xCL/

如你在上面的输出中看到的,没有语法突出显示,没有 html 格式。只是原始输出。

9、更改 Wgetpaste 默认值

所有默认值(DEFAULT_{NICK,LANGUAGE,EXPIRATION}[_${SERVICE}]DEFAULT_SERVICE)都可以在 /etc/wgetpaste.conf 中全局更改,也可以在 ~/.wgetpaste.conf 文件中针对每个用户更改。但是,这些文件在我的系统中默认情况下并不存在。我想我们需要手动创建它们。开发人员已经在这里这里为这两个文件提供了示例内容。只需使用给定的样本内容手动创建这些文件,并相应地修改参数即可更改 Wgetpaste 的默认设置。

10、获得帮助

要显示帮助部分,请运行:

$ wgetpaste -h

via: https://www.ostechnix.com/how-to-easily-upload-text-snippets-to-pastebin-like-services-from-commandline/

作者:SK 选题:lujun9972 译者:wxy 校对:wxy

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

随着新一波的区块链热潮,许多同学怀着巨大的热情进入了这个领域,同时也会遇到不少疑惑,区块链开发需要哪些知识?怎么学习?从哪里学习?遇到问题怎么办?本文将试图给区块链领域新人一个快速实用的指引。

一、基本 IT 技能

区块链堪称“黑科技”,本身具有大量的技术元素,有志于从技术角度切入区块链的人,应该具备或掌握基本的 IT 技能,达到至少是常规级别“程序员”或“系统管理员”的技能水平。

首先需要熟练的 Linux 操作系统知识

大多数区块链系统是可以跑在 Linux 上的,包括 CentOS 和 Ubuntu 等,你至少要会一些基本的 Linux 操作指令,比如 ls 查看目录、pstop 查看进程、find 查找文件、netstat 查看网络、ulimit 检查系统参数限制、df/du 查看磁盘空间、用 apt/yum 安装软件等等,如果这些基本命令都不掌握,在 Linux 上操作肯定是举步维艰的。

这方面的书和资料都很多,相信一星期就能上手。另外,善于 Linux 的 man 指令,可以获得每个命令的详细帮助。如果学会写 shell 脚本,那更如虎添翼,可以把大量的繁琐操作给自动化了。

要有清晰的网络概念

区块链本来是分布式系统,节点之间一定是通过网络相连的,只是跑起来的话,不需要多高深的网络知识,只需要了解什么是 TCP/IP;公网、内网、本地地址的区别;端口如何配置;节点和节点、SDK 和节点之间的互联是否会被防火墙和网络策略挡住;采用 ifconfigtelnetpingnetstat 等命令检查网络信息和进行探测、定位网络问题。一般来说,Linux 书籍也都会介绍这部分内容。

区块链周边的支持,如浏览器、中间件、业务应用,会依赖一些第三方基础软件,如 MySQL/MariaDB 数据库、Nginx 服务、Tomcat 服务等,至少懂得怎么去安装指定版本的软件,掌握修改这些软件的配置文件并使之生效的基本操作,了解各款软件的密码、权限配置和网络安全策略,以保护自身安全。

如果是基于云、Docker 或者 k8s 等容器环境构建,需要了解使用的服务商或容器的功能、性能、配置方式,包括对资源的分配:CPU、内存、带宽、存储等,以及安全和权限的配置、网络策略配置、运维方式,达到轻松分发构建的同时,还能保持其稳定性和可用性。

各种云服务商和容器解决方案都有周全的文档和客服服务渠道,可以帮助用户顺畅地使用。

到编程语言阶段,可以根据自己的学习路径,选择不同的语言

如果是使用 Java 语言,那就应该熟练掌握 Eclipse、IntelliJ IDEA 等集成 IDE,熟悉 Gradle 为主的工程管理软件,熟悉 Spring、Springboot 等 Java 的基础开发组件,熟悉在 IDE 或命令行下对资源路径如 ApplicationContext 等路径的定义,或许还有 myBatis 等流行的组件,这些都可以在 Java 相关的社区和网站找到资料和书籍。

在熟练使用 Java 语言的情况下,采用 Java SDK 接入到区块链,跑起一个演示示例,将是非常轻松写意的事情。

如果是采用其他语言,我们也提供了 Python、Node.js、Golang 等语言的区块链 SDK。

不同的语言,其安装包有不同的稳定版本,会采用不同的环境和依赖安装配置方法,会有不同的 IDE 和调试方法,就不在本文一一罗列,相信学习和使用语言这件事本身,于程序员已经是最基本的技能了。

最后,作为在开源世界里冲浪的玩家,“全球最大同性交友网站”——GitHub 一定是要上的了

注册 GitHub 账号,掌握 Git 版本管理工具的基本操作,克隆和拉取开源软件代码,提交议题,提交自己的修改,给开源项目提交拉取请求,再顺手点个星标,激情而有范儿,在开源世界里留下你的姓名。

二、区块链领域的基础知识栈

以下部分的知识和区块链或区块链某一个平台更加相关,从底到上依次是:

HASH(哈希算法)、签名、证书

严格来说,这并不是区块链领域的专有知识,只是必须具备的基础知识,包括 SHA3/SHA256/RIPEMD160 等摘要算法,以及这些算法和“区块链地址”的关系,基于公私钥的数字签名和验证方法,数字证书的概念和格式,比如 X.509 证书,以及保存证书/公私钥的文件格式,如 PEM 文件、keystore文件等。

基础应用密码学

基础应用密码学其实范围很广,作为入门者,至少要了解对称和非对称加密的常见算法,如 AES 对称加密,RSA、ECDSA 椭圆曲线等非对称加密算法,以及这些算法在签名验签、数据加密、通信协商和保护方面的作用。如果要使用国密,那么需要了解 SM2~SM9 一系列算法的概念和使用。

分布式网络结构

区块链是先天的“分布式网络系统”,节点和节点通过网络的 P2P 端口互连,客户端、SDK 通过 RPC/通道端口互连,首先要保证网络之间是互通的,监听的地址和端口是对的,端口是开放的,防火墙和网络策略是正确的,用于安全连接的证书已经到位,才能保证区块链的“通则不痛”。

这也要求使用者具备基本的网络知识、网络工具,同时了解区块链特有的节点类型(共识节点、观察节点、轻节点等)、互连方式(点对点双向连接、JSON RPC 的 HTTP 短连接、通道长连接等)。详情点击参考《FISCO BCOS 网络端口讲解》

智能合约

智能合约可说是应用开发者直面区块链的一道大门,入得此门,精彩无穷。流行的智能合约语言是 Solidity 语言,这门源自以太坊,从诞生开始就是为区块链而来的。

Solidity 语言更新活跃、文档完备,具有良好的一致性和事务性,功能足够实现中型的商业应用。

当然,它在实时调试、第三库支持、运行速度等方面还比不上成熟的语言,如果开发者想要用 C++ 等语言编写智能合约,那就要对区块链上的计算范式进行深入了解,避免写出无法共识的智能合约来,一般是建议有深入的了解后再采用 Solidity 之外的其他语言编写合约。

要掌握 Solidity 合约,当然是通读文档,并动手尝试。具体参考该文档

ABI 接口原理

在采用 EVM 作为虚拟机的区块链上,EVM 执行的是 Solidity 语言的合约。合约编译会生成后缀名为 ABI 的文件,其实里面就是该合约接口定义的 JSON 文本,可以用文本查看器查阅,了解你写的合约如何翻译成 ABI 里的接口,接口返回类型,参数列表,参数类型等,只要有合约的 ABI 文件,就可以调用区块链 SDK 的接口,解析这个合约相关的交易、返回值、回执等。

区块数据结构

区块 Block 有区块头和区块体。区块体有交易列表,交易列表里的每个 交易 Transaction (Tx)有发起方、目标地址、调用方法和参数,以及发送者签名。交易的结果会生成一个“ 回执 Receipt ”,回执里包含被调用方法的返回值、运行过程生成的 EventLog 等……

了解这些,基本上就掌握了区块链数据的脉络,还可以继续深究数据结构里的梅克尔根以及对应的梅克尔树是如何生成的,有什么作用(如用于 SPV:Simplified PaymentVerification)。具体参考该文档

RPC 接口

这里把区块链节点暴露的功能接口统称为“RPC 接口”。查看链上数据,包括区块、交易、回执、系统信息、配置信息,向链上发起交易,以调用智能合约、修改系统配置等,或者通过 AMOP 协议发送消息、监听事件,都是通过 RPC 接口。

几十个 RPC 接口建议一一走读,或善用搜索,以发现自己想要的接口。

接口通信采用的协议可能是 JSON RPC,或者是 FISCO BCOS 独创的通道协议,SDK 基本上已经对接口和协议进行了良好的包装,也可以在深入理解 ABI 和 RLP 等编码模式前提下自行开发接口客户端。具体参考该文档

准入和权限模型

联盟链强调安全可控,节点准入是第一步,在链初始化后,其他节点或者 SDK 配置了相应的证书,才能接入到既有的联盟链上。

链上的角色用权限模型控制,包括管理员权限、发布合约的权限、创建表的权限、参数配置权限等,以避免角色之间操作混淆,某些角色既当运动员又当裁判员。

初学者需要仔细阅读区块链平台提供的技术文档了解原理,遵循操作手册的步骤进行操作。具体参考该文档

数据存储模型

区块链节点会采用文件数据库(LevelDB 或 RocksDB),或者关系型数据库如 MySQL 保存数据,所以,链上是真的有“数据库”的。

写入数据库的数据包括区块、交易、回执、合约产生的状态数据等,是否写入“调用合约产生的历史数据”根据不同的平台而定, FISCO BCOS 默认只保存最新的状态值,可以选择性地将修改记录写入“回执”或“历史表”里进行追踪。

FISCO BCOS 还提供方案,将历史数据导出到链下数据库进行关联分析。具体参考该文档

共识机制原理

联盟链通常采用插件化共识机制实现,FISCO BCOS 提供 PBFT 和 RAFT 两种高效共识算法,而不会采用“挖矿”这些高耗能低效率的共识。

共识机制是区块链的灵魂,对共识机制进行深入学习,才可以深入理解区块链通过多方协作、达成高度一致性、支持交易事务性、防篡改防作恶的功效。具体参考该文档

区块链的知识包罗万象,更深层次的知识还有分布式系统理论、博弈论、前沿密码学、经济学、社会学等,掌握以上的基础知识,再深入学习,举一反三,用场景去验证和探索创新式应用,方可发挥技术的潜力,感受分布式商业的魅力。

三、做一个怎样的学习者

在这个过程中,希望学习者做到:

读文档的耐心

我们的开源项目文档足有 20 万字以上的篇幅,公众号里还有大量的技术解析和科普文章,这都是程序员们在编码之外,用尽自己仅有的语文储备,码出的海量文字,是一笔巨大的技术财富,涵盖了相关开源项目的方方面面。如果能通读,或者记住文档结构和标题,需要时快速打开,足以解惑且深入。

搜资料的能力

文档、公众号都有搜索功能,当想起和开源社区有关的问题时,可以随手用关键字搜索,一般都能找到答案。如果有语言不详之处,可以向开源项目团队提出,或者根据自己的理解进行补充。通用的知识点,如操作系统、网络等,通过公网搜索引擎,一般都能找到答案。

排查环境和依赖问题的能力

开源软件牵涉的系统环境、第三方软件、软件的版本等常常有错综复杂的依赖关系,太高或太低的版本都可能会有一些问题,请注意阅读项目文档对软硬件环境和依赖的描述,保证自己的环境符合要求,并善用配置管理工具、软件安装工具获取和设置合适的版本。

调试能力

如上所述,Solidity 语言的调试工具完善程度尚未达到完美,但可以善用合约的返回值、EventLog 等方式,通过 WeBASE、控制台等趁手的工具进行调试,并查阅 Solidity 文档,了解问题可能出在哪里。

区块链节点的日志开启 debug 级别后,也会打印详细的信息,可以查阅运行日志,获取运行信息和可能的错误信息,将这些信息与自己所做的操作比如发交易的流程结合起来进行分析,提高调试效率。

同时,目前的开源软件通常会在屏幕上打印错误原因和解决问题的提示,仔细查看操作反馈,大概率能了解错误原因和解决方案。

代码阅读能力

开源软件的最大效能是把代码毫无遗漏的摊到了开发者和学习者面前,了解代码结构,查阅代码里的关键流程,用关键字去搜索代码里的对应实现,都可以深入系统细节,挖掘设计思想,定位问题,寻找优化方法。一个好学且硬核的程序员,足可通过代码,和世界对话。

问问题的方式方法

“一个好问题,比答案还重要”。我们的社区非常活跃,大家都很热情地答复和解决问题。我们鼓励在社区里公开提出问题,一方面使大家都可以分享问题,找到解决方案,另一方面提问者也可以得到更多人的帮助。同时,希望提问者提出问题时,一次性描述详尽,把相关的操作步骤、系统环境、软件版本、出错提示以及希望得到的解决方案都提出来。

如果是通用性的问题,可以先搜索再提问,有利于培养独立解决问题的能力。希望提问者能向社区反馈更深层次的问题,以帮助社区快速优化。对很多典型问题,社区也积累了一些行之有效的解决方案,我们会整理和公布出来,以便查阅。

从新人到老鸟的路也许漫漫,如果能参考这篇小文的一些方法,可以少踩许多坑,多写一些应用。Enjoy blockchain,社区与你共同进步。

资源链接

学习逻辑操作符和 shell 扩展,本文是三篇 Bash 编程系列的第二篇。

Bash 是一种强大的编程语言,完美契合命令行和 shell 脚本。本系列(三篇文章,基于我的 三集 Linux 自学课程)讲解如何在 CLI 使用 Bash 编程。

第一篇文章 讲解了 Bash 的一些简单命令行操作,包括如何使用变量和控制操作符。第二篇文章探讨文件、字符串、数字等类型和各种各样在执行流中提供控制逻辑的的逻辑运算符,还有 Bash 中的各类 shell 扩展。本系列第三篇也是最后一篇文章,将会探索能重复执行操作的 forwhileuntil 循环。

逻辑操作符是程序中进行判断的根本要素,也是执行不同的语句组合的依据。有时这也被称为流控制。

逻辑操作符

Bash 中有大量的用于不同条件表达式的逻辑操作符。最基本的是 if 控制结构,它判断一个条件,如果条件为真,就执行一些程序语句。操作符共有三类:文件、数字和非数字操作符。如果条件为真,所有的操作符返回真值(0),如果条件为假,返回假值(1)。

这些比较操作符的函数语法是,一个操作符加一个或两个参数放在中括号内,后面跟一系列程序语句,如果条件为真,程序语句执行,可能会有另一个程序语句列表,该列表在条件为假时执行:

if [ arg1 operator arg2 ] ; then list
或
if [ arg1 operator arg2 ] ; then list ; else list ; fi

像例子中那样,在比较表达式中,空格不能省略。中括号的每部分,[],是跟 test 命令一样的传统的 Bash 符号:

if test arg1 operator arg2 ; then list

还有一个更新的语法能提供一点点便利,一些系统管理员比较喜欢用。这种格式对于不同版本的 Bash 和一些 shell 如 ksh(Korn shell)兼容性稍差。格式如下:

if [[ arg1 operator arg2 ]] ; then list

文件操作符

文件操作符是 Bash 中一系列强大的逻辑操作符。图表 1 列出了 20 多种不同的 Bash 处理文件的操作符。在我的脚本中使用频率很高。

操作符描述
-a filename如果文件存在,返回真值;文件可以为空也可以有内容,但是只要它存在,就返回真值
-b filename如果文件存在且是一个块设备,如 /dev/sda/dev/sda1,则返回真值
-c filename如果文件存在且是一个字符设备,如 /dev/TTY1,则返回真值
-d filename如果文件存在且是一个目录,返回真值
-e filename如果文件存在,返回真值;与上面的 -a 相同
-f filename如果文件存在且是一个一般文件,不是目录、设备文件或链接等的其他的文件,则返回 真值
-g filename如果文件存在且 SETGID 标记被设置在其上,返回真值
-h filename如果文件存在且是一个符号链接,则返回真值
-k filename如果文件存在且粘滞位已设置,则返回真值
-p filename如果文件存在且是一个命名的管道(FIFO),返回真值
-r filename如果文件存在且有可读权限(它的可读位被设置),返回真值
-s filename如果文件存在且大小大于 0,返回真值;如果一个文件存在但大小为 0,则返回假值
-t fd如果文件描述符 fd 被打开且被关联到一个终端设备上,返回真值
-u filename如果文件存在且它的 SETUID 位被设置,返回真值
-w filename如果文件存在且有可写权限,返回真值
-x filename如果文件存在且有可执行权限,返回真值
-G filename如果文件存在且文件的组 ID 与当前用户相同,返回真值
-L filename如果文件存在且是一个符号链接,返回真值(同 -h
-N filename如果文件存在且从文件上一次被读取后文件被修改过,返回真值
-O filename如果文件存在且你是文件的拥有者,返回真值
-S filename如果文件存在且文件是套接字,返回真值
file1 -ef file2如果文件 file1 和文件 file2 指向同一设备的同一 INODE 号,返回真值(即硬链接)
file1 -nt file2如果文件 file1file2 新(根据修改日期),或 file1 存在而 file2 不存在,返回真值
file1 -ot file2如果文件 file1file2 旧(根据修改日期),或 file1 不存在而 file2 存在

图表 1:Bash 文件操作符

以测试一个文件存在与否来举例:

[student@studentvm1 testdir]$ File="TestFile1" ; if [ -e $File ] ; then echo "The file $File exists." ; else echo "The file $File does not exist." ; fi
The file TestFile1 does not exist.
[student@studentvm1 testdir]$

创建一个用来测试的文件,命名为 TestFile1。目前它不需要包含任何数据:

[student@studentvm1 testdir]$ touch TestFile1

在这个简短的 CLI 程序中,修改 $File 变量的值相比于在多个地方修改表示文件名的字符串的值要容易:

[student@studentvm1 testdir]$ File="TestFile1" ; if [ -e $File ] ; then echo "The file $File exists." ; else echo "The file $File does not exist." ; fi
The file TestFile1 exists.
[student@studentvm1 testdir]$

现在,运行一个测试来判断一个文件是否存在且长度不为 0(表示它包含数据)。假设你想判断三种情况:

  1. 文件不存在;
  2. 文件存在且为空;
  3. 文件存在且包含数据。

因此,你需要一组更复杂的测试代码 — 为了测试所有的情况,使用 if-elif-else 结构中的 elif 语句:

[student@studentvm1 testdir]$ File="TestFile1" ; if [ -s $File ] ; then echo "$File exists and contains data." ; fi
[student@studentvm1 testdir]$

在这个情况中,文件存在但不包含任何数据。向文件添加一些数据再运行一次:

[student@studentvm1 testdir]$ File="TestFile1" ; echo "This is file $File" > $File ; if [ -s $File ] ; then echo "$File exists and contains data." ; fi
TestFile1 exists and contains data.
[student@studentvm1 testdir]$

这组语句能返回正常的结果,但是仅仅是在我们已知三种可能的情况下测试某种确切的条件。添加一段 else 语句,这样你就可以更精确地测试。把文件删掉,你就可以完整地测试这段新代码:

[student@studentvm1 testdir]$ File="TestFile1" ; rm $File ; if [ -s $File ] ; then echo "$File exists and contains data." ; else echo "$File does not exist or is empty." ; fi
TestFile1 does not exist or is empty.

现在创建一个空文件用来测试:

[student@studentvm1 testdir]$ File="TestFile1" ; touch $File ; if [ -s $File ] ; then echo "$File exists and contains data." ; else echo "$File does not exist or is empty." ; fi
TestFile1 does not exist or is empty.

向文件添加一些内容,然后再测试一次:

[student@studentvm1 testdir]$ File="TestFile1" ; echo "This is file $File" > $File ; if [ -s $File ] ; then echo "$File exists and contains data." ; else echo "$File does not exist or is empty." ; fi
TestFile1 exists and contains data.

现在加入 elif 语句来辨别是文件不存在还是文件为空:

[student@studentvm1 testdir]$ File="TestFile1" ; touch $File ; if [ -s $File ] ; then echo "$File exists and contains data." ; elif [ -e $File ] ; then echo "$File exists and is empty." ; else echo "$File does not exist." ; fi
TestFile1 exists and is empty.
[student@studentvm1 testdir]$ File="TestFile1" ; echo "This is $File" > $File ; if [ -s $File ] ; then echo "$File exists and contains data." ; elif [ -e $File ] ; then echo "$File exists and is empty." ; else echo "$File does not exist." ; fi
TestFile1 exists and contains data.
[student@studentvm1 testdir]$

现在你有一个可以测试这三种情况的 Bash CLI 程序,但是可能的情况是无限的。

如果你能像保存在文件中的脚本那样组织程序语句,那么即使对于更复杂的命令组合也会很容易看出它们的逻辑结构。图表 2 就是一个示例。 if-elif-else 结构中每一部分的程序语句的缩进让逻辑更变得清晰。

File="TestFile1"
echo "This is $File" > $File
if [ -s $File ]
   then
   echo "$File exists and contains data."
elif [ -e $File ]
   then
   echo "$File exists and is empty."
else
   echo "$File does not exist."
fi

图表 2: 像在脚本里一样重写书写命令行程序

对于大多数 CLI 程序来说,让这些复杂的命令变得有逻辑需要写很长的代码。虽然 CLI 可能是用 Linux 或 Bash 内置的命令,但是当 CLI 程序很长或很复杂时,创建一个保存在文件中的脚本将更有效,保存到文件中后,可以随时运行。

字符串比较操作符

字符串比较操作符使我们可以对字符串中的字符按字母顺序进行比较。图表 3 列出了仅有的几个字符串比较操作符。

操作符描述
-z string如果字符串的长度为 0 ,返回真值
-n string如果字符串的长度不为 0 ,返回真值
string1 == string2string1 = string2如果两个字符串相等,返回真值。处于遵从 POSIX 一致性,在测试命令中应使用一个等号 =。与命令 [[ 一起使用时,会进行如上描述的模式匹配(混合命令)。
string1 != string2两个字符串不相等,返回真值
string1 < string2如果对 string1string2 按字母顺序进行排序,string1 排在 string2 前面(即基于地区设定的对所有字母和特殊字符的排列顺序)
string1 > string2如果对 string1string2 按字母顺序进行排序,string1 排在 string2 后面

图表 3: Bash 字符串逻辑操作符

首先,检查字符串长度。比较表达式中 $MyVar 两边的双引号不能省略(你仍应该在目录 ~/testdir 下 )。

[student@studentvm1 testdir]$ MyVar="" ; if [ -z "" ] ; then echo "MyVar is zero length." ; else echo "MyVar contains data" ; fi
MyVar is zero length.
[student@studentvm1 testdir]$ MyVar="Random text" ; if [ -z "" ] ; then echo "MyVar is zero length." ; else echo "MyVar contains data" ; fi
MyVar is zero length.

你也可以这样做:

[student@studentvm1 testdir]$ MyVar="Random text" ; if [ -n "$MyVar" ] ; then echo "MyVar contains data." ; else echo "MyVar is zero length" ; fi
MyVar contains data.
[student@studentvm1 testdir]$ MyVar="" ; if [ -n "$MyVar" ] ; then echo "MyVar contains data." ; else echo "MyVar is zero length" ; fi
MyVar is zero length

有时候你需要知道一个字符串确切的长度。这虽然不是比较,但是也与比较相关。不幸的是,计算字符串的长度没有简单的方法。有很多种方法可以计算,但是我认为使用 expr(求值表达式)命令是相对最简单的一种。阅读 expr 的手册页可以了解更多相关知识。注意表达式中你检测的字符串或变量两边的引号不要省略。

[student@studentvm1 testdir]$ MyVar="" ; expr length "$MyVar"
0
[student@studentvm1 testdir]$ MyVar="How long is this?" ; expr length "$MyVar"
17
[student@studentvm1 testdir]$ expr length "We can also find the length of a literal string as well as a variable."
70

关于比较操作符,在我们的脚本中使用了大量的检测两个字符串是否相等(例如,两个字符串是否实际上是同一个字符串)的操作。我使用的是非 POSIX 版本的比较表达式:

[student@studentvm1 testdir]$ Var1="Hello World" ; Var2="Hello World" ; if [ "$Var1" == "$Var2" ] ; then echo "Var1 matches Var2" ; else echo "Var1 and Var2 do not match." ; fi
Var1 matches Var2
[student@studentvm1 testdir]$ Var1="Hello World" ; Var2="Hello world" ; if [ "$Var1" == "$Var2" ] ; then echo "Var1 matches Var2" ; else echo "Var1 and Var2 do not match." ; fi
Var1 and Var2 do not match.

在你自己的脚本中去试一下这些操作符。

数字比较操作符

数字操作符用于两个数字参数之间的比较。像其他类操作符一样,大部分都很容易理解。

操作符描述
arg1 -eq arg2如果 arg1 等于 arg2,返回真值
arg1 -ne arg2如果 arg1 不等于 arg2,返回真值
arg1 -lt arg2如果 arg1 小于 arg2,返回真值
arg1 -le arg2如果 arg1 小于或等于 arg2,返回真值
arg1 -gt arg2如果 arg1 大于 arg2,返回真值
arg1 -ge arg2如果 arg1 大于或等于 arg2,返回真值

图表 4: Bash 数字比较逻辑操作符

来看几个简单的例子。第一个示例设置变量 $X 的值为 1,然后检测 $X 是否等于 1。第二个示例中,$X 被设置为 0,所以比较表达式返回结果不为真值。

[student@studentvm1 testdir]$ X=1 ; if [ $X -eq 1 ] ; then echo "X equals 1" ; else echo "X does not equal 1" ; fi
X equals 1
[student@studentvm1 testdir]$ X=0 ; if [ $X -eq 1 ] ; then echo "X equals 1" ; else echo "X does not equal 1" ; fi
X does not equal 1
[student@studentvm1 testdir]$

自己来多尝试一下其他的。

杂项操作符

这些杂项操作符展示一个 shell 选项是否被设置,或一个 shell 变量是否有值,但是它不显示变量的值,只显示它是否有值。

操作符描述
-o optname如果一个 shell 选项 optname 是启用的(查看内建在 Bash 手册页中的 set -o 选项描述下面的选项列表),则返回真值
-v varname如果 shell 变量 varname 被设置了值(被赋予了值),则返回真值
-R varname如果一个 shell 变量 varname 被设置了值且是一个名字引用,则返回真值

图表 5: 杂项 Bash 逻辑操作符

自己来使用这些操作符实践下。

扩展

Bash 支持非常有用的几种类型的扩展和命令替换。根据 Bash 手册页,Bash 有七种扩展格式。本文只介绍其中五种:~ 扩展、算术扩展、路径名称扩展、大括号扩展和命令替换。

大括号扩展

大括号扩展是生成任意字符串的一种方法。(下面的例子是用特定模式的字符创建大量的文件。)大括号扩展可以用于产生任意字符串的列表,并把它们插入一个用静态字符串包围的特定位置或静态字符串的两端。这可能不太好想象,所以还是来实践一下。

首先,看一下大括号扩展的作用:

[student@studentvm1 testdir]$ echo {string1,string2,string3}
string1 string2 string3

看起来不是很有用,对吧?但是用其他方式使用它,再来看看:

[student@studentvm1 testdir]$ echo "Hello "{David,Jen,Rikki,Jason}.
Hello David. Hello Jen. Hello Rikki. Hello Jason.

这看起来貌似有点用了 — 我们可以少打很多字。现在试一下这个:

[student@studentvm1 testdir]$ echo b{ed,olt,ar}s
beds bolts bars

我可以继续举例,但是你应该已经理解了它的用处。

~ 扩展

资料显示,使用最多的扩展是波浪字符(~)扩展。当你在命令中使用它(如 cd ~/Documents)时,Bash shell 把这个快捷方式展开成用户的完整的家目录。

使用这个 Bash 程序观察 ~ 扩展的作用:

[student@studentvm1 testdir]$ echo ~
/home/student
[student@studentvm1 testdir]$ echo ~/Documents
/home/student/Documents
[student@studentvm1 testdir]$ Var1=~/Documents ; echo $Var1 ; cd $Var1
/home/student/Documents
[student@studentvm1 Documents]$

路径名称扩展

路径名称扩展是展开文件通配模式为匹配该模式的完整路径名称的另一种说法,匹配字符使用 ?*。文件通配指的是在大量操作中匹配文件名、路径和其他字符串时用特定的模式字符产生极大的灵活性。这些特定的模式字符允许匹配字符串中的一个、多个或特定字符。

  • ? — 匹配字符串中特定位置的一个任意字符
  • * — 匹配字符串中特定位置的 0 个或多个任意字符

这个扩展用于匹配路径名称。为了弄清它的用法,请确保 testdir 是当前工作目录(PWD),先执行基本的列出清单命令 ls(我家目录下的内容跟你的不一样)。

[student@studentvm1 testdir]$ ls
chapter6  cpuHog.dos    dmesg1.txt  Documents  Music       softlink1  testdir6    Videos
chapter7  cpuHog.Linux  dmesg2.txt  Downloads  Pictures    Templates  testdir
testdir  cpuHog.mac    dmesg3.txt  file005    Public      testdir    tmp
cpuHog     Desktop       dmesg.txt   link3      random.txt  testdir1   umask.test
[student@studentvm1 testdir]$

现在列出以 Dotestdir/Documentstestdir/Downloads 开头的目录:

Documents:
Directory01  file07  file15        test02  test10  test20      testfile13  TextFiles
Directory02  file08  file16        test03  test11  testfile01  testfile14
file01       file09  file17        test04  test12  testfile04  testfile15
file02       file10  file18        test05  test13  testfile05  testfile16
file03       file11  file19        test06  test14  testfile09  testfile17
file04       file12  file20        test07  test15  testfile10  testfile18
file05       file13  Student1.txt  test08  test16  testfile11  testfile19
file06       file14  test01        test09  test18  testfile12  testfile20

Downloads:
[student@studentvm1 testdir]$

然而,并没有得到你期望的结果。它列出了以 Do 开头的目录下的内容。使用 -d 选项,仅列出目录而不列出它们的内容。

[student@studentvm1 testdir]$ ls -d Do*
Documents  Downloads
[student@studentvm1 testdir]$

在两个例子中,Bash shell 都把 Do* 模式展开成了匹配该模式的目录名称。但是如果有文件也匹配这个模式,会发生什么?

[student@studentvm1 testdir]$ touch Downtown ; ls -d Do*
Documents  Downloads  Downtown
[student@studentvm1 testdir]$

因此所有匹配这个模式的文件也被展开成了完整名字。

命令替换

命令替换是让一个命令的标准输出数据流被当做参数传给另一个命令的扩展形式,例如,在一个循环中作为一系列被处理的项目。Bash 手册页显示:“命令替换可以让你用一个命令的输出替换为命令的名字。”这可能不太好理解。

命令替换有两种格式:command$(command)。在更早的格式中使用反引号(`),在命令中使用反斜杠(\)来保持它转义之前的文本含义。然而,当用在新版本的括号格式中时,反斜杠被当做一个特殊字符处理。也请注意带括号的格式打开个关闭命令语句都是用一个括号。

我经常在命令行程序和脚本中使用这种能力,一个命令的结果能被用作另一个命令的参数。

来看一个非常简单的示例,这个示例使用了这个扩展的两种格式(再一次提醒,确保 testdir 是当前工作目录):

[student@studentvm1 testdir]$ echo "Todays date is `date`"
Todays date is Sun Apr  7 14:42:46 EDT 2019
[student@studentvm1 testdir]$ echo "Todays date is $(date)"
Todays date is Sun Apr  7 14:42:59 EDT 2019
[student@studentvm1 testdir]$

-seq 工具用于一个数字序列:

[student@studentvm1 testdir]$ seq 5
1
2
3
4
5
[student@studentvm1 testdir]$ echo `seq 5`
1 2 3 4 5
[student@studentvm1 testdir]$

现在你可以做一些更有用处的操作,比如创建大量用于测试的空文件。

[student@studentvm1 testdir]$ for I in $(seq -w 5000) ; do touch file-$I ; done

seq 工具加上 -w 选项后,在生成的数字前面会用 0 补全,这样所有的结果都等宽,例如,忽略数字的值,它们的位数一样。这样在对它们按数字顺序进行排列时很容易。

seq -w 5000 语句生成了 1 到 5000 的数字序列。通过把命令替换用于 for 语句,for 语句就可以使用该数字序列来生成文件名的数字部分。

算术扩展

Bash 可以进行整型的数学计算,但是比较繁琐(你一会儿将看到)。数字扩展的语法是 $((arithmetic-expression)) ,分别用两个括号来打开和关闭表达式。算术扩展在 shell 程序或脚本中类似命令替换;表达式结算后的结果替换了表达式,用于 shell 后续的计算。

我们再用一个简单的用法来开始:

[student@studentvm1 testdir]$ echo $((1+1))
2
[student@studentvm1 testdir]$ Var1=5 ; Var2=7 ; Var3=$((Var1*Var2)) ; echo "Var 3 = $Var3"
Var 3 = 35

下面的除法结果是 0,因为表达式的结果是一个小于 1 的整型数字:

[student@studentvm1 testdir]$ Var1=5 ; Var2=7 ; Var3=$((Var1/Var2)) ; echo "Var 3 = $Var3"
Var 3 = 0

这是一个我经常在脚本或 CLI 程序中使用的一个简单的计算,用来查看在 Linux 主机中使用了多少虚拟内存。 free 不提供我需要的数据:

[student@studentvm1 testdir]$ RAM=`free | grep ^Mem | awk '{print $2}'` ; Swap=`free | grep ^Swap | awk '{print $2}'` ; echo "RAM = $RAM and Swap = $Swap" ; echo "Total Virtual memory is $((RAM+Swap))" ;
RAM = 4037080 and Swap = 6291452
Total Virtual memory is 10328532

我使用 ` 字符来划定用作命令替换的界限。

我用 Bash 算术扩展的场景主要是用脚本检查系统资源用量后基于返回的结果选择一个程序运行的路径。

总结

本文是 Bash 编程语言系列的第二篇,探讨了 Bash 中文件、字符串、数字和各种提供流程控制逻辑的逻辑操作符还有不同种类的 shell 扩展。


via: https://opensource.com/article/19/10/programming-bash-logical-operators-shell-expansions

作者:David Both 选题:lujun9972 译者:lxbwolf 校对:wxy

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

Awk 是一个强大的工具,可以执行某些可能由其它常见实用程序(包括 sort)来完成的任务。

Awk 是个普遍存在的 Unix 命令,用于扫描和处理包含可预测模式的文本。但是,由于它具有函数功能,因此也可以合理地称之为编程语言。

令人困惑的是,有不止一个 awk。(或者,如果你认为只有一个,那么其它几个就是克隆。)有 awk(由Aho、Weinberger 和 Kernighan 编写的原始程序),然后有 nawkmawk 和 GNU 版本的 gawk。GNU 版本的 awk 是该实用程序的一个高度可移植的自由软件版本,具有几个独特的功能,因此本文是关于 GNU awk 的。

虽然它的正式名称是 gawk,但在 GNU+Linux 系统上,它的别名是 awk,并用作该命令的默认版本。 在其他没有带有 GNU awk 的系统上,你必须先安装它并将其称为 gawk,而不是 awk。本文互换使用术语 awkgawk

awk 既是命令语言又是编程语言,这使其成为一个强大的工具,可以处理原本留给 sortcutuniq 和其他常见实用程序的任务。幸运的是,开源中有很多冗余空间,因此,如果你面临是否使用 awk 的问题,答案可能是肯定的“随便”。

awk 的灵活之美在于,如果你已经确定使用 awk 来完成一项任务,那么无论接下来发生什么,你都可以继续使用 awk。这包括对数据排序而不是按交付给你的顺序的永恒需求。

样本数据集

在探索 awk 的排序方法之前,请生成要使用的样本数据集。保持简单,这样你就不会为极端情况和意想不到的复杂性所困扰。这是本文使用的样本集:

Aptenodytes;forsteri;Miller,JF;1778;Emperor
Pygoscelis;papua;Wagler;1832;Gentoo
Eudyptula;minor;Bonaparte;1867;Little Blue
Spheniscus;demersus;Brisson;1760;African
Megadyptes;antipodes;Milne-Edwards;1880;Yellow-eyed
Eudyptes;chrysocome;Viellot;1816;Sothern Rockhopper
Torvaldis;linux;Ewing,L;1996;Tux

这是一个很小的数据集,但它提供了多种数据类型:

  • 属名和种名,彼此相关但又是分开的
  • 姓,有时是以逗号开头的首字母缩写
  • 代表日期的整数
  • 任意术语
  • 所有字段均以分号分隔

根据你的教育背景,你可能会认为这是二维数组或表格,或者只是行分隔的数据集合。你如何看待它只是你的问题,而 awk 只认识文本。由你决定告诉 awk 你想如何解析它。

只想排序

如果你只想按特定的可定义字段(例如电子表格中的“单元格”)对文本数据集进行排序,则可以使用 sort 命令

字段和记录

无论输入的格式如何,都必须在其中找到模式才可以专注于对你重要的数据部分。在此示例中,数据由两个因素定界:行和字段。每行都代表一个新的记录,就如你在电子表格或数据库转储中看到的一样。在每一行中,都有用分号(;)分隔的不同的字段(将其视为电子表格中的单元格)。

awk 一次只处理一条记录,因此,当你在构造发给 awk 的这指令时,你可以只关注一行记录。写下你想对一行数据执行的操作,然后在下一行进行测试(无论是心理上还是用 awk 进行测试),然后再进行其它的一些测试。最后,你要对你的 awk 脚本要处理的数据做好假设,以便可以按你要的数据结构提供给你数据。

在这个例子中,很容易看到每个字段都用分号隔开。为简单起见,假设你要按每行的第一字段对列表进行排序。

在进行排序之前,你必须能够让 awk 只关注在每行的第一个字段上,因此这是第一步。终端中 awk 命令的语法为 awk,后跟相关选项,最后是要处理的数据文件。

$ awk --field-separator=";" '{print $1;}' penguins.list
Aptenodytes
Pygoscelis
Eudyptula
Spheniscus
Megadyptes
Eudyptes
Torvaldis

因为字段分隔符是对 Bash shell 具有特殊含义的字符,所以必须将分号括在引号中或在其前面加上反斜杠。此命令仅用于证明你可以专注于特定字段。你可以使用另一个字段的编号尝试相同的命令,以查看数据的另一个“列”的内容:

$ awk --field-separator=";" '{print $3;}' penguins.list
Miller,JF
Wagler
Bonaparte
Brisson
Milne-Edwards
Viellot
Ewing,L

我们尚未进行任何排序,但这是良好的基础。

脚本编程

awk 不仅仅是命令,它是一种具有索引、数组和函数的编程语言。这很重要,因为这意味着你可以获取要排序的字段列表,将列表存储在内存中,进行处理,然后打印结果数据。对于诸如此类的一系列复杂操作,在文本文件中进行操作会更容易,因此请创建一个名为 sort.awk 的新文件并输入以下文本:

#!/bin/gawk -f

BEGIN {
        FS=";";
}

这会将该文件建立为 awk 脚本,该脚本中包含执行的行。

BEGIN 语句是 awk 提供的特殊设置功能,用于只需要执行一次的任务。定义内置变量 FS,它代表 字段分隔符 field separator ,并且与你在 awk 命令中使用 --field-separator 设置的值相同,它只需执行一次,因此它包含在 BEGIN 语句中。

awk 中的数组

你已经知道如何通过使用 $ 符号和字段编号来收集特定字段的值,但是在这种情况下,你需要将其存储在数组中而不是将其打印到终端。这是通过 awk 数组完成的。awk 数组的重要之处在于它包含键和值。 想象一下有关本文的内容;它看起来像这样:author:"seth",title:"How to sort with awk",length:1200。诸如作者、标题和长度之类的元素是键,跟着的内容为值。

在排序的上下文中这样做的好处是,你可以将任何字段分配为键,将任何记录分配为值,然后使用内置的 awk 函数 asorti()(按索引排序)按键进行排序。现在,随便假设你想按第二个字段排序。

没有被特殊关键字 BEGINEND 引起来的 awk 语句是在每个记录都要执行的循环。这是脚本的一部分,该脚本扫描数据中的模式并进行相应的处理。每次 awk 将注意力转移到一条记录上时,都会执行 {} 中的语句(除非以 BEGINEND 开头)。

要将键和值添加到数组,请创建一个包含数组的变量(在本示例脚本中,我将其称为 ARRAY,虽然不是很原汁原味,但很清楚),然后在方括号中分配给它键,用等号(=)连接值。

{   # dump each field into an array
    ARRAY[$2] = $R;
}

在此语句中,第二个字段的内容($2)用作关键字,而当前记录($R)用作值。

asorti() 函数

除了数组之外,awk 还具有一些基本函数,你可以将它们用作常见任务的快速简便的解决方案。GNU awk中引入的函数之一 asorti() 提供了按键(索引)或值对数组进行排序的功能。

你只能在对数组进行填充后对其进行排序,这意味着此操作不能对每个新记录都触发,而只能在脚本的最后阶段进行。为此,awk 提供了特殊的 END 关键字。与 BEGIN 相反,END 语句仅在扫描了所有记录之后才触发一次。

将这些添加到你的脚本:

END {
    asorti(ARRAY,SARRAY);
    # get length
    j = length(SARRAY);
   
    for (i = 1; i &lt;= j; i++) {
        printf("%s %s\n", SARRAY[i],ARRAY[SARRAY[i]])
    }
}

asorti() 函数获取 ARRAY 的内容,按索引对其进行排序,然后将结果放入名为 SARRAY 的新数组(我在本文中发明的任意名称,表示“排序的 ARRAY”)。

接下来,将变量 j(另一个任意名称)分配给 length() 函数的结果,该函数计算 SARRAY 中的项数。

最后,使用 for 循环使用 printf() 函数遍历 SARRAY 中的每一项,以打印每个键,然后在 ARRAY 中打印该键的相应值。

运行该脚本

要运行你的 awk 脚本,先使其可执行:

$ chmod +x sorter.awk

然后针对 penguin.list 示例数据运行它:

$ ./sorter.awk penguins.list
antipodes Megadyptes;antipodes;Milne-Edwards;1880;Yellow-eyed
chrysocome Eudyptes;chrysocome;Viellot;1816;Sothern Rockhopper
demersus Spheniscus;demersus;Brisson;1760;African
forsteri Aptenodytes;forsteri;Miller,JF;1778;Emperor
linux Torvaldis;linux;Ewing,L;1996;Tux
minor Eudyptula;minor;Bonaparte;1867;Little Blue
papua Pygoscelis;papua;Wagler;1832;Gentoo

如你所见,数据按第二个字段排序。

这有点限制。最好可以在运行时灵活选择要用作排序键的字段,以便可以在任何数据集上使用此脚本并获得有意义的结果。

添加命令选项

你可以通过在脚本中使用字面值 var 将命令变量添加到 awk 脚本中。更改脚本,以使迭代子句在创建数组时使用 var

{ # dump each field into an array
    ARRAY[$var] = $R;
}

尝试运行该脚本,以便在执行脚本时使用 -v var 选项将其按第三字段排序:

$ ./sorter.awk -v var=3 penguins.list
Bonaparte Eudyptula;minor;Bonaparte;1867;Little Blue
Brisson Spheniscus;demersus;Brisson;1760;African
Ewing,L Torvaldis;linux;Ewing,L;1996;Tux
Miller,JF Aptenodytes;forsteri;Miller,JF;1778;Emperor
Milne-Edwards Megadyptes;antipodes;Milne-Edwards;1880;Yellow-eyed
Viellot Eudyptes;chrysocome;Viellot;1816;Sothern Rockhopper
Wagler Pygoscelis;papua;Wagler;1832;Gentoo

修正

本文演示了如何在纯 GNU awk 中对数据进行排序。你可以对脚本进行改进,以便对你有用,花一些时间在gawk 的手册页上研究 awk 函数并自定义脚本以获得更好的输出。

这是到目前为止的完整脚本:

#!/usr/bin/awk -f
# GPLv3 appears here
# usage: ./sorter.awk -v var=NUM FILE

BEGIN { FS=";"; }

{ # dump each field into an array
    ARRAY[$var] = $R;
}

END {
    asorti(ARRAY,SARRAY);
    # get length
    j = length(SARRAY);
   
    for (i = 1; i &lt;= j; i++) {
        printf("%s %s\n", SARRAY[i],ARRAY[SARRAY[i]])
    }
}

via: https://opensource.com/article/19/11/how-sort-awk

作者:Seth Kenlon 选题:lujun9972 译者:wxy 校对:wxy

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