2018年4月

众所周知,对硬盘分区是 Linux 管理员一项最重要的管理任务之一,他们不能不知道这个。

在最糟糕的时候,你至少每周会收到一次依赖小组的请求,而在更大的环境里会更加频繁 。

你可能会问为什么我们要用 parted 而不是 fdisk? 它们有什么区别?好问题,我会告诉你这两者的区别。

  • parted 支持用户在大于 2TB 的硬盘上创建硬盘分区, 但 fdisk 命令不支持
  • 对比 fdisk 来说,parted 是一个更高级的工具
  • 支持更多的分区表类型,包括 GPT (LCTT 译注:全局唯一标识分区表)
  • 它允许用户调整分区大小, 但当缩减分区空间的时候,它没有如我意料的工作,多数情况下我会得到错误消息。所以我会建议用户不要用 parted 来缩减分区大小。

什么是 parted

parted 是一个操作硬盘分区的程序。它支持多种分区表类型,包括 MS-DOS 和 GPT。

它允许用户创建、删除、调整、缩减、移动和复制分区,以及重新组织硬盘的使用,复制数据到新的硬盘上。gpartedparted 的图形界面前端。

怎样安装 parted

大部分发行版已经预安装了 parted。如果没有,用下列命令来安装 parted

对于 Debian/Ubuntu 用户, 使用 APT-GET 命令 或者 APT 命令 来安装 parted

$ sudo apt install parted

对于 RHEL/CentOS 用户,用 YUM 命令 来安装 parted

$ sudo yum install parted

对于 Fedora 用户,用 DNF 命令 来安装 parted

$ sudo dnf install parted

对于 Arch Linux 用户,用 Pacman 命令来安装 parted

$ sudo pacman -S parted

对于 openSUSE 用户, 用 Zypper 命令来安装 parted

$ sudo zypper in parted

怎样启动 parted

下面的 parted 命令会自动选择 /dev/sda ,因为这是系统的第一个硬盘。

$ sudo parted
GNU parted 3.2
Using /dev/sda
Welcome to GNU parted! Type 'help' to view a list of commands.
(parted)

同时我们也可以用下面的命令来重新选择对应的的硬盘。

(parted) select /dev/sdb
Using /dev/sdb
(parted)

如果你想选择特定的硬盘, 用下列的格式来输入命令。 这次 ,我们将选择 /dev/sdb

$ sudo parted [Device Name]
$ sudo parted /dev/sdb
GNU parted 3.2
Using /dev/sdb
Welcome to GNU parted! Type 'help' to view a list of commands.
(parted)

怎样用 parted 列出所有可用的硬盘

如果你不知道你的电脑上有什么硬盘,只需要运行下列命令,该命令会显示所有可用硬盘的名字,以及其它的有用信息比如储存空间、型号、扇区大小、硬盘标志以及分区信息。

$ sudo parted -l
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sda: 32.2GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
 1 1049kB 32.2GB 32.2GB primary ext4 boot


Error: /dev/sdb: unrecognised disk label
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 53.7GB
Sector size (logical/physical): 512B/512B
Partition Table: unknown
Disk Flags:

上面的错误信息清晰地显示出硬盘 /dev/sdb 没有有效的 磁盘标签 disk label 。 它不会自动得到磁盘标签,所以, 我们便要自己设置硬盘标签。

怎样用 parted 创建硬盘分区

parted 允许用户创建主分区或者拓展分区。创建这两种类型的分区的步骤还是一样,但请确保你已经指定了需要的分区类型,比如 primary (主分区)或者 extended (扩展分区)。

为了演示这项操作 ,我们安装了一个新的 50 GB 的硬盘到到电脑上,挂载在 /dev/sdb 上。

有两种方法创建分区,第一种是更详细的方法,另一种只是一个命令。 在下面的例子中,我们将用更详细的方法添加一个主分区。提醒一下, 我们应该先设置磁盘标签,因为它不会自动设置任何标签。

在下面的例子中,我们将要创建一个 10 GB 的分区

$ sudo parted /dev/sdb
GNU parted 3.2
Using /dev/sdb
Welcome to GNU parted! Type 'help' to view a list of commands.
(parted) mklabel msdos
(parted) unit GB
(parted) mkpart
Partition type? primary/extended? primary
File system type? [ext2]? ext4
Start? 0.00GB
End? 10.00GB
(parted) print
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 53.7GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
 1 0.00GB 10.0GB 10.0GB primary ext4 lba

(parted) quit
Information: You may need to update /etc/fstab.

同时,我们也可以使用单条 parted 命令

在下面的例子中,我们将在硬盘上创建一个 10 GB 的分区。

$ sudo parted [Disk Name] [mkpart] [Partition Type] [Filesystem Type] [Partition Start Size] [Partition End Size]
$ sudo parted /dev/sdb mkpart primary ext4 10.0GB 20.0GB
Information: You may need to update /etc/fstab.

怎样使用所有剩余空间创建分区

你已经创建了除了 /home 之外等所有要求的分区,而且你想要用硬盘上所有剩余的空间来创建 /home 分区,要怎样做?可以使用下面的命令来创建分区。

下面的命令创建了一个 33.7 GB 的分区,从 20 GB 开始到 53 GB 结束。 100% 使用率允许用户用硬盘上所有剩余的空余空间。

$ sudo parted [Disk Name] [mkpart] [Partition Type] [Filesystem Type] [Partition Start Size] [Partition End Size]

$ sudo parted /dev/sdb mkpart primary ext4 20.0GB 100%
Information: You may need to update /etc/fstab.

怎样用 parted 列出所有的分区

你也许注意到了,我们已经在上述步骤中创建了三个分区,如果你想要列出所有在硬盘上可用的分区,可以使用 print 命令。

$ sudo parted /dev/sdb print
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 53.7GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
 1 1049kB 10.0GB 9999MB primary ext4
 2 10.0GB 20.0GB 9999MB primary ext4
 3 20.0GB 53.7GB 33.7GB primary ext4

怎样用 mkfs 格式化分区

用户可以用 mkfs 命令格式化分区。下面的步骤会用 mkfs 来格式化分区。

$ sudo mkfs.ext4 /dev/sdb1
mke2fs 1.43.4 (31-Jan-2017)
Creating filesystem with 2621440 4k blocks and 656640 inodes
Filesystem UUID: 415cf467-634c-4403-8c9f-47526bbaa381
Superblock backups stored on blocks:
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

同样的。

$ sudo mkfs.ext4 /dev/sdb2
$ sudo mkfs.ext4 /dev/sdb3

创建必要的文件夹然后将这些分区挂载在上面。

$ sudo mkdir /par1 /par2 /par3
$ sudo mount /dev/sdb1 /par1
$ sudo mount /dev/sdb2 /par2
$ sudo mount /dev/sdb3 /par3

运行下列命令来检查是否成功挂载上新创建的分区。

$ df -h /dev/sdb[1-3]
Filesystem Size Used Avail Use% Mounted on
/dev/sdb1 9.2G 37M 8.6G 1% /par1
/dev/sdb2 9.2G 37M 8.6G 1% /par2
/dev/sdb3 31G 49M 30G 1% /par3

怎样检查硬盘空闲空间

运行下列命令来检查硬盘上的空闲空间,这块硬盘上有 25.7 GB 的空闲空间。

$ sudo parted /dev/sdb print free
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 53.7GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
 32.3kB 1049kB 1016kB Free Space
 1 1049kB 10.0GB 9999MB primary ext4
 2 10.0GB 20.0GB 9999MB primary ext4
 3 20.0GB 28.0GB 8001MB primary ext4
 28.0GB 53.7GB 25.7GB Free Space

怎样使用 parted 命令来重新调整分区大小

parted 允许用户重新调整分区大小。不过我已在文章的开头说了,不要缩小分区大小,不然会有许多错误。

运行下列命令来检查硬盘分区以及所有可用空间。 可以看到硬盘上有 25.7GB 的可用空间。

$ sudo parted /dev/sdb print free
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 53.7GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
 32.3kB 1049kB 1016kB Free Space
 1 1049kB 10.0GB 9999MB primary ext4
 2 10.0GB 20.0GB 9999MB primary ext4
 3 20.0GB 28.0GB 8001MB primary ext4
 28.0GB 53.7GB 25.7GB Free Space

运行下列命令来重新调整分区大小。 我们将要重新调整(增加)分区 3 的结束位置,从 28GB33GB

$ sudo parted [Disk Name] [resizepart] [Partition Number] [Partition New End Size]

$ sudo parted /dev/sdb resizepart 3 33.0GB
Information: You may need to update /etc/fstab.

运行下列命令来确认分区是否已经扩容。可以看到,分区 3 已经从 8GB 增加到 13GB

$ sudo parted /dev/sdb print
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 53.7GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
 1 1049kB 10.0GB 9999MB primary ext4
 2 10.0GB 20.0GB 9999MB primary ext4
 3 20.0GB 33.0GB 13.0GB primary ext4

重新调整文件系统大小。

$ sudo resize2fs /dev/sdb3
resize2fs 1.43.4 (31-Jan-2017)
Resizing the filesystem on /dev/sdb3 to 3173952 (4k) blocks.
The filesystem on /dev/sdb3 is now 3173952 (4k) blocks long.

最后,确认分区是否已经扩容。

$ df -h /dev/sdb[1-3]
Filesystem Size Used Avail Use% Mounted on
/dev/sdb1 9.2G 5.1G 3.6G 59% /par1
/dev/sdb2 9.2G 2.1G 6.6G 24% /par2
/dev/sdb3 12G 1.1G 11G 10% /par3

怎样用 parted 删除分区

我们用 rm 命令方便地删除未使用的分区(如果该分区不会再被用到了)。下列步骤中,我们将会删除分区 3 (/dev/sdb3)。

$ sudo parted [Disk Name] [rm] [Partition Number]

$ sudo parted /dev/sdb rm 3
Warning: Partition /dev/sdb3 is being used. Are you sure you want to continue?
Yes/No? Yes
Error: Partition(s) 3 on /dev/sdb have been written, but we have been unable to inform the kernel of the change, probably because it/they are in use. As a result, the old partition(s) will remain in use.
You should reboot now before making further changes.
Ignore/Cancel? Ignore
Information: You may need to update /etc/fstab.

我们也可以用下列的命令检查。可以看到,分区 3 已经被成功移除。

$ sudo parted /dev/sdb print
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 53.7GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
 1 1049kB 10.0GB 9999MB primary ext4
 2 10.0GB 20.0GB 9999MB primary ext4

怎样用 parted 命令设置/更改分区标志

我们可以用下列的命令来轻易更改分区的标志。 我们将对 /dev/sdb2 设置 lvm 标志。

$ sudo parted [Disk Name] [set] [Partition Number] [Flags Name] [Flag On/Off]

$ sudo parted /dev/sdb set 2 lvm on
Information: You may need to update /etc/fstab.

我们可以列出分区来验证这次的更改。

$ sudo parted /dev/sdb print
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 53.7GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number Start End Size Type File system Flags
 1 1049kB 10.0GB 9999MB primary ext4
 2 10.0GB 20.0GB 9999MB primary ext4 lvm

如果你想知道可用的标志,只需要用如下的命令。

$ (parted) help set
 set NUMBER FLAG STATE change the FLAG on partition NUMBER

    NUMBER is the partition number used by Linux. On MS-DOS disk labels, the primary partitions number from 1 to 4, logical partitions from 5 onwards.
 FLAG is one of: boot, root, swap, hidden, raid, lvm, lba, hp-service, palo, prep, msftres, bios_grub, atvrecv, diag, legacy_boot, msftdata, irst, esp
 STATE is one of: on, off

如果你想知道 parted 的其它可用命令, 只需要去到 help 页面。

$ sudo parted
GNU parted 3.2
Using /dev/sda
Welcome to GNU parted! Type 'help' to view a list of commands.
(parted) help
 align-check TYPE N check partition N for TYPE(min|opt) alignment
 help [COMMAND] print general help, or help on COMMAND
 mklabel,mktable LABEL-TYPE create a new disklabel (partition table)
 mkpart PART-TYPE [FS-TYPE] START END make a partition
 name NUMBER NAME name partition NUMBER as NAME
 print [devices|free|list,all|NUMBER] display the partition table, available devices, free space, all found partitions, or a particular partition
 quit exit program
 rescue START END rescue a lost partition near START and END
 resizepart NUMBER END resize partition NUMBER
 rm NUMBER delete partition NUMBER
 select DEVICE choose the device to edit
 disk_set FLAG STATE change the FLAG on selected device
 disk_toggle [FLAG] toggle the state of FLAG on selected device
 set NUMBER FLAG STATE change the FLAG on partition NUMBER
 toggle [NUMBER [FLAG]] toggle the state of FLAG on partition NUMBER
 unit UNIT set the default unit to UNIT
 version display the version number and copyright information of GNU parted
(parted) quit

via: https://www.2daygeek.com/how-to-manage-disk-partitions-using-parted-command/

作者:Magesh Maruthamuthu 译者:zyk2290 校对:wxy

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

这篇文章最初发表在 Kevin Monroe 的博客 上。

监视日志和指标状态是集群管理员的重点工作。它的好处很明显:指标能帮你设置一个合理的性能目标,而日志分析可以发现影响你工作负载的问题。然而,困难的是如何找到一个与大量运行的应用程序一起工作的监视解决方案。

在本文中,我将使用 Graylog (用于日志)和 Prometheus (用于指标)去打造一个 Kubernetes 集群的监视解决方案。当然了,这不仅是将三个东西连接起来那么简单,实现上,最终结果看起来应该如题图所示:

正如你所了解的,Kubernetes 不是一件东西 —— 它由主控节点、工作节点、网络连接、配置管理等等组成。同样,Graylog 是一个配角(apache2、mongodb、等等),Prometheus 也一样(telegraf、grafana 等等)。在部署中连接这些点看起来似乎有些让人恐惧,但是使用合适的工具将不会那么困难。

我将使用 conjure-upCanonical 版本的 Kubernetes (CDK) 去探索 Kubernetes。我发现 conjure-up 接口对部署大型软件很有帮助,但是我知道一些人可能不喜欢 GUI、TUI 以及其它用户界面。对于这些人,我将用命令行再去部署一遍。

在开始之前需要注意的一点是,Graylog 和 Prometheus 是部署在 Kubernetes 外侧而不是集群上。像 Kubernetes 仪表盘和 Heapster 是运行的集群的非常好的信息来源,但是我的目标是为日志/指标提供一个分析机制,而不管集群运行与否。

开始探索

如果你的系统上没有 conjure-up,首先要做的第一件事情是,请先安装它,在 Linux 上,这很简单:

sudo snap install conjure-up --classic

对于 macOS 用户也提供了 brew 包:

brew install conjure-up

你需要最新的 2.5.2 版,它的好处是添加了 CDK spell,因此,如果你的系统上已经安装了旧的版本,请使用 sudo snap refresh conjure-up 或者 brew update && brew upgrade conjure-up 去更新它。

安装完成后,运行它:

conjure-up

你将发现有一个 spell 列表。选择 CDK 然后按下回车。

这个时候,你将看到 CDK spell 可用的附加组件。我们感兴趣的是 Graylog 和 Prometheus,因此选择这两个,然后点击 “Continue”。

它将引导你选择各种云,以决定你的集群部署的地方。之后,你将看到一些部署的后续步骤,接下来是回顾屏幕,让你再次确认部署内容:

除了典型的 K8s 相关的应用程序(etcd、flannel、load-balancer、master 以及 workers)之外,你将看到我们选择的日志和指标相关的额外应用程序。

Graylog 栈包含如下:

  • apache2:graylog web 界面的反向代理
  • elasticsearch:日志使用的文档数据库
  • filebeat:从 K8s master/workers 转发日志到 graylog
  • graylog:为日志收集器提供一个 api,以及提供一个日志分析界面
  • mongodb:保存 graylog 元数据的数据库

Prometheus 栈包含如下:

  • grafana:指标相关的仪表板的 web 界面
  • prometheus:指标收集器以及时序数据库
  • telegraf:发送主机的指标到 prometheus 中

你可以在回顾屏幕上微调部署,但是默认组件是必选 的。点击 “Deploy all Remaining Applications” 继续。

部署工作将花费一些时间,它将部署你的机器和配置你的云。完成后,conjure-up 将展示一个摘要屏幕,它包含一些链接,你可以用你的终端去浏览各种感兴趣的内容:

浏览日志

现在,Graylog 已经部署和配置完成,我们可以看一下采集到的一些数据。默认情况下,filebeat 应用程序将从 Kubernetes 的 master 和 worker 中转发系统日志( /var/log/*.log )和容器日志(/var/log/containers/*.log)到 graylog 中。

记住如下的 apache2 的地址和 graylog 的 admin 密码:

juju status --format yaml apache2/0 | grep public-address
 public-address: <your-apache2-ip>
juju run-action --wait graylog/0 show-admin-password
 admin-password: <your-graylog-password>

在浏览器中输入 http://<your-apache2-ip> ,然后以管理员用户名(admin)和密码()登入。

注意: 如果这个界面不可用,请等待大约 5 分钟时间,以便于配置的反向代理生效。

登入后,顶部的 “Sources” 选项卡可以看到从 K8s 的 master 和 workers 中收集日志的概述:

通过点击 “System / Inputs” 选项卡深入这些日志,选择 “Show received messages” 查看 filebeat 的输入:

在这里,你可以应用各种过滤或者设置 Graylog 仪表板去帮助识别大多数比较重要的事件。查看 Graylog Dashboard 文档,可以了解如何定制你的视图的详细资料。

浏览指标

我们的部署通过 grafana 仪表板提供了两种类型的指标:系统指标,包括像 K8s master 和 worker 的 CPU /内存/磁盘使用情况,以及集群指标,包括像从 K8s cAdvisor 端点上收集的容器级指标。

记住如下的 grafana 的地址和 admin 密码:

juju status --format yaml grafana/0 | grep public-address
 public-address: <your-grafana-ip>
juju run-action --wait grafana/0 get-admin-password
 password: <your-grafana-password>

在浏览器中输入 http://<your-grafana-ip>:3000,输入管理员用户(admin)和密码()登入。成功登入后,点击 “Home” 下拉框,选取 “Kubernetes Metrics (via Prometheus)” 去查看集群指标仪表板:

我们也可以通过下拉框切换到 “Node Metrics (via Telegraf) ” 去查看 K8s 主机的系统指标。

另一种方法

正如在文章开始的介绍中提到的,我喜欢用 conjure-up 的向导去完成像 Kubernetes 这种复杂软件的部署。现在,我们来看一下 conjure-up 的另一种方法,你可能希望去看到实现相同结果的一些命令行的方法。还有其它的可能已经部署了前面的 CDK,并想去扩展使用上述的 Graylog/Prometheus 组件。不管什么原因你既然看到这了,既来之则安之,继续向下看吧。

支持 conjure-up 的工具是 Juju。CDK spell 所做的一切,都可以使用 juju 命令行来完成。我们来看一下,如何一步步完成这些工作。

从 Scratch 中启动

如果你使用的是 Linux,安装 Juju 很简单,命令如下:

sudo snap install juju --classic

对于 macOS,Juju 也可以从 brew 中安装:

brew install juju

现在为你选择的云配置一个控制器。你或许会被提示请求一个凭据(用户名密码):

juju bootstrap

我们接下来需要基于 CDK 捆绑部署:

juju deploy canonical-kubernetes

从 CDK 开始

使用我们部署的 Kubernetes 集群,我们需要去添加 Graylog 和 Prometheus 所需要的全部应用程序:

## deploy graylog-related applications
juju deploy xenial/apache2
juju deploy xenial/elasticsearch
juju deploy xenial/filebeat
juju deploy xenial/graylog
juju deploy xenial/mongodb
## deploy prometheus-related applications
juju deploy xenial/grafana
juju deploy xenial/prometheus
juju deploy xenial/telegraf

现在软件已经部署完毕,将它们连接到一起,以便于它们之间可以相互通讯:

## relate graylog applications
juju relate apache2:reverseproxy graylog:website
juju relate graylog:elasticsearch elasticsearch:client
juju relate graylog:mongodb mongodb:database
juju relate filebeat:beats-host kubernetes-master:juju-info
juju relate filebeat:beats-host kubernetes-worker:jujuu-info
## relate prometheus applications
juju relate prometheus:grafana-source grafana:grafana-source
juju relate telegraf:prometheus-client prometheus:target
juju relate kubernetes-master:juju-info telegraf:juju-info
juju relate kubernetes-worker:juju-info telegraf:juju-info

这个时候,所有的应用程序已经可以相互之间进行通讯了,但是我们还需要多做一点配置(比如,配置 apache2 反向代理、告诉 prometheus 如何从 K8s 中取数、导入到 grafana 仪表板等等):

## configure graylog applications
juju config apache2 enable_modules="headers proxy_html proxy_http"
juju config apache2 vhost_http_template="$(base64 <vhost-tmpl>)"
juju config elasticsearch firewall_enabled="false"
juju config filebeat \
 logpath="/var/log/*.log /var/log/containers/*.log"
juju config filebeat logstash_hosts="<graylog-ip>:5044"
juju config graylog elasticsearch_cluster_name="<es-cluster>"
## configure prometheus applications
juju config prometheus scrape-jobs="<scraper-yaml>"
juju run-action --wait grafana/0 import-dashboard \
 dashboard="$(base64 <dashboard-json>)"

以上的步骤需要根据你的部署来指定一些值。你可以用与 conjure-up 相同的方法得到这些:

  • <vhost-tmpl>: 从 github 获取我们的示例 模板
  • <graylog-ip>juju run --unit graylog/0 'unit-get private-address'
  • <es-cluster>juju config elasticsearch cluster-name
  • <scraper-yaml>: 从 github 获取我们的示例 scraper[K8S_PASSWORD][20][K8S_API_ENDPOINT][21] substitute 的正确值
  • <dashboard-json>: 从 github 获取我们的 主机k8s 仪表板

最后,发布 apache2 和 grafana 应用程序,以便于可以通过它们的 web 界面访问:

## expose relevant endpoints
juju expose apache2
juju expose grafana

现在我们已经完成了所有的部署、配置、和发布工作,你可以使用与上面的浏览日志浏览指标部分相同的方法去查看它们。

总结

我的目标是向你展示如何去部署一个 Kubernetes 集群,很方便地去监视它的日志和指标。无论你是喜欢向导的方式还是命令行的方式,我希望你清楚地看到部署一个监视系统并不复杂。关键是要搞清楚所有部分是如何工作的,并将它们连接到一起工作,通过断开/修复/重复的方式,直到它们每一个都能正常工作。

这里有一些像 conjure-up 和 Juju 一样非常好的工具。充分发挥这个生态系统贡献者的专长让管理大型软件变得更容易。从一套可靠的应用程序开始,按需定制,然后投入到工作中!

大胆去尝试吧,然后告诉我你用的如何。你可以在 Freenode IRC 的 #conjure-up#juju 中找到像我这样的爱好者。感谢阅读!

关于作者

Kevin 在 2014 年加入 Canonical 公司,他专注于复杂软件建模。他在 Juju 大型软件团队中找到了自己的位置,他的任务是将大数据和机器学习应用程序转化成可重复的(可靠的)解决方案。


via: https://insights.ubuntu.com/2018/01/16/monitor-your-kubernetes-cluster/

作者:Kevin Monroe 译者:qhwdw 校对:wxy

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

这三个问题可以帮你避开不实宣传。

不错,“区块链”这个概念异常的火热。

众所周知,我一直关注区块链及相关技术的成熟度发展情况,思考我们是否对其评价过高了;但从目前的情况来看,还没有这个迹象。我在文中提到的区块链技术是广义上的,包含了狭义上不属于区块链的分布式账本技术(DLT)。我对私有链permissioned blockchain更感兴趣,其中私有链的定义可以参考我的文章《区块链是安全性方面的话题吗?》。简而言之,我对加密货币之外的区块链业务应用特别感兴趣 注1

我们对区块链的技术成熟度的判断应该有一部分可以得到证实 注2 。如果我们判断正确,未来将会出现海量的区块链应用。这很可能会变成现实,但并不是所有的应用都是优秀的区块链应用,其中一部分很可能是非常糟糕的。

但区块链所处的技术成熟度意味着,大量业务将快速拥抱新技术 注3 ,但对于可能的前景却一知半解。促成这种情况的原因可以大致分为三种:

  1. 对于涉及多用户数据存储的业务应用,在投入精力的情况下,几乎都可以改造为基于区块链的版本;
  2. 很多区块链相关的会议和“专家”呼吁尽快拥抱区块链,否则可能会在半年内被淘汰 注4
  3. 完全理解区块链技术是很难的,支持其在企业中落地的往往是工程师。

对于最后一条,我必须补充几句,不然很容易被引起众怒 注5 。作为一名工程师,我显然无意贬低工程师。但工程师的天性使然,我们对见到的新鲜事物(亮点)热情澎湃,却对业务本身 深入 fully grok 注6 不足,故对于新技术给业务带来的影响理解可能并不深刻。在业务领导者看来,这些影响不一定是有利的。

上面提到的三种促因可能导致一种风险,即在没有充分评估利弊的情况下,将业务改造为区块链应用。在另一文(区块链:每个人都应该参与进来吗?)中提到几个场景,用于判断一个业务什么情况下适合采用区块链技术。这些场景是有益的,但更进一步,我坚信人们更加需要的是,业务完全不适用区块链的几种简单的场景判定。我总结了三种场景判定,如果对于其中任何一个问题你给出了肯定的回答,那么很大概率上区块链不适合你。

场景判定 1:业务是否需要集中式的管控或授权?

如果你给出了肯定的回答,那么区块链不适合你。

例如,假设你是一个普通销售商,具有唯一的订单系统,那么对于何时发货你有唯一的授权,显然区块链不适合你。假设你是一个内容提供商,所有提供的内容都会经过唯一的编辑和发布过程,显然区块链不适合你。

经验总结:只有当任务对应的执行流程及相应的认证流程是分布于众多主体时,区块链是有价值的。

场景判定 2:业务使用经典数据库是否工作良好?

如果你给出了肯定的回答,那么区块链不适合你。

该场景似乎与上一个场景是强相关的,但并不总是如此。在一些应用中,处理流程是分布的,但信息存储是中心化的;在另外一些应用中,处理流程需要中心化的授权,但信息存储是分布的,即总有一个并不是分布式的。但如果业务使用经典数据库可以工作量良好的话,使用经典数据库是一个好主意。

经典数据库不仅性能良好,在设计与运营成本方面低比区块链或分布式账本,而且我们在这方面技术积累丰厚。区块链让所有人 注8 可以查看和持有数据,但间接成本和潜在成本都比较高昂。

场景判定 3:业务采用新技术是否成本高昂或对合作伙伴有负面效果?

如果你给出了肯定的回答,那么区块链不适合你。

我曾听过这种观点,即区块链会让所有人获益。但这显然是不可能的。假设你正在为某个流程设计一个应用,改变合作伙伴与你及应用的交互方式,那么你需要判断这个改变是否符合合作伙伴的想法。不论是否涉及区块链,可以很容易的设计并引入一个应用,虽然降低了你自己的业务阻力,但与此同时增加了合作伙伴的业务阻力。

假设我为汽车行业生产发动机配件,那么使用区块链追溯和管理配件会让我受益匪浅。例如,我可以查看购买的滚珠轴承的生产商、生产时间和钢铁材料供应商等。换一个角度,假设我是滚珠轴承生产商,已经为40多个客户公司建立了处理流程。为一家客户引入新的流程会涉及工作方式、系统体系、储藏和安全性标准等方面的变更,这无法让我感兴趣,相反,这会导致复杂性和高开销。

总结

这几个场景判定用于提纲挈领,并不是一成不变的。其中数据库相关的那个场景判定更像是技术方面的,但也是紧密结合业务定位和功能的。希望这几个判定可以为区块链技术引进促因带来的过热进行降温。

  • 注 1. 请不要误解我的意思,加密货币显然是一种有趣的区块链业务应用,只是不在本文的讨论范畴而已。
  • 注 2. 知道具体是哪些部分是很有意义的,如果你知道,请告诉我好吗?
  • 注 3. 坦率的说,它其实更像是一大堆技术的集合体。
  • 注 4. 这显然是不太可能的,如果被淘汰的主体是这些会议和“专家”本身倒十分有可能。
  • 注 5. 由于比方打得有些不恰当,估计还是会引起众怒。
  • 注 6. 我太喜欢 grok 这个单词了,我把它放在这里作为我的工程师标志 注7
  • 注 7. 你可能已经想到了,我读过Stranger in a Strange Land一书,包括删减版和原版。
  • 注 8. 在合理的情况下。

原文最初发表于爱丽丝, 夏娃和鲍勃 – 一个安全性主题博客,已获得转载许可。


via: https://opensource.com/article/18/3/3-tests-not-moving-blockchain

作者:Mike Bursell 译者:pinewall 校对:wxy

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

作为一个 Linux 用户,我们常用 ssh 命令 来登入远程机器。ssh 命令你用得越多,你在键入一些重要的命令上花的时间也越多。我们可以用 定义在你的 .bashrc 文件里的别名 或函数来大幅度缩减花在命令行界面(CLI)的时间。但这不是最佳解决之道。最佳办法是在 ssh 配置文件中使用 SSH 别名

这里是我们能把 ssh 命令用得更好的几个例子。

ssh 登入到 AWS(译注:Amazon Web Services,亚马逊公司旗下云计算服务平台)实例的连接是一种痛。仅仅输入以下命令,每次也完全是浪费你时间。

ssh -p 3000 -i /home/surendra/mysshkey.pem [email protected]

缩短到:

ssh aws1

调试时连接到系统。

ssh -vvv [email protected]

缩短到:

ssh xyz

在本篇中,我们将看到如何不使用 bash 别名或函数实现 ssh 命令的缩短。ssh 别名的主要优点是所有的 ssh 命令快捷方式都存储在一个单一文件,如此就易于维护。其他优点是 对于类似于 SSH 和 SCP 的命令 我们能用相同的别名。

在我们进入实际配置之前,我们应该知道 /etc/ssh/ssh_config/etc/ssh/sshd_config~/.ssh/config 文件三者的区别。以下是对这些文件的解释。

/etc/ssh/ssh\_config 和 ~/.ssh/config 间的区别

系统级别的 SSH 配置项存放在 /etc/ssh/ssh_config,而用户级别的 SSH 配置项存放在 ~/.ssh/config 文件中。

/etc/ssh/sshconfig 和 /etc/ssh/sshdconfig 间的区别

系统级别的 SSH 配置项是在 /etc/ssh/ssh_config 文件中,而系统级别的 SSH 服务端配置项存放在 /etc/ssh/sshd_config 文件。

在 ~/.ssh/config 文件里配置项的语法

~/.ssh/config 文件内容的语法:

配置项 值
配置项 值1 值2

例 1: 创建主机(www.linuxnix.com)的 SSH 别名

编辑 ~/.ssh/config 文件写入以下内容:

Host tlj
 User root
 HostName 18.197.176.13
 port 22

保存此文件。

以上 ssh 别名用了

  1. tlj 作为一个别名的名称
  2. root 作为将要登入的用户
  3. 18.197.176.13 作为主机的 IP 地址
  4. 22 作为访问 SSH 服务的端口

输出:

sanne@Surendras-MacBook-Pro:~ > ssh tlj
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-93-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud
Last login: Sat Oct 14 01:00:43 2017 from 20.244.25.231
root@linuxnix:~# exit
logout
Connection to 18.197.176.13 closed.

例 2: 不用密码用 ssh 密钥登到系统要用 IdentityFile

例:

Host aws
 User ec2-users
 HostName ec2-54-200-184-202.us-west-2.compute.amazonaws.com
 IdentityFile ~/Downloads/surendra.pem
 port 22

例 3: 对同一主机使用不同的别名。在下例中,我们对同一 IP/主机 18.197.176.13 用了 tljlinuxnixlinuxnix.com 三个别名。

~/.ssh/config 文件内容

Host tlj linuxnix linuxnix.com
 User root
 HostName 18.197.176.13
 port 22

输出:

sanne@Surendras-MacBook-Pro:~ > ssh tlj
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-93-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
Last login: Sat Oct 14 01:00:43 2017 from 220.244.205.231
root@linuxnix:~# exit
logout
Connection to 18.197.176.13 closed.
sanne@Surendras-MacBook-Pro:~ > ssh linuxnix.com
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-93-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
Last login: Sun Oct 15 20:31:08 2017 from 1.129.110.13
root@linuxnix:~# exit
logout
Connection to 138.197.176.103 closed.
[6571] sanne@Surendras-MacBook-Pro:~ > ssh linuxnix
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-93-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
Last login: Sun Oct 15 20:31:20 2017 from 1.129.110.13
root@linuxnix:~# exit
logout
Connection to 18.197.176.13 closed.

例 4: 用相同的 SSH 别名复制文件到远程系统

语法:

scp <文件名> <ssh_别名>:<位置>

例子:

sanne@Surendras-MacBook-Pro:~ > scp abc.txt tlj:/tmp
abc.txt                           100%   12KB  11.7KB/s   00:01    
sanne@Surendras-MacBook-Pro:~ >

若我们已经将 ssh 主机设置好一个别名,由于 sshscp 两者用几乎相同的语法和选项,scp 也可以轻易使用。

请在下面尝试从本机 scp 一个文件到远程机器。

例 5: 解决 Linux 中的 SSH 超时问题。默认情况,如果你不积极地使用终端,你的 ssh 登入就会超时

SSH 超时问题 是一个更痛的点意味着你在一段时间后不得不重新登入到远程机器。我们能在 ~/.ssh/config 文件里边恰当地设置 SSH 超时时间来使你的会话不管在什么时间总是激活的。我们将用 2 个能保持会话存活的 SSH 选项来实现这一目的。之一是 ServerAliveInterval 保持你会话存活的秒数和 ServerAliveCountMax 在(经历了一个)给定数值的会话之后初始化会话。

ServerAliveInterval A
ServerAliveCountMax B

例:

Host tlj linuxnix linuxnix.com
 User root
 HostName 18.197.176.13
 port 22
 ServerAliveInterval 60
 ServerAliveCountMax 30

在下篇中我们将会看到一些其他的退出方式。


via: https://www.linuxnix.com/5-ssh-alias-examples-using-ssh-config-file/

作者:SURENDRA ANNE 译者:ch-cn 校对:wxy

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

了解如何解决 Linux 平台上的 mount.nfs: Stale file handle 错误。这个 NFS 错误可以在客户端或者服务端解决。

当你在你的环境中使用网络文件系统时,你一定不时看到 mount.nfs:Stale file handle 错误。此错误表示 NFS 共享无法挂载,因为自上次配置后有些东西已经更改。

无论是你重启 NFS 服务器或某些 NFS 进程未在客户端或服务器上运行,或者共享未在服务器上正确输出,这些都可能是导致这个错误的原因。此外,当这个错误发生在先前挂载的 NFS 共享上时,它会令人不快。因为这意味着配置部分是正确的,因为是以前挂载的。在这种情况下,可以尝试下面的命令:

确保 NFS 服务在客户端和服务器上运行良好。

#  service nfs status
rpc.svcgssd is stopped
rpc.mountd (pid 11993) is running...
nfsd (pid 12009 12008 12007 12006 12005 12004 12003 12002) is running...
rpc.rquotad (pid 11988) is running...

如果 NFS 共享目前挂载在客户端上,则强制卸载它并尝试在 NFS 客户端上重新挂载它。通过 df 命令检查它是否正确挂载,并更改其中的目录。

# umount -f /mydata_nfs

# mount -t nfs server:/nfs_share /mydata_nfs

#df -k
------ output clipped -----
server:/nfs_share 41943040  892928  41050112   3% /mydata_nfs

在上面的挂载命令中,服务器可以是 NFS 服务器的 IP 或主机名

如果你在强制取消挂载时遇到像下面错误:

# umount -f /mydata_nfs
umount2: Device or resource busy
umount: /mydata_nfs: device is busy
umount2: Device or resource busy
umount: /mydata_nfs: device is busy

然后你可以用 lsof 命令来检查哪个进程或用户正在使用该挂载点,如下所示:

# lsof |grep mydata_nfs
lsof: WARNING: can't stat() nfs file system /mydata_nfs
      Output information may be incomplete.
su         3327      root  cwd   unknown                                                   /mydata_nfs/dir (stat: Stale NFS file handle)
bash       3484      grid  cwd   unknown                                                   /mydata_nfs/MYDB (stat: Stale NFS file handle)
bash      20092  oracle11  cwd   unknown                                                   /mydata_nfs/MPRP (stat: Stale NFS file handle)
bash      25040  oracle11  cwd   unknown                                                   /mydata_nfs/MUYR (stat: Stale NFS file handle)

如果你在上面的示例中看到共有 4 个 PID 正在使用该挂载点上的某些文件。尝试杀死它们以释放挂载点。完成后,你将能够正确卸载它。

有时 mount 命令会有相同的错误。接着使用下面的命令在客户端重启 NFS 服务后挂载。

#  service nfs restart
Shutting down NFS daemon:                                  [  OK  ]
Shutting down NFS mountd:                                  [  OK  ]
Shutting down NFS quotas:                                  [  OK  ]
Shutting down RPC idmapd:                                  [  OK  ]
Starting NFS services:                                     [  OK  ]
Starting NFS quotas:                                       [  OK  ]
Starting NFS mountd:                                       [  OK  ]
Starting NFS daemon:                                       [  OK  ]

另请阅读:如何在 HPUX 中逐步重启 NFS

即使这没有解决你的问题,最后一步是在 NFS 服务器上重启服务。警告!这将断开从该 NFS 服务器输出的所有 NFS 共享。所有客户端将看到挂载点断开。这一步将 99% 解决你的问题。如果没有,请务必检查 NFS 配置,提供你修改的配置并发布你启动时看到的错误。

上面文章中的输出来自 RHEL6.3 服务器。请将你的评论发送给我们。


via: https://kerneltalks.com/troubleshooting/resolve-mount-nfs-stale-file-handle-error/

作者:KernelTalks 译者:geekpi 校对:wxy

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

早上醒来的时候,我就在想:“为什么我们学习一个新技能这么难?”

我不认为那是因为它很难。我认为原因可能在于我们花了太多的时间,而这件难事需要有丰富的阅历和足够的知识,然而我们要把这样的知识转换成技能所用的练习时间又不够。

拿游泳来说,你可以花上几天时间来阅读很多有关游泳的书籍,花几个小时和资深的游泳者和教练交流,观看所有可以获得的训练视频,但你第一次跳进水池的时候,仍然会像一个石头那样沉入水中,

要点在于:你认为自己有多了解那件事都无关紧要 —— 你得通过练习把知识变成技能。为了帮你练习,我把训练放在了这个系列的 第一部分第二部分 了。当然,你会在今后的文章中看到更多练习,我保证 :)

好,让我们开始今天的学习。

到现在为止,你已经知道了怎样解释像 “7 + 3” 或者 “12 - 9” 这样的两个整数相加减的算术表达式。今天我要说的是怎么解析(识别)、解释有多个数字相加减的算术表达式,比如 “7 - 3 + 2 - 1”。

文中的这个算术表达式可以用下面的这个语法图表示:

什么是 语法图 syntax diagram 语法图 是对一门编程语言中的语法规则进行图像化的表示。基本上,一个语法图就能告诉你哪些语句可以在程序中出现,哪些不能出现。

语法图很容易读懂:按照箭头指向的路径。某些路径表示的是判断,有些表示的是循环。

你可以按照以下的方式读上面的语法图:一个 term 后面可以是加号或者减号,接着可以是另一个 term,这个 term 后面又可以是一个加号或者减号,后面又是一个 term,如此循环。从字面上你就能读懂这个图片了。或许你会奇怪,“term” 是什么、对于本文来说,“term” 就是个整数。

语法图有两个主要的作用:

  • 它们用图形的方式表示一个编程语言的特性(语法)。
  • 它们可以用来帮你写出解析器 —— 你可以根据下列简单规则把图片转换成代码。

你已经知道,识别出记号流中的词组的过程就叫做 解析。解释器或者编译器执行这个任务的部分叫做 解析器。解析也称为 语法分析,并且解析器这个名字很合适,你猜的对,就是 语法分析器

根据上面的语法图,下面这些表达式都是合法的:

  • 3
  • 3 + 4
  • 7 - 3 + 2 - 1

因为算术表达式的语法规则在不同的编程语言里面是很相近的,我们可以用 Python shell 来“测试”语法图。打开 Python shell,运行下面的代码:

>>> 3
3
>>> 3 + 4
7
>>> 7 - 3 + 2 - 1
5

意料之中。

表达式 “3 + ” 不是一个有效的数学表达式,根据语法图,加号后面必须要有个 term (整数),否则就是语法错误。然后,自己在 Python shell 里面运行:

>>> 3 +
  File "<stdin>", line 1
    3 +
      ^
SyntaxError: invalid syntax

能用 Python shell 来做这样的测试非常棒,让我们把上面的语法图转换成代码,用我们自己的解释器来测试,怎么样?

从之前的文章里(第一部分第二部分)你知道 expr 方法包含了我们的解析器和解释器。再说一遍,解析器仅仅识别出结构,确保它与某些特性对应,而解释器实际上是在解析器成功识别(解析)特性之后,就立即对表达式进行评估。

以下代码片段显示了对应于图表的解析器代码。语法图里面的矩形方框(term)变成了 term 方法,用于解析整数,expr 方法和语法图的流程一致:

def term(self):
    self.eat(INTEGER)

def expr(self):
    # 把当前标记设为从输入中拿到的第一个标记
    self.current_token = self.get_next_token()

    self.term()
    while self.current_token.type in (PLUS, MINUS):
        token = self.current_token
        if token.type == PLUS:
            self.eat(PLUS)
            self.term()
        elif token.type == MINUS:
            self.eat(MINUS)
            self.term()

你能看到 expr 首先调用了 term 方法。然后 expr 方法里面的 while 循环可以执行 0 或多次。在循环里面解析器基于标记做出判断(是加号还是减号)。花一些时间,你就知道,上述代码确实是遵循着语法图的算术表达式流程。

解析器并不解释任何东西:如果它识别出了一个表达式,它就静默着,如果没有识别出来,就会抛出一个语法错误。改一下 expr 方法,加入解释器的代码:

def term(self):
    """Return an INTEGER token value"""
    token = self.current_token
    self.eat(INTEGER)
    return token.value

def expr(self):
    """Parser / Interpreter """
    # 将输入中的第一个标记设置成当前标记
    self.current_token = self.get_next_token()

    result = self.term()
    while self.current_token.type in (PLUS, MINUS):
        token = self.current_token
        if token.type == PLUS:
            self.eat(PLUS)
            result = result + self.term()
        elif token.type == MINUS:
            self.eat(MINUS)
            result = result - self.term()

    return result

因为解释器需要评估一个表达式, term 方法被改成返回一个整型值,expr 方法被改成在合适的地方执行加法或减法操作,并返回解释的结果。尽管代码很直白,我建议花点时间去理解它。

进行下一步,看看完整的解释器代码,好不?

这是新版计算器的源代码,它可以处理包含有任意多个加法和减法运算的有效的数学表达式。

# 标记类型
#
# EOF (end-of-file 文件末尾)标记是用来表示所有输入都解析完成
INTEGER, PLUS, MINUS, EOF = 'INTEGER', 'PLUS', 'MINUS', 'EOF'


class Token(object):
    def __init__(self, type, value):
        # token 类型: INTEGER, PLUS, MINUS, or EOF
        self.type = type
        # token 值: 非负整数值, '+', '-', 或无
        self.value = value

    def __str__(self):
        """String representation of the class instance.

        Examples:
            Token(INTEGER, 3)
            Token(PLUS, '+')
        """
        return 'Token({type}, {value})'.format(
            type=self.type,
            value=repr(self.value)
        )

    def __repr__(self):
        return self.__str__()


class Interpreter(object):
    def __init__(self, text):
        # 客户端字符输入, 例如. "3 + 5", "12 - 5", 
        self.text = text
        # self.pos is an index into self.text
        self.pos = 0
        # 当前标记实例
        self.current_token = None
        self.current_char = self.text[self.pos]

    ##########################################################
    # Lexer code                                             #
    ##########################################################
    def error(self):
        raise Exception('Invalid syntax')

    def advance(self):
        """Advance the `pos` pointer and set the `current_char` variable."""
        self.pos += 1
        if self.pos > len(self.text) - 1:
            self.current_char = None  # Indicates end of input
        else:
            self.current_char = self.text[self.pos]

    def skip_whitespace(self):
        while self.current_char is not None and self.current_char.isspace():
            self.advance()

    def integer(self):
        """Return a (multidigit) integer consumed from the input."""
        result = ''
        while self.current_char is not None and self.current_char.isdigit():
            result += self.current_char
            self.advance()
        return int(result)

    def get_next_token(self):
        """Lexical analyzer (also known as scanner or tokenizer)

        This method is responsible for breaking a sentence
        apart into tokens. One token at a time.
        """
        while self.current_char is not None:

            if self.current_char.isspace():
                self.skip_whitespace()
                continue

            if self.current_char.isdigit():
                return Token(INTEGER, self.integer())

            if self.current_char == '+':
                self.advance()
                return Token(PLUS, '+')

            if self.current_char == '-':
                self.advance()
                return Token(MINUS, '-')

            self.error()

        return Token(EOF, None)

    ##########################################################
    # Parser / Interpreter code                              #
    ##########################################################
    def eat(self, token_type):
        # 将当前的标记类型与传入的标记类型作比较,如果他们相匹配,就
        # “eat” 掉当前的标记并将下一个标记赋给 self.current_token,
        # 否则抛出一个异常
        if self.current_token.type == token_type:
            self.current_token = self.get_next_token()
        else:
            self.error()

    def term(self):
        """Return an INTEGER token value."""
        token = self.current_token
        self.eat(INTEGER)
        return token.value

    def expr(self):
        """Arithmetic expression parser / interpreter."""
        # 将输入中的第一个标记设置成当前标记
        self.current_token = self.get_next_token()

        result = self.term()
        while self.current_token.type in (PLUS, MINUS):
            token = self.current_token
            if token.type == PLUS:
                self.eat(PLUS)
                result = result + self.term()
            elif token.type == MINUS:
                self.eat(MINUS)
                result = result - self.term()

        return result


def main():
    while True:
        try:
            # To run under Python3 replace 'raw_input' call
            # 要在 Python3 下运行,请把 ‘raw_input’ 的调用换成 ‘input’
            text = raw_input('calc> ')
        except EOFError:
            break
        if not text:
            continue
        interpreter = Interpreter(text)
        result = interpreter.expr()
        print(result)


if __name__ == '__main__':
    main()

把上面的代码保存到 calc3.py 文件中,或者直接从 GitHub 上下载。试着运行它。看看它能不能处理我之前给你看过的语法图里面派生出的数学表达式。

这是我在自己的笔记本上运行的示例:

$ python calc3.py
calc> 3
3
calc> 7 - 4
3
calc> 10 + 5
15
calc> 7 - 3 + 2 - 1
5
calc> 10 + 1 + 2 - 3 + 4 + 6 - 15
5
calc> 3 +
Traceback (most recent call last):
  File "calc3.py", line 147, in <module>
    main()
  File "calc3.py", line 142, in main
    result = interpreter.expr()
  File "calc3.py", line 123, in expr
    result = result + self.term()
  File "calc3.py", line 110, in term
    self.eat(INTEGER)
  File "calc3.py", line 105, in eat
    self.error()
  File "calc3.py", line 45, in error
    raise Exception('Invalid syntax')
Exception: Invalid syntax

记得我在文章开始时提过的练习吗:它们在这儿,我保证过的:)

  • 画出只包含乘法和除法的数学表达式的语法图,比如 “7 * 4 / 2 * 3”。认真点,拿只钢笔或铅笔,试着画一个。 修改计算器的源代码,解释只包含乘法和除法的数学表达式。比如 “7 * 4 / 2 * 3”。
  • 从头写一个可以处理像 “7 - 3 + 2 - 1” 这样的数学表达式的解释器。用你熟悉的编程语言,不看示例代码自己思考着写出代码。做的时候要想一想这里面包含的组件:一个词法分析器,读取输入并转换成标记流,一个解析器,从词法分析器提供的记号流中获取,并且尝试识别流中的结构,一个解释器,在解析器成功解析(识别)有效的数学表达式后产生结果。把这些要点串起来。花一点时间把你获得的知识变成一个可以运行的数学表达式的解释器。

检验你的理解:

  1. 什么是语法图?
  2. 什么是语法分析?
  3. 什么是语法分析器?

嘿,看!你看完了所有内容。感谢你们坚持到今天,而且没有忘记练习。:) 下次我会带着新的文章回来,尽请期待。


via: https://ruslanspivak.com/lsbasi-part3/

作者:Ruslan Spivak 译者:BriFuture 校对:wxy

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