12 条实用的 zypper 命令范例

Summary: zypper 是 Suse Linux 系统的包和补丁管理器，你可以根据下面的 12 条附带输出示例的实用范例来学习 zypper 命令的使用。

`zypper` 是 Suse Linux 系统的包和补丁管理器，你可以根据下面的 12 条附带输出示例的实用范例来学习 `zypper` 命令的使用。

![](/data/attachment/album/201812/12/132928jmstt99so6s0tesp.jpg)

Suse Linux 使用 `zypper` 进行包管理，其是一个由 [ZYpp 包管理引擎](https://en.wikipedia.org/wiki/ZYpp)提供的包管理系统。在此篇文章中我们将分享 12 条附带输出示例的实用 `zypper` 命令，能帮助你处理日常的系统管理任务。

不带参数的 `zypper` 命令将列出所有可用的选项，这比参考详细的 man 手册要容易上手得多。

```
root@kerneltalks # zypper
  用法：
        zypper [--global-options] <command> [--command-options] [arguments]
        zypper <subcommand> [--command-options] [arguments]

  全局选项：
        --help, -h              帮助
        --version, -V           输出版本号
        --promptids             输出 zypper 用户提示符列表
        --config, -c <file>     使用指定的配置文件来替代默认的
        --userdata <string>     在历史和插件中使用的用户自定义事务 id
        --quiet, -q             忽略正常输出，只打印错误信息
        --verbose, -v           增加冗长程度
        --color
        --no-color              是否启用彩色模式，如果 tty 支持的话
        --no-abbrev, -A         表格中的文字不使用缩写
        --table-style, -s       表格样式（整型）
        --non-interactive, -n   不询问任何选项，自动使用默认答案
        --non-interactive-include-reboot-patches
                                针对带有重启标志的补丁，不使用交互模式
        --xmlout, -x            切换至 XML 输出
        --ignore-unknown, -i    忽略未知的包

        --reposd-dir, -D <dir>  使用自定义仓库文件目录
        --cache-dir, -C <dir>   为所有缓存启用可选路径
        --raw-cache-dir <dir>   启用可选 raw 元数据缓存路径
        --solv-cache-dir <dir>  启用可选 solv 文件缓存路径
        --pkg-cache-dir <dir>   启用可选包缓存路径

     仓库选项：
        --no-gpg-checks         忽略 GPG 检查失败并跳过
        --gpg-auto-import-keys  自动信任并导入新仓库的签名密钥
        --plus-repo, -p <URI>   使用附加仓库
        --plus-content <tag>    另外使用禁用的仓库来提供特定的关键词
                                尝试使用 '--plus-content debug' 选项来启用仓库
        --disable-repositories  不从仓库中读取元数据
        --no-refresh            不刷新仓库
        --no-cd                 忽略 CD/DVD 中的仓库
        --no-remote             忽略远程仓库
        --releasever            设置所有 .repo 文件中的 $releasever 变量（默认值：发行版版本）

     Target Options:
        --root, -R <dir>        在另一个根路径下进行操作
        --disable-system-resolvables
                                不读取已安装包

  命令：
        help, ?                 打印帮助
        shell, sh               允许多命令

     仓库管理：
        repos, lr               列出所有自定义仓库
        addrepo, ar             添加一个新仓库
        removerepo, rr          移除指定仓库
        renamerepo, nr          重命名指定仓库
        modifyrepo, mr          修改指定仓库
        refresh, ref            刷新所有仓库
        clean                   清除本地缓存

     服务管理：
        services, ls            列出所有自定义服务
        addservice, as          添加一个新服务
        modifyservice, ms       修改指定服务
        removeservice, rs       移除指定服务
        refresh-services, refs  刷新所有服务

     软件管理：
        install, in             安装包
        remove, rm              移除包
        verify, ve              确认包依赖的完整性
        source-install, si      安装源码包及其构建依赖
        install-new-recommends, inr
                                安装由已安装包建议一并安装的新包

     更新管理：
        update, up              更新已安装包至更新版本
        list-updates, lu        列出可用更新
        patch                   安装必要的补丁
        list-patches, lp        列出必要的补丁
        dist-upgrade, dup       进行发行版更新
        patch-check, pchk       检查补丁

     查询：
        search, se              查找符合匹配模式的包
        info, if                展示特定包的完全信息
        patch-info              展示特定补丁的完全信息
        pattern-info            展示特定模式的完全信息
        product-info            展示特定产品的完全信息
        patches, pch            列出所有可用的补丁
        packages, pa            列出所有可用的包
        patterns, pt            列出所有可用的模式
        products, pd            列出所有可用的产品
        what-provides, wp       列出提供特定功能的包

     包锁定：
        addlock, al             添加一个包锁定
        removelock, rl          移除一个包锁定
        locks, ll               列出当前的包锁定
        cleanlocks, cl          移除无用的锁定

     其他命令：
        versioncmp, vcmp        比较两个版本字符串
        targetos, tos           打印目标操作系统 ID 字符串
        licenses                打印已安装包的证书和 EULAs 报告
        download                使用命令行下载指定 rpm 包到本地目录
        source-download         下载所有已安装包的源码 rpm 包到本地目录

     子命令：
        subcommand              列出可用子命令

输入 'zypper help <command>' 来获得特定命令的帮助。
```

### 如何使用 zypper 安装包

`zypper` 通过 `in` 或 `install` 子命令来在你的系统上安装包。它的用法与 [yum 软件包安装](https://kerneltalks.com/tools/package-installation-linux-yum-apt/) 相同。你只需要提供包名作为参数，包管理器（此处是 `zypper`）就会处理所有的依赖并与你指定的包一并安装。

```
# zypper install telnet
Refreshing service 'SMT-http_smt-ec2_susecloud_net'.
Refreshing service 'cloud_update'.
Loading repository data...
Reading installed packages...
Resolving package dependencies...

The following NEW package is going to be installed:
  telnet

1 new package to install.
Overall download size: 51.8 KiB. Already cached: 0 B. After the operation, additional 113.3 KiB will be used.
Continue? [y/n/...? shows all options] (y): y
Retrieving package telnet-1.2-165.63.x86_64                                                                                (1/1),  51.8 KiB (113.3 KiB unpacked)
Retrieving: telnet-1.2-165.63.x86_64.rpm .........................................................................................................................[done]
Checking for file conflicts: .....................................................................................................................................[done]
(1/1) Installing: telnet-1.2-165.63.x86_64 .......................................................................................................................[done]
```

以上是我们安装 `telnet` 包时的输出，供你参考。

推荐阅读：[在 YUM 和 APT 系统中安装包](https://kerneltalks.com/tools/package-installation-linux-yum-apt/)

### 如何使用 zypper 移除包

要在 Suse Linux 中擦除或移除包，使用 `zypper` 附带 `remove` 或 `rm` 子命令。

```
root@kerneltalks # zypper rm telnet
Loading repository data...
Reading installed packages...
Resolving package dependencies...

The following package is going to be REMOVED:
  telnet

1 package to remove.
After the operation, 113.3 KiB will be freed.
Continue? [y/n/...? shows all options] (y): y
(1/1) Removing telnet-1.2-165.63.x86_64 ..........................................................................................................................[done]
```

我们在此处移除了先前安装的 telnet 包。

### 使用 zypper 检查依赖或者认证已安装包的完整性

有时可以通过强制忽略依赖关系来安装软件包。`zypper` 使你能够扫描所有已安装的软件包并检查其依赖性。如果缺少任何依赖项，它将提供你安装或重新安装它的机会，从而保持已安装软件包的完整性。

使用附带 `verify` 或 `ve` 子命令的 `zypper` 命令来检查已安装包的完整性。

```
root@kerneltalks # zypper ve
Refreshing service 'SMT-http_smt-ec2_susecloud_net'.
Refreshing service 'cloud_update'.
Loading repository data...
Reading installed packages...

Dependencies of all installed packages are satisfied.
```

在上面的输出中，你能够看到最后一行说明已安装包的所有依赖都已安装完全，并且无需更多操作。

### 如何在 Suse Linux 中使用 zypper 下载包

`zypper` 提供了一种方法使得你能够将包下载到本地目录而不去安装它。你可以在其他具有同样配置的系统上使用这个已下载的软件包。包会被下载至 `/var/cache/zypp/packages/<repo>/<arch>/` 目录。

```
root@kerneltalks # zypper download telnet
Refreshing service 'SMT-http_smt-ec2_susecloud_net'.
Refreshing service 'cloud_update'.
Loading repository data...
Reading installed packages...
Retrieving package telnet-1.2-165.63.x86_64                                                                                (1/1),  51.8 KiB (113.3 KiB unpacked)
(1/1) /var/cache/zypp/packages/SMT-http_smt-ec2_susecloud_net:SLES12-SP3-Pool/x86_64/telnet-1.2-165.63.x86_64.rpm ................................................[done]

download: Done.

# ls -lrt /var/cache/zypp/packages/SMT-http_smt-ec2_susecloud_net:SLES12-SP3-Pool/x86_64/
total 52
-rw-r--r-- 1 root root 53025 Feb 21 03:17 telnet-1.2-165.63.x86_64.rpm

```

你能看到我们使用 `zypper` 将 telnet 包下载到了本地。

推荐阅读：[在 YUM 和 APT 系统中只下载包而不安装](https://kerneltalks.com/howto/download-package-using-yum-apt/)

### 如何使用 zypper 列出可用包更新

`zypper` 允许你浏览已安装包的所有可用更新，以便你可以提前计划更新活动。使用 `list-updates` 或 `lu` 子命令来显示已安装包的所有可用更新。

```
root@kerneltalks # zypper lu
Refreshing service 'SMT-http_smt-ec2_susecloud_net'.
Refreshing service 'cloud_update'.
Loading repository data...
Reading installed packages...
S | Repository                        | Name                       | Current Version               | Available Version                  | Arch
--|-----------------------------------|----------------------------|-------------------------------|------------------------------------|-------
v | SLES12-SP3-Updates                | at-spi2-core               | 2.20.2-12.3                   | 2.20.2-14.3.1                      | x86_64
v | SLES12-SP3-Updates                | bash                       | 4.3-82.1                      | 4.3-83.5.2                         | x86_64
v | SLES12-SP3-Updates                | ca-certificates-mozilla    | 2.7-11.1                      | 2.22-12.3.1                        | noarch
v | SLE-Module-Containers12-Updates   | containerd                 | 0.2.5+gitr639_422e31c-20.2    | 0.2.9+gitr706_06b9cb351610-16.8.1  | x86_64
v | SLES12-SP3-Updates                | crash                      | 7.1.8-4.3.1                   | 7.1.8-4.6.2                        | x86_64
v | SLES12-SP3-Updates                | rsync                      | 3.1.0-12.1                    | 3.1.0-13.10.1                      | x86_64
```

输出特意被格式化以便于阅读。每一列分别代表包所属仓库名称、包名、已安装版本、可用的更新版本和架构。

### 在 Suse Linux 中列出和安装补丁

使用 `list-patches` 或 `lp` 子命令来显示你的 Suse Linux 系统需要被应用的所有可用补丁。

```
root@kerneltalks # zypper lp
Refreshing service 'SMT-http_smt-ec2_susecloud_net'.
Refreshing service 'cloud_update'.
Loading repository data...
Reading installed packages...

Repository                        | Name                                     | Category    | Severity  | Interactive | Status | Summary
----------------------------------|------------------------------------------|-------------|-----------|-------------|--------|------------------------------------------------------------------------------------
SLE-Module-Containers12-Updates   | SUSE-SLE-Module-Containers-12-2018-273   | security    | important | ---         | needed | Version update for docker, docker-runc, containerd, golang-github-docker-libnetwork
SLE-Module-Containers12-Updates   | SUSE-SLE-Module-Containers-12-2018-62    | recommended | low       | ---         | needed | Recommended update for sle2docker
SLE-Module-Public-Cloud12-Updates | SUSE-SLE-Module-Public-Cloud-12-2018-268 | recommended | low       | ---         | needed | Recommended update for python-ecdsa
SLES12-SP3-Updates                | SUSE-SLE-SERVER-12-SP3-2018-116          | security    | moderate  | ---         | needed | Security update for rsync
---- output clipped ----
SLES12-SP3-Updates                | SUSE-SLE-SERVER-12-SP3-2018-89           | security    | moderate  | ---         | needed | Security update for perl-XML-LibXML
SLES12-SP3-Updates                | SUSE-SLE-SERVER-12-SP3-2018-90           | recommended | low       | ---         | needed | Recommended update for lvm2

Found 37 applicable patches:
37 patches needed (18 security patches)
```

使用相应的表头可以很好地组织输出。你可以轻松地找出并根据情况计划你的补丁更新。我们能看到在我们的系统中，37 个可用补丁中有 18 个是安全补丁，需要被高优先级应用！

你可以通过发出 `zypper patch` 命令安装所有需要的补丁。

##### 如何使用 zypper 更新包

要使用 `zypper` 更新包，使用 `update` 或 `up` 子命令后接包名。在上述列出的更新命令中，我们知道在我们的服务器上 `rsync` 包更新可用。让我们现在来更新它吧！

```
root@kerneltalks # zypper update rsync
Refreshing service 'SMT-http_smt-ec2_susecloud_net'.
Refreshing service 'cloud_update'.
Loading repository data...
Reading installed packages...
Resolving package dependencies...

The following package is going to be upgraded:
  rsync

1 package to upgrade.
Overall download size: 325.2 KiB. Already cached: 0 B. After the operation, additional 64.0 B will be used.
Continue? [y/n/...? shows all options] (y): y
Retrieving package rsync-3.1.0-13.10.1.x86_64                                                                                (1/1), 325.2 KiB (625.5 KiB unpacked)
Retrieving: rsync-3.1.0-13.10.1.x86_64.rpm .......................................................................................................................[done]
Checking for file conflicts: .....................................................................................................................................[done]
(1/1) Installing: rsync-3.1.0-13.10.1.x86_64 .....................................................................................................................[done]
```

### 在 Suse Linux 上使用 zypper 查找包

如果你不确定包的全名也不要担心。你可以使用 `zypper` 附带的 `se` 或 `search` 子命令并提供查找字符串来查找包。

```
root@kerneltalks # zypper se lvm
Refreshing service 'SMT-http_smt-ec2_susecloud_net'.
Refreshing service 'cloud_update'.
Loading repository data...
Reading installed packages...

S  | Name          | Summary                      | Type
---|---------------|------------------------------|-----------
   | libLLVM       | Libraries for LLVM           | package
   | libLLVM-32bit | Libraries for LLVM           | package
   | llvm          | Low Level Virtual Machine    | package
   | llvm-devel    | Header Files for LLVM        | package
   | lvm2          | Logical Volume Manager Tools | srcpackage
i+ | lvm2          | Logical Volume Manager Tools | package
   | lvm2-devel    | Development files for LVM2   | package
```

在上述示例中我们查找了 `lvm` 字符串并得到了如上输出列表。你能在 `zypper install/remove/update` 命令中使用 `Name` 字段的名字。

### 使用 zypper 检查已安装包信息

你能够使用 `zypper` 检查已安装包的详细信息。`info` 或 `if` 子命令将列出已安装包的信息。它也可以显示未安装包的详细信息，在该情况下，`Installed` 参数将返回 `No` 值。

```
root@kerneltalks # zypper info rsync
Refreshing service 'SMT-http_smt-ec2_susecloud_net'.
Refreshing service 'cloud_update'.
Loading repository data...
Reading installed packages...

Information for package rsync:
------------------------------
Repository     : SLES12-SP3-Updates
Name           : rsync
Version        : 3.1.0-13.10.1
Arch           : x86_64
Vendor         : SUSE LLC <https://www.suse.com/>
Support Level  : Level 3
Installed Size : 625.5 KiB
Installed      : Yes
Status         : up-to-date
Source package : rsync-3.1.0-13.10.1.src
Summary        : Versatile tool for fast incremental file transfer
Description    :
    Rsync is a fast and extraordinarily versatile file  copying  tool. It can copy
    locally, to/from another host over any remote shell, or to/from a remote rsync
    daemon. It offers a large number of options that control every aspect of its
    behavior and permit very flexible specification of the set of files to be
    copied. It is famous for its delta-transfer algorithm, which reduces the amount
    of data sent over the network by sending only the differences between the
    source files and the existing files in the destination. Rsync is widely used
    for backups and mirroring and as an improved copy command for everyday use.
```

### 使用 zypper 列出仓库

使用 `zypper` 命令附带 `lr` 或 `repos` 子命令列出仓库。

```
root@kerneltalks # zypper lr
Refreshing service 'cloud_update'.
Repository priorities are without effect. All enabled repositories share the same priority.

#  | Alias                                                                                | Name                                                  | Enabled | GPG Check | Refresh
---|--------------------------------------------------------------------------------------|-------------------------------------------------------|---------|-----------|--------
 1 | SMT-http_smt-ec2_susecloud_net:SLE-Module-Adv-Systems-Management12-Debuginfo-Pool    | SLE-Module-Adv-Systems-Management12-Debuginfo-Pool    | No      | ----      | ----
 2 | SMT-http_smt-ec2_susecloud_net:SLE-Module-Adv-Systems-Management12-Debuginfo-Updates | SLE-Module-Adv-Systems-Management12-Debuginfo-Updates | No      | ----      | ----
 3 | SMT-http_smt-ec2_susecloud_net:SLE-Module-Adv-Systems-Management12-Pool              | SLE-Module-Adv-Systems-Management12-Pool              | Yes     | (r ) Yes  | No
 4 | SMT-http_smt-ec2_susecloud_net:SLE-Module-Adv-Systems-Management12-Updates           | SLE-Module-Adv-Systems-Management12-Updates           | Yes     | (r ) Yes  | Yes
 5 | SMT-http_smt-ec2_susecloud_net:SLE-Module-Containers12-Debuginfo-Pool                | SLE-Module-Containers12-Debuginfo-Pool                | No      | ----      | ----
 6 | SMT-http_smt-ec2_susecloud_net:SLE-Module-Containers12-Debuginfo-Updates             | SLE-Module-Containers12-Debuginfo-Updates             | No      | ----      | ----
```

此处你需要检查 `enabled` 列来确定哪些仓库是已被启用的而哪些没有。

### 在 Suse Linux 中使用 zypper 添加或移除仓库

要添加仓库你需要仓库或 .repo 文件的 URI，否则你会遇到如下错误。

```
root@kerneltalks # zypper addrepo -c SLES12-SP3-Updates
If only one argument is used, it must be a URI pointing to a .repo file.
```

使用 URI，你可以像如下方式添加仓库：

```
root@kerneltalks # zypper  addrepo -c http://smt-ec2.susecloud.net/repo/SUSE/Products/SLE-SDK/12-SP3/x86_64/product?credentials=SMT-http_smt-ec2_susecloud_net SLE-SDK12-SP3-Pool
Adding repository 'SLE-SDK12-SP3-Pool' ...........................................................................................................................[done]
Repository 'SLE-SDK12-SP3-Pool' successfully added

URI         : http://smt-ec2.susecloud.net/repo/SUSE/Products/SLE-SDK/12-SP3/x86_64/product?credentials=SMT-http_smt-ec2_susecloud_net
Enabled     : Yes
GPG Check   : Yes
Autorefresh : No
Priority    : 99 (default priority)

Repository priorities are without effect. All enabled repositories share the same priority.
```

在 Suse 中使用附带 `addrepo` 或 `ar` 子命令的 `zypper` 命令添加仓库，后接 URI 以及你需要提供一个别名。

要在 Suse 中移除一个仓库，使用附带 `removerepo` 或 `rr` 子命令的 `zypper` 命令。

```
root@kerneltalks # zypper removerepo nVidia-Driver-SLE12-SP3
Removing repository 'nVidia-Driver-SLE12-SP3' ....................................................................................................................[done]
Repository 'nVidia-Driver-SLE12-SP3' has been removed.
```

### 清除 zypper 本地缓存

使用 `zypper clean` 命令清除 zypper 本地缓存。

```
root@kerneltalks # zypper clean
All repositories have been cleaned up.
```

---

via: <https://kerneltalks.com/commands/12-useful-zypper-command-examples/>

作者：[KernelTalks](https://kerneltalks.com) 译者：[cycoe](https://github.com/cycoe) 校对：[wxy](https://github.com/wxy)

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


Cat 命令的源码历史

Summary: 对于 cat，这个想法和程序本身都很古老。不准确地说，我的电脑上的 cat 来自 1969 年。

![](/data/attachment/album/201812/12/164029zjwhnefzwzo6jmn6.jpg)

以前我和我的一些亲戚争论过计算机科学的学位值不值得读。当时我正在上大学，并要决定是不是该主修计算机。我姨和我表姐觉得我不应该主修计算机。她们承认知道如何编程肯定是很有用且对自己有利的一件事，但是她们认为计算机科学现在发展的如此迅速以至于我学的东西几乎马上就过时了。建议我更好是把编程作为辅业，选择一个基础原理可以受用终身的领域主修，比如经济学或物理学。

我知道我姨和我表姐说的不对，并决定主修计算机科学。（对不住啊！）平常人可能会觉得像计算机科学领域和软件工程专业每隔几年就完全和之前不一样了。其原因很容易理解。我们有了个人电脑，然后有了互联网，有了手机，之后还有了机器学习…… 科技总是在更新，支撑科技发展的原理和技能当然也在改变。当然，最惊人的是其实原理的改变竟然如此之小。我敢肯定，大多数人在知道了他们电脑里一些重要的软件的历史是多么久远时他们一定会深感震惊。当然我不是说那些刷版本号的浮夸软件 —— 我电脑上的 Firefox 浏览器副本，可能是我用的最多的软件，可能两周前就更新过。如果你看了比如 `grep` 的手册页，你就会发现它在 2010 年后就没有过更新了（至少在 MacOS 上如此）。初版 `grep` 是在 1974 年写就的，那时可以算是计算机世界的侏罗纪了。直到现在，人们（还有程序）仍然依赖 `grep` 来完成日常工作。

我姨和我表姐认为计算机技术就像一系列日渐精致的沙堡，在潮水抹净沙滩后新的沙堡完全取代旧的。但事实上，在很多领域上，我们都是不断积累能够解决问题的程序。我们可能不得不偶尔修改这些程序以避免软件无法使用，但大多数情况下我们都可以不修改。`grep` 是一个简单的程序，可以解决一个仍然存在的需求，所以它能够存活下来。 大多数应用程序编程都是在非常高的级别上完成的，它们建立在解决了旧问题的旧程序的金字塔上。 30 年或 40 年前的思路和概念，远非过时，在很多情况下它们依然在您的笔记本电脑上软件中存在着。

我想追溯这样的老程序自第一次写就以来改变了多少回很有趣。 `cat` 可能是所有 Unix 实用程序中最简单的，因此我们以它为例。Ken Thompson 于 1969 年编写了 `cat` 的原始实现。如果我告诉别人我的电脑上安装了个来自 1969 年的程序，这准确吗？我们电脑上的程序多大了？

感谢这种[这种](https://github.com/dspinellis/unix-history-repo)仓库，我们可以完整的看到 `cat` 自 1969 年后是如何发展的。我会先聚焦于可以算得上是我的 MacBook 上的 `cat` 的祖先的 `cat` 实现。随着我们从 Unix 上的第一版 `cat` 追踪到现在 MacOS 上的 `cat`，你会发现，这个程序被重写的次数比你想的还要多 —— 但是直到现在它运行的方式和五十年前多少是完全一致的。

### 研究 Unix

Ken Thompson 和 Dennis Ritchie 在 PDP 7 上开始写 Unix。那还是 1969 年，C 还没被发明出来，因此所有早期的 Unix 软件都是用 PDP 7 汇编实现的。他们使用的汇编种类是 Unix 特有的，Ken Thompson 在 DEC（PDP 7 的厂商）提供的汇编器之上加了些特性，实现了自己的汇编器。Thompson 的更改在[最初的 Unix 程序员手册](https://www.bell-labs.com/usr/dmr/www/man11.pdf)的 `as`（也就是汇编器）条目下均有所记录。

因此，[最初的](https://gist.github.com/sinclairtarget/47143ba52b9d9e360d8db3762ee0cbf5#file-1-cat-pdp7-s) `cat` 也是使用 PDP 7 汇编实现的。 我添加了一些注释，试图解释每条指令的作用，但除非你理解 Thompson 在编写汇编器时加的特性，否则程序仍然很难理解。在那些特性中有两个很重要：其一是 `;` 这个字符可以在一行中用来分隔多条语句，它多出现于在使用 `sys` 指令时将系统调用的多个参数放在同一行上。其二是， Thompson 的汇编器支持使用 0 到 9 作为“临时标签”，这是在程序内可以重用的标签。因此。就如 Unix 程序员手册中所说：“对程序员的想象力和汇编程序的符号空间的要求都降低了”。在任何给定的指令内，你都可以使用 `nf` 和 `nb` 来引用下一个或最近的临时标签 `n`。 例如，如果存在标记为 `1:` 的代码块，你就可以使用指令 `jmp 1b` 从下游代码跳回该块。 （但是你不使用 `jmp 1f` 的话就没法从上面的代码跳到这里。）

初版 `cat` 最有趣的就是它包含着我们应该认识的符号。有一块指令块标记为 `getc`，还有一个标记为 `putc`，可以看到这两个符号比 C 标准还古老。第一版的 `cat` 函数实际上已经包含了这两个函数的实现。该实现做了输入缓存，这样它就不需要一次只读写一个字母。

`cat` 的第一个版本并没有持续多久。 Ken Thompson 和 Dennis Ritchie 说服贝尔实验室购买了 PDP 11，这样他们就能够继续扩展和改进 Unix。 PDP 11 的指令集和之前不一样，因此必须重写 `cat`。 我也注释了[这个第二版](https://gist.github.com/sinclairtarget/47143ba52b9d9e360d8db3762ee0cbf5#file-2-cat-pdp11-s) `cat`。 它为新的指令集使用新的汇编程序助记符，并利用了 PDP 11 的各种[寻址模式](https://en.wikipedia.org/wiki/PDP-11_architecture#Addressing_modes)。（如果你对源代码中的括号和美元符号感到困惑，那是因为这些符号用于指示不同的寻址模式。）但它也使用 `;` 字符和临时标签，和 `cat` 的第一个版本一样，这意味着当把 `as` 移植到 PDP 11 上时，必须要保留这些功能。

`cat` 的第二个版本比第一个版本简单得多。 它也更有 Unix 味儿，它不只是依靠参数列表，一旦没给参数列表，它将从 `stdin` 读取数据，这也就是今天 `cat` 仍在做的事情。 你也可以在此版本的 `cat` 中以 `-` 为参数，以表示它应该从`stdin`读取。

在 1973 年，为了准备发布第四版 Unix，大部分代码都用 C 语言重写了。但是 `cat` 似乎在之后一段时间内并没有使用 C 重写。 [cat 的第一个 C 语言实现](https://gist.github.com/sinclairtarget/47143ba52b9d9e360d8db3762ee0cbf5#file-3-cat-v7-c)出现在第七版 Unix 中。 这个实现非常有趣，因为它很简单。 在所有以后的实现中，这个实现和在 K＆R 的 C 语言教科书中用作教学示范的理想化 `cat` 最相似。这个程序的核心就是经典的两行：

```
while ((c = getc(fi)) != EOF)
 putchar(c);
```

当然实际代码要比这多一些，额外的代码主要是为了确保你没有在读/写同一个文件。另一个有趣的事情是，`cat` 的这一版实现只识别一个标志位 `-u`。 `-u` 标志可用于避免缓冲输入和输出，否则 `cat` 将以 512 字节为块进行输入输出。

### BSD

在第七版 Unix 之后，Unix 出现了各种衍生品和分支。 MacOS 建立于 Darwin 之上，而 Darwin 又源自<ruby> 伯克利软件分发版 <rt>  Berkeley Software Distribution </rt></ruby>（BSD），因此 BSD 是我们最感兴趣的 Unix 分支。 BSD 最初只是 Unix 中的实用程序和附加组件的集合，但它最终成为了一个完整的操作系统。直到第四版 BSD，人称 4BSD，为一大堆新标志添加了支持之前，BSD 似乎还是依赖于最初的 `cat` 实现的。`cat` 的 [4BSD 实现](https://gist.github.com/sinclairtarget/47143ba52b9d9e360d8db3762ee0cbf5#file-4-cat-bsd4-c) 显然是从原始实现中衍生出来的，尽管它添加了一个新函数来实现由新标志触发的行为。已经在文件中使用的 `fflg` 变量（用于标记输入是从 `stdin` 还是文件读取的）的命名约定，被新添加的 `nflg`、`bflg`、`vflg`、`sflg`、`eflg` 和 `tflg` 沿袭了下来，这些变量记录了在调用程序时是否使用了这些新标志。这些是最后一批添加到 `cat` 的命令行标志。如今 `cat` 的手册页列出了这些标志，没有其他的标志了，至少在 Mac OS 上是如此。 4BSD 于 1980 年发布，因此这套标志已有 38 年历史。

`cat` 最后一次被完全重写是在 BSD NET/2 上，其目的是通过替换 AT&T 发布的全部 Unix 源代码来规避许可证问题。BSD Net/2 在 1991 年发布。这一版本的 `cat` 是由 Kevin Fall 重写的。 Kevin Fall 于 1988 年毕业于加州大学伯克利分校并在下一年成为<ruby> 计算机系统研究组 <rt>  Computer Systems Research Group </rt></ruby>（CSRG）的组员，Fall 和我说当时使用 AT&T 代码的 Unix 工具被列在了 CSRG 的墙上，组员需要从中选出他们想要重写的工具； Fall 选了 `cat` 以及 `mknod`。 MacOS 系统内自带的 `cat` 实现源码的最上面还有着他的名字。他的这一版 `cat`，尽管平淡无奇，在今天还是被无数人使用着。

[Fall 的原始 cat 实现](https://gist.github.com/sinclairtarget/47143ba52b9d9e360d8db3762ee0cbf5#file-5-cat-net2-c) 比我们迄今为止看到的版本都要长。 除了支持 `-?` 帮助标志外，它没有增加任何新功能。 从概念上讲，它与 4BSD 的实现非常相似。 它长是因为 Fall 将实现分为 “原始” 模式和 “加工” 模式。 “原始” 模式是 `cat` 的经典实现；它一个字符一个字符的打印文件。 “加工” 模式是带有所有 4BSD 命令行选项的 `cat`。 如此区别不无道理，但这么办也扩充了实现规模，因此乍一看其源码似乎比实际上更复杂。文件末尾还有一个奇特的错误处理函数，进一步地增加了实现的长度。

### MacOS

在 2001 年，苹果发布了 MacOS X。这一发布对苹果意义重大。因为苹果用了多年的时间尝试以取代其现有的老旧操作系统（经典的 Mac OS），但是都失败了。 在 Mac OS X 之前苹果两次尝试在内部创建一个新的操作系统，但两者都无疾而终。 最后，苹果收购了史蒂夫·乔布斯的 NeXT 公司，后者开发了一个名为 NeXTSTEP 的操作系统和面向对象编程框架。 苹果将 NeXTSTEP 作为 Mac OS X 的基础。因为 NeXTSTEP 部分基于 BSD，使以 NeXTSTEP 为基础的 Mac OS X 的自然就把 BSD 系的代码直接带入苹果宇宙的中心。

因此，Mac OS X 的非常早期的第一个版本包含了从 NetBSD 项目中提取的 `cat` 的[实现](https://gist.github.com/sinclairtarget/47143ba52b9d9e360d8db3762ee0cbf5#file-6-cat-macosx-c)。如今仍保持开发的 NetBSD 最初是 386BSD 的分支，而后者又直接基于 BSD Net/2。所以 Mac OS X 里面的第一个 `cat` 的实现就是 Kevin Fall 的 `cat`。唯一改变的是，Fall 的错误处理函数 `err()` 被 `err.h` 提供的 `err()` 函数取代了。 `err.h` 是 C 标准库的 BSD 扩展。

之后不久，这里的 `cat` 的 NetBSD 实现被换成了 FreeBSD 中的 `cat` 实现。 [根据维基百科](https://en.wikipedia.org/wiki/Darwin_(operating_system))，苹果在 Mac OS X 10.3（Panther）中开始使用 FreeBSD 的实现而不是 NetBSD 的实现。但根据苹果自己开源的版本，`cat` 的 Mac OS X 实现在 2007 年发布的 Mac OS X 10.5（Leopard）之前没有被替换。苹果为 Leopard 替换的的 [FreeBSD 实现](https://gist.github.com/sinclairtarget/47143ba52b9d9e360d8db3762ee0cbf5#file-7-cat-macos-10-13-c)与今天苹果计算机上的实现相同。截至 2018 年，2007 年以来的这个实现仍未被更新或修改。

所以 Mac OS 上的 `cat` 已经很老了。实际上，这一实现在 2007 年在 MacOS X 上露面两年前就被发布了。 [这个 2005 年的修改](https://github.com/freebsd/freebsd/commit/a76898b84970888a6fd015e15721f65815ea119a#diff-6e405d5ab5b47ca2a131ac7955e5a16b) 在 FreeBSD 的 Github 镜像中可见，是在苹果将其合并入 Mac OS X 前对 FreeBSD 的 `cat` 实现进行的最后一次更改。所以 Mac OS X 中的实现没有与 FreeBSD 的 `cat` 实现保持同步，它如今已经 13 岁了。对于软件修改了多少代码才能仍是算是同一软件这一话题有着旷日持久的争论。不过，在这种情况下，源文件自 2005 年以来根本没有变化。

现在 Mac OS 使用的 `cat` 实现与 Fall 1991 年为 BSD Net/2 版本编写的实现没有什么不同。最大的区别是添加了一个全新的功能来提供 Unix 域套接字支持。FreeBSD 开发人员似乎将 Fall 的 `raw_args()` 函数和 `cook_args()` 函数组合成一个名为`scanfiles()` 的函数。否则，程序的核心就仍是 Fall 的代码。

我问过 Fall 对编写了如今被数以百万计的苹果用户（直接或者间接通过依赖 `cat` 的某些程序）使用的 `cat` 实现有何感想。Fall，如今是一位顾问，也是最新版《TCP/IP 详解》的合著者，他说，当人们从了解他对 `cat` 所做的工作中收获颇丰时，他感到很惊讶。 Fall 在计算机领域有着悠久的职业生涯，曾参与许多备受瞩目的项目，但似乎很多人仍对他在 1989 年重写 `cat` 的那六个月的工作感到最为兴奋。

### 百年老程序

在宏伟的发明史中，计算机并不是一项古老的发明。我们已经习惯了百年的照片甚至是百年的视频短片。但是计算机程序不一样 —— 它们代表着高科技和新技术。至少，他们是现代的技术造出来的。随着计算行业的成熟，我们有朝一日会发现自己正在使用有着接近百年历史的程序吗？

计算机硬件可能会发生较大的变化，使得我们也许无法让现在编译的可执行文件在一个世纪后的硬件上运行。也许编程语言设计的进步让未来没有人能理解 C 语言，`cat` 将来也可能也被别的语言重写很久了。 （尽管 C 已经存在了五十年了，而且它似乎不会很快就被替换掉。）但除此之外，为什么不永远使用我们现在的 `cat`？

我认为 `cat` 的历史表明，计算机科学中的一些想法确实非常持久。事实上，对于 `cat`，这个想法和程序本身都很古老。不准确地说，我的电脑上的 `cat` 来自 1969 年。但我也可以说我的计算机上的 `cat` 来自1989 年，当时 Fall 写了他的 `cat` 实现。许多其他软件也同样古老。因此，也许我们不应该把计算机科学和软件开发视为不断破坏现状和发明新事物的领域。我们的计算机系统是由诸多历史文物构建的。有时，我们可能会花费更多时间在理解和维护这些历史文物上，而不是花在编写新代码上。

如果你喜欢本文，你可能更喜欢两周来一篇更新！在推特上关注 [@TwoBitHistory](https://twitter.com/TwoBitHistory) 或者订阅这个 [RSS 源](https://twobithistory.org/feed.xml) 以保证接受到新的文章。

---

via: <https://twobithistory.org/2018/11/12/cat.html>

作者：[Two-Bit History](https://twobithistory.org) 选题：[lujun9972](https://github.com/lujun9972) 译者：[name1e5s](https://github.com/name1e5s) 校对：[wxy](https://github.com/wxy)

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


Emacs 系列（五）：Org 模式之文档与演示稿

![](/data/attachment/album/201812/12/225949wapsn1pc0dpfpfbz.png)

这是 [Emacs 和 Org 模式系列](https://changelog.complete.org/archives/tag/emacs2018)的第五篇。

这篇博文是由 Org 模式的源文件生成的，其有几种格式：[博客页面](https://github.com/jgoerzen/public-snippets/blob/master/emacs/emacs-org-beamer/emacs-org-beamer.org)、[演示稿](http://changelog.complete.org/archives/9900-emacs-5-documents-and-presentations-with-org-mode) 和 [PDF 文档](https://github.com/jgoerzen/public-snippets/raw/master/emacs/emacs-org-beamer/emacs-org-beamer.pdf)。

### 1 Org 模式的输出

#### 1.1 背景

Org 模式不仅仅只是一个议程生成程序，它也能输出许多不同的格式： LaTeX、PDF、Beamer、iCalendar（议程）、HTML、Markdown、ODT、普通文本、手册页和其它更多的复杂的格式，比如说网页文件。

这也不只是一些事后的想法，这是 Org 模式的设计核心部分并且集成的很好。

这一个文件可以同时是源代码、自动生成的输出、任务列表、文档和展示。

有些人将 Org 模式作为他们首选的标记格式，甚至对于 LaTeX 文档也是如此。Org 模式手册中的 [输出一节](https://orgmode.org/manual/Exporting.html#Exporting) 有更详细的介绍。

#### 1.2 开始

对于任意的 Org 模式的文档，只要按下 `C-c C-e` 键，就会弹出一个让你选择多种输出格式和选项的菜单。这些选项通常是次键选择，所以很容易设置和执行。例如：要输出一个 PDF 文档，按 `C-c C-e l p`，要输出 HMTL 格式的， 按 `C-c C-e h h`。

对于所有的输出选项，都有许多可用的设置；详情参见手册。事实上，使用 LaTeX 格式相当于同时使用 LaTeX 和 HTML 模式，在不同的模式中插入任意的前言和设置等。

#### 1.3 第三方插件

[ELPA](https://www.emacswiki.org/emacs/ELPA) 中也包含了许多额外的输出格式，详情参见 [ELPA](https://www.emacswiki.org/emacs/ELPA)。

### 2 Org 模式的 Beamer 演示

#### 2.1 关于 Beamer

[Beamer](https://en.wikipedia.org/wiki/Beamer_(LaTeX)) 是一个生成演示稿的 LaTeX 环境. 它包括了以下特性：

* 在演示稿中自动生成结构化的元素（例如 [Marburg 主题](https://hartwork.org/beamer-theme-matrix/all/beamer-albatross-Marburg-1.png))。 在演示稿中，这个特性可以为观众提供了视觉参考。
* 对组织演示稿有很大的帮助。
* 主题
* 完全支持 LaTeX

#### 2.2 Org 模式中 Beamer 的优点

在 Org 模式中用 Beamer 有很多好处，总的来说：

* Org 模式很简单而且对可视化支持的很好，同时改变结构可以快速的重组你的材料。
* 与 org-babel 绑定在一起，实时语法高亮源码和内嵌结果。
* 语法通常更容易使用。

我已经完全用 Org 模式和 beamer 替换掉使用 LibreOffice/Powerpoint/GoogleDocs。事实上，当我必须使用其中一种工具时，这是相当令人沮丧的，因为它们在可视化表示演讲稿结构方面远远比不上 Org 模式。

#### 2.3 标题层次

Org 模式的 Beamer 会将你文档中的部分（文中定义了标题的）转换成幻灯片。当然，问题是：哪些部分？这是由 H [输出设置](https://orgmode.org/manual/Export-settings.html#Export-settings)（`org-export-headline-levels`）决定的。

针对不同的人，有许多不同的方法。我比较喜欢我的演示稿这样：

```
#+OPTIONS: H:2
#+BEAMER_HEADER: \AtBeginSection{\frame{\sectionpage}}
```

这将为每个主题提供了独立部分，以突出主题的改变然后使用级别 2（两个星号）的标题来设置幻灯片。许多 Beamer 主题也有第三个间接层次，所以你可以将 H 设为 3。

#### 2.4 主题和配置

你可以在 Org 模式的文件顶部来插入几行来配置 Beamer 和 LaTeX。在本文中，例如，你可以这样定义：

```
#+TITLE:  Documents and presentations with org-mode
#+AUTHOR: John Goerzen
#+BEAMER_HEADER: \institute{The Changelog}
#+PROPERTY: comments yes
#+PROPERTY: header-args :exports both :eval never-export
#+OPTIONS: H:2
#+BEAMER_THEME: CambridgeUS
#+BEAMER_COLOR_THEME: default
```

#### 2.5 高级设置

我比教喜欢修改颜色、项目符号样式等。我的配置如下：

```
# We can't just +BEAMER_INNER_THEME: default because that picks the theme default.
# Override per https://tex.stackexchange.com/questions/11168/change-bullet-style-formatting-in-beamer
#+BEAMER_INNER_THEME: default
#+LaTeX_CLASS_OPTIONS: [aspectratio=169]
#+BEAMER_HEADER: \definecolor{links}{HTML}{0000A0}
#+BEAMER_HEADER: \hypersetup{colorlinks=,linkcolor=,urlcolor=links}
#+BEAMER_HEADER: \setbeamertemplate{itemize items}[default]
#+BEAMER_HEADER: \setbeamertemplate{enumerate items}[default]
#+BEAMER_HEADER: \setbeamertemplate{items}[default]
#+BEAMER_HEADER: \setbeamercolor*{local structure}{fg=darkred}
#+BEAMER_HEADER: \setbeamercolor{section in toc}{fg=darkred}
#+BEAMER_HEADER: \setlength{\parskip}{\smallskipamount}
```

在这里，`aspectratio=169` 将纵横比设为 16:9, 其它部分都是标准的 LaTeX/Beamer 配置。

#### 2.6 缩小 (适应屏幕)

有时你会遇到一些非常大的代码示例，你可能更倾向与将幻灯片缩小以适应它们。

只要按下 `C-c C-c p` 将 `BEAMER_opt` 属性设为 `shrink=15`\。（或者设为更大的 shrink 值）。上一张幻灯片就用到了这个。

#### 2.7 效果

这就是最终的效果：

[![screenshot1](/data/attachment/album/201812/12/225959m16cb032yc011wu4.png)](https://www.flickr.com/photos/jgoerzen/26366340577/in/dateposted/)

### 3 幻灯片之间的交互

#### 3.1 交互式的 Emacs 幻灯片

使用 [org-tree-slide](https://orgmode.org/worg/org-tutorials/non-beamer-presentations.html#org-tree-slide) 这个插件的话，就可以在 Emacs 的右侧显示幻灯片了。 只要按下 `M-x`，然后输入 `org-tree-slide-mode`，回车，然后你就可以用 `C->` 和 `C-<` 在幻灯片之间切换了。

你可能会发现 `C-c C-x C-v` （即 `org-toggle-inline-images`）有助于使系统显示内嵌的图像。

#### 3.2 HTML 幻灯片

有许多方式可以将 Org 模式的演讲稿导出为 HTML，并有不同级别的 JavaScript 集成。有关详细信息，请参见 Org 模式的 wiki 中的 [非 beamer 演讲稿一节](https://orgmode.org/worg/org-tutorials/non-beamer-presentations.html)。

### 4 更多

#### 4.1 本文中的附加资源

* [orgmode.org beamer tutorial](https://orgmode.org/worg/exporters/beamer/tutorial.html)
* [LaTeX wiki](https://en.wikibooks.org/wiki/LaTeX/Presentations)
* [Generating section title slides](https://tex.stackexchange.com/questions/117658/automatically-generate-section-title-slides-in-beamer/117661)
* [Shrinking content to fit on slide](https://tex.stackexchange.com/questions/78514/content-doesnt-fit-in-one-slide)
* 很棒的资源： refcard-org-beamer
	+ 详情参见其 [Github repo](https://github.com/fniessen/refcard-org-beamer) 中的 PDF 和 .org 文件。
* 很漂亮的主题： [Theme matrix](https://hartwork.org/beamer-theme-matrix/)

#### 4.2 下一个 Emacs 系列

mu4e 邮件!

---

via: <http://changelog.complete.org/archives/9900-emacs-5-documents-and-presentations-with-org-mode>

作者：[John Goerzen](http://changelog.complete.org/archives/author/jgoerzen) 选题：[lujun9972](https://github.com/lujun9972) 译者：[oneforalone](https://github.com/oneforalone) 校对：[wxy](https://github.com/wxy)

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


降低项目失败率的三个原则

Summary: 透明和包容性的项目要求可以降低您的失败率。 以下是如何协作收集它们。

透明和包容性的项目要求可以降低您的失败率。 以下是如何协作收集它们。

>
> 透明和包容性的项目要求可以降低您的失败率。 以下是如何协作收集它们。
>
>
>

![](/data/attachment/album/201812/13/163341meoeovjyfpjdhody.jpg)

众所周知，明确、简洁和可衡量的需求会带来更多成功的项目。一项[麦肯锡与牛津大学](http://calleam.com/WTPF/?page_id=1445)的关于大型项目的研究表明：“平均而言，大型 IT 项目超出预算 45％，时间每推移 7％，价值就比预期低 56％ 。”该研究还表明，造成这种失败的一些原因是“模糊的业务目标，不同步的利益相关者以及过度的返工。”

业务分析师经常发现自己通过持续对话来构建这些需求。为此，他们必须吸引多个利益相关方，并确保参与者提供明确的业务目标。这样可以减少返工，提高更多项目的成功率。

他们可以用开放和包容的方式做到这一点。

### 成功的框架

提高项目成功率的一个工具是[开放决策框架](https://opensource.com/open-organization/resources/open-decision-framework)。开放决策框架是一种资源，可以帮助用户在拥抱[开放原则](https://opensource.com/open-organization/resources/open-org-definition)的组织中做出更有效的决策。该框架强调三个主要原则：透明、包容、以客户为中心。

**透明**。很多时候，开发人员和产品设计人员都认为他们知道利益相关者如何使用特定工具或软件。但这些假设往往是不正确的，并导致对利益相关者实际需求的误解。开发人员和企业主讨论时实行透明势在必行。开发团队不仅需要了解一些好的情景，还需要了解利益相关方面对某些工具或流程所面临的挑战。提出诸如以下问题：“必须手动完成哪些步骤？”以及“这个工具是否按预期运行？”这提供了对问题的共同理解和讨论的基准。

**包容**。对于业务分析师来说，在收集需求时观察肢体语言和视觉暗示非常重要。如果有人双臂交叉或睁着眼睛坐着，那么这清楚地表明他们感觉没被聆听。业务分析师必须鼓励那些不被聆听的人公开交流，并给他们机会被聆听。在开始会议之前，制定基本规则，使所有人都能发表意见并分享他们的想法。聆听提供的反馈，并在收到反馈时礼貌地回复。多样化的意见和协作解决问题将为会议带来令人兴奋的想法。

**以顾客为中心**。以客户为中心的第一步是识别客户。谁从这种变化、更新或开发中受益？在项目早期，进行利益相关者映射，以帮助确定关键利益相关者，他们在项目中的角色以及他们适应大局的方式。让合适的客户参与并确保满足他们的需求将会确定更成功的要求，进行更现实（现实）的测试，并最终成功交付。

当你的需求会议透明、包容和以客户为中心时，你将收集更好的需求。当你使用[开放决策框架](https://opensource.com/open-organization/16/6/introducing-open-decision-framework)来进行会议时，参与者会感觉有更多参与与授权，他们会提供更准确和完整的需求。换一种说法：

**透明+包容+以客户为中心=更好的需求=成功的项目**

---

via: <https://opensource.com/open-organization/18/2/constructing-project-requirements>

作者：[Tracy Buckner](https://opensource.com/users/tracyb) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


数据科学家的命令行技巧

Summary: 立志掌握命令行应该在每个开发人员的学习清单上，特别是数据科学家。

立志掌握命令行应该在每个开发人员的学习清单上，特别是数据科学家。

![](/data/attachment/album/201812/13/221149dirhh8vthq2vll9j.png)

对于许多数据科学家来说，数据操作从始至终就是 Pandas 或 Tidyverse。从理论上讲，这样做没有任何问题。毕竟，这就是这些工具存在的原因。然而，对于像分隔符转换这样的简单任务，这些工具是大材小用了。

立志掌握命令行应该在每个开发人员的学习清单上，特别是数据科学家。学习 shell 的来龙去脉将无可否认地提高你的生产力。除此之外，命令行还是计算领域的一个重要历史课程。例如，awk —— 一种数据驱动的脚本语言。1977 年，在 [Brain Kernighan](https://en.wikipedia.org/wiki/Brian_Kernighan)（即传奇的 [K&R 书](https://en.wikipedia.org/wiki/The_C_Programming_Language)中 K）的帮助下，awk 首次出现。今天，大约五十年过去了，awk 仍然活跃在每年[新出版的书](https://www.amazon.com/Learning-AWK-Programming-cutting-edge-text-processing-ebook/dp/B07BT98HDS)里面。因此，可以安全地假设对命令行魔法的付出不会很快贬值。

### 我们将涵盖什么

* ICONV
* HEAD
* TR
* WC
* SPLIT
* SORT & UNIQ
* CUT
* PASTE
* JOIN
* GREP
* SED
* AWK

### ICONV

文件编码可能会很棘手。现在大部分文件都是 UTF-8 编码的。要了解 UTF-8 背后的一些魔力，请查看这个出色的[视频](https://www.youtube.com/watch?v=MijmeoH9LT4)。尽管如此，有时我们收到的文件不是这种编码。这可能引起对改变编码模式的一些胡乱尝试。这里，`iconv` 是一个拯救者。`iconv` 是一个简单的程序，它将获取采用一种编码的文本并输出采用另一种编码的文本。

```
# Converting -f (from) latin1 (ISO-8859-1)
# -t (to) standard UTF_8

iconv -f ISO-8859-1 -t UTF-8 < input.txt > output.txt
```

实用选项：

* `iconv -l` 列出所有已知编码
* `iconv -c` 默默丢弃无法转换的字符

### HEAD

如果你是一个 Pandas 重度用户，那么会很熟悉 `head`。通常在处理新数据时，我们想做的第一件事就是了解其内容。这就得启动 Pandas，读取数据然后调用 `df.head()` —— 要说这有点费劲。没有任何选项的 `head` 将打印出文件的前 10 行。`head` 的真正力量在于干净利落的测试操作。例如，如果我们想将文件的分隔符从逗号更改为管道。一个快速测试将是：`head mydata.csv | sed 's/,/|/g'`。

```
# Prints out first 10 lines
head filename.csv

# Print first 3 lines
head -n 3 filename.csv
```

实用选项：

* `head -n` 打印特定行数
* `head -c` 打印特定字节数

### TR

`tr` 类似于翻译。这个功能强大的实用程序是文件基础清理的主力。理想的用例是替换文件中的分隔符。

```
# Converting a tab delimited file into commas
cat tab_delimited.txt | tr "\t" "," comma_delimited.csv
```

`tr` 另一个功能是你可以用内建 `[:class:]` 变量（POSIX 字符类）发挥威力。这些包括了：

* `[:alnum:]` 所有字母和数字
* `[:alpha:]` 所有字母
* `[:blank:]` 所有水平空白
* `[:cntrl:]` 所有控制字符
* `[:digit:]` 所有数字
* `[:graph:]` 所有可打印字符，但不包括空格
* `[:lower:]` 所有小写字母
* `[:print:]` 所有可打印字符，包括空格
* `[:punct:]` 所有标点符号
* `[:space:]` 所有水平或垂直空白
* `[:upper:]` 所有大写字母
* `[:xdigit:]` 所有 16 进制数字

你可以将这些连接在一起以组成强大的程序。以下是一个基本的字数统计程序，可用于检查 README 是否被滥用。

```
cat README.md | tr "[:punct:][:space:]" "\n" | tr "[:upper:]" "[:lower:]" | grep . | sort | uniq -c | sort -nr
```

另一个使用基本正则表达式的例子：

```
# Converting all upper case letters to lower case
cat filename.csv | tr '[A-Z]' '[a-z]'
```

实用选项：

* `tr -d` 删除字符
* `tr -s` 压缩字符
* `\b` 退格
* `\f` 换页
* `\v` 垂直制表符
* `\NNN` 八进制字符

### WC

单词计数。它的价值主要来自其 `-l` 选项，它会给你提供行数。

```
# Will return number of lines in CSV
wc -l gigantic_comma.csv
```

这个工具可以方便地确认各种命令的输出。所以，如果我们在转换文件中的分隔符之后运行 `wc -l`，我们会期待总行数是一样的，如果不一致，我们就知道有地方出错了。

实用选项：

* `wc -c` 打印字节数
* `wc -m` 打印字符数
* `wc -L` 打印最长行的长度
* `wc -w` 打印单词数量

### SPLIT

文件大小的范围可以很广。对于有的任务，拆分文件或许是有好处的，所以使用 `split` 吧。`split` 的基本语法是：

```
# We will split our CSV into new_filename every 500 lines
split -l 500 filename.csv new_filename_
# filename.csv
# ls output
# new_filename_aaa
# new_filename_aab
# new_filename_aa
```

它有两个奇怪的地方是命名约定和缺少文件扩展名。后缀约定可以通过 `-d` 标志变为数字。要添加文件扩展名，你需要运行以下 `find` 命令。它将通过附加 `.csv` 扩展名来更改当前目录中所有文件的名称，所以小心了。

```
find . -type f -exec mv '{}' '{}'.csv \;
# ls output
# filename.csv.csv
# new_filename_aaa.csv
# new_filename_aab.csv
# new_filename_aac.csv
```

实用选项：

* `split -b N` 按特定字节大小分割
* `split -a N` 生成长度为 N 的后缀
* `split -x` 使用十六进制后缀

### SORT & UNIQ

上面两个命令很明显：它们的作用就是字面意思。这两者结合起来可以提供最强大的冲击 (例如，唯一单词的数量)。这是由于 `uniq` 只作用于重复的相邻行。这也是在输出前进行 `sort` 的原因。一个有趣的事情是 `sort -u` 会达到和典型的 `sort file.txt | uniq` 模式一样的结果。

`sort` 对数据科学家来说确实具有潜在的有用能力：能够根据特定列对整个 CSV 进行排序。

```
# Sorting a CSV file by the second column alphabetically
sort -t"," -k2,2 filename.csv

# Numerically
sort -t"," -k2n,2 filename.csv

# Reverse order
sort -t"," -k2nr,2 filename.csv
```

这里的 `-t` 选项将逗号指定为分隔符，通常假设分隔符是空格或制表符。此外，`-k` 选项是为了确定我们的键。这里的语法是 `-km,n`，`m` 作为开始列，`n` 作为结束列。

实用选项：

* `sort -f` 忽略大小写
* `sort -r` 反向排序
* `sort -R` 乱序
* `uniq -c` 统计出现次数
* `uniq -d` 只打印重复行

### CUT

`cut` 用于删除列。作为演示，如果我们只想删除第一和第三列。

```
cut -d, -f 1,3 filename.csv
```

要选择除了第一行外的所有行。

```
cut -d, -f 2- filename.csv
```

结合其他命令，将 `cut` 用作过滤器。

```
# Print first 10 lines of column 1 and 3, where "some_string_value" is present
head filename.csv | grep "some_string_value" | cut -d, -f 1,3
```

查出第二列中唯一值的数量。

```
cat filename.csv | cut -d, -f 2 | sort | uniq | wc -l

# Count occurences of unique values, limiting to first 10 results
cat filename.csv | cut -d, -f 2 | sort | uniq -c | head
```

### PASTE

`paste` 是一个带有趣味性功能的特定命令。如果你有两个需要合并的文件，并且它们已经排序好了，`paste` 帮你解决了接下来的步骤。

```
# names.txt
adam
john
zach

# jobs.txt
lawyer
youtuber
developer

# Join the two into a CSV
paste -d ',' names.txt jobs.txt > person_data.txt

# Output
adam,lawyer
john,youtuber
zach,developer
```

更多 SQL 式变种，见下文。

### JOIN

`join` 是一个简单的、<ruby> 准切向的 <rt>  quasi-tangential </rt></ruby> SQL。最大的区别是 `join` 将返回所有列以及只能在一个字段上匹配。默认情况下，`join` 将尝试使用第一列作为匹配键。为了获得不同结果，必须使用以下语法：

```
# Join the first file (-1) by the second column
# and the second file (-2) by the first
join -t "," -1 2 -2 1 first_file.txt second_file.txt
```

标准的 `join` 是内连接。然而，外连接通过 `-a` 选项也是可行的。另一个值得一提的技巧是 `-q` 标志，如果发现有缺失的字段，可用于替换值。

```
# Outer join, replace blanks with NULL in columns 1 and 2
# -o which fields to substitute - 0 is key, 1.1 is first column, etc...
join -t"," -1 2 -a 1 -a2 -e ' NULL' -o '0,1.1,2.2' first_file.txt second_file.txt
```

它不是最用户友好的命令，而是绝望时刻的绝望措施。

实用选项：

* `join -a` 打印不可配对的行
* `join -e` 替换丢失的输入字段
* `join -j` 相当于 `-1 FIELD -2 FIELD`

### GREP

`grep` 即 <ruby> 用正则表达式全局搜索并且打印 <rt>  Global search for a Regular Expression and Print </rt></ruby>，可能是最有名的命令，并且名副其实。`grep` 很强大，特别适合在大型代码库中查找。在数据科学的王国里，它充当其他命令的提炼机制。虽然它的标准用途也很有价值。

```
# Recursively search and list all files in directory containing 'word'

grep -lr 'word' .

# List number of files containing word

grep -lr 'word' . | wc -l

```

计算包含单词或模式的总行数。

```
grep -c 'some_value' filename.csv

# Same thing, but in all files in current directory by file name

grep -c 'some_value' *
```

对多个值使用“或”运算符： `\|`。

```
grep "first_value\|second_value" filename.csv
```

实用选项：

* `alias grep="grep --color=auto"` 使 grep 色彩丰富
* `grep -E` 使用扩展正则表达式
* `grep -w` 只匹配整个单词
* `grep -l` 打印匹配的文件名
* `grep -v` 非匹配

### 大人物们

`sed` 和 `awk` 是本文中最强大的两个命令。为简洁起见，我不打算详细讨论这两个命令。相反，我将介绍各种能证明其令人印象深刻的力量的命令。如果你想了解更多，[这儿就有一本书](https://www.amazon.com/sed-awk-Dale-Dougherty/dp/1565922255/ref=sr_1_1?ie=UTF8&qid=1524381457&sr=8-1&keywords=sed+and+awk)是关于它们的。

### SED

`sed` 本质上是一个流编辑器。它擅长替换，但也可以用于所有输出重构。

最基本的 `sed` 命令由 `s/old/new/g` 组成。它的意思是搜索 `old`，全局替换为 `new`。 如果没有 `/g`，我们的命令将在 `old` 第一次出现后终止。

为了快速了解它的功能，我们可以深入了解一个例子。 在以下情景中，你已有以下文件：

```
balance,name
$1,000,john
$2,000,jack
```

我们可能想要做的第一件事是删除美元符号。`-i` 标志表示原位。`''` 表示零长度文件扩展名，从而覆盖我们的初始文件。理想情况下，你可以单独测试，然后输出到新文件。

```
sed -i '' 's/\$//g' data.txt
# balance,name
# 1,000,john
# 2,000,jack
```

接下来，去除 `blance` 列的逗号。

```
sed -i '' 's/\([0-9]\),\([0-9]\)/\1\2/g' data.txt
# balance,name
# 1000,john
# 2000,jack
```

最后 jack 有一天决定辞职。所以，再见了，我的朋友。

```
sed -i '' '/jack/d' data.txt
# balance,name
# 1000,john
```

正如你所看到的，`sed` 有很多强大的功能，但乐趣并不止于此。

### AWK

最好的留在最后。`awk` 不仅仅是一个简单的命令：它是一个成熟的语言。在本文中涉及的所有内容中，`awk` 是目前为止最酷的。如果你感兴趣，这里有很多很棒的资源 —— 看 [这里](https://www.amazon.com/AWK-Programming-Language-Alfred-Aho/dp/020107981X/ref=sr_1_1?ie=UTF8&qid=1524388936&sr=8-1&keywords=awk)、[这里](http://www.grymoire.com/Unix/Awk.html) 和 [这里](https://www.tutorialspoint.com/awk/index.htm)。

`awk` 的常见用例包括：

* 文字处理
* 格式化文本报告
* 执行算术运算
* 执行字符串操作

`awk` 可以以最原生的形式并行 `grep`。

```
awk '/word/' filename.csv
```

或者更加神奇：将 `grep` 和 `cut` 组合起来。在这里，对于所有带我们指定单词 `word` 的行，`awk` 打印第三和第四列，用 `tab` 分隔。`-F,` 用于指定切分时的列分隔符为逗号。

```
awk -F, '/word/ { print $3 "\t" $4 }' filename.csv
```

`awk` 内置了许多精巧的变量。比如，`NF` —— 字段数，和 `NR` —— 记录数。要获取文件中的第 53 条记录：

```
awk -F, 'NR == 53' filename.csv
```

更多的花招是其基于一个或多个值进行过滤的能力。下面的第一个示例将打印第一列等于给定字符串的行的行号和列。

```
awk -F, ' $1 == "string" { print NR, $0 } ' filename.csv

# Filter based off of numerical value in second column
awk -F, ' $2 == 1000 { print NR, $0 } ' filename.csv
```

多个数值表达式：

```
# Print line number and columns where column three greater
# than 2005 and column five less than one thousand

awk -F, ' $3 >= 2005 && $5 <= 1000 { print NR, $0 } ' filename.csv
```

求出第三列的总和：

```
awk -F, '{ x+=$3 } END { print x }' filename.csv
```

在第一列等于 `something` 的那些行，求出第三列值的总和。

```
awk -F, '$1 == "something" { x+=$3 } END { print x }' filename.csv
```

获取文件的行列数：

```
awk -F, 'END { print NF, NR }' filename.csv

# Prettier version
awk -F, 'BEGIN { print "COLUMNS", "ROWS" }; END { print NF, NR }' filename.csv
```

打印出现了两次的行：

```
awk -F, '++seen[$0] == 2' filename.csv
```

删除重复的行：

```
# Consecutive lines
awk 'a !~ $0; {a=$0}']

# Nonconsecutive lines
awk '! a[$0]++' filename.csv

# More efficient
awk '!($0 in a) {a[$0];print}
```

使用内置函数 `gsub()` 替换多个值。

```
awk '{gsub(/scarlet|ruby|puce/, "red"); print}'
```

这个 `awk` 命令将组合多个 CSV 文件，忽略标题，然后在最后附加它。

```
awk 'FNR==1 && NR!=1{next;}{print}' *.csv > final_file.csv
```

需要缩小一个庞大的文件？ `awk` 可以在 `sed` 的帮助下处理它。具体来说，该命令根据行数将一个大文件分成多个较小的文件。这个一行脚本将增加一个扩展名。

```
sed '1d;$d' filename.csv | awk 'NR%NUMBER_OF_LINES==1{x="filename-"++i".csv";}{print > x}'

# Example: splitting big_data.csv into data_(n).csv every 100,000 lines
sed '1d;$d' big_data.csv | awk 'NR%100000==1{x="data_"++i".csv";}{print > x}'
```

### 结语

命令行拥有无穷无尽的力量。本文中介绍的命令足以将你从一无所知提升到英雄人物。除了涵盖的内容之外，还有许多实用程序可以考虑用于日常数据操作。[Csvkit](http://csvkit.readthedocs.io/en/1.0.3/)、[xsv](https://github.com/BurntSushi/xsv) 还有 [q](https://github.com/harelba/q) 是需要记住的三个。如果你希望更深入地了解命令行数据科学，查看[这本书](https://www.amazon.com/Data-Science-Command-Line-Time-Tested/dp/1491947853/ref=sr_1_1?ie=UTF8&qid=1524390894&sr=8-1&keywords=data+science+at+the+command+line)。它也可以[免费](https://www.datascienceatthecommandline.com/)在线获得！

---

via: <http://kadekillary.work/post/cli-4-ds/>

作者：[Kade Killary](http://kadekillary.work/authors/kadekillary) 选题：[lujun9972](https://github.com/lujun9972) 译者：[GraveAccent](https://github.com/graveaccent) 校对：[wxy](https://github.com/wxy)

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


给写作者们的 7 个命令行工具

Summary: 扔掉你的文字编辑器，然后使用这些开源工具在命令行上写作吧。

扔掉你的文字编辑器，然后使用这些开源工具在命令行上写作吧。

>
> 扔掉你的文字编辑器，然后使用这些开源工具在命令行上写作吧。
>
>
>

![](/data/attachment/album/201812/13/224349sakaydvbedllll9l.jpg)

对于大多数人（尤其是非技术人员），写作意味着在 LibreOffice Writer 或者其他带图形界面的文字处理应用上编辑文本。但是还有许多可行的方法可以让任何人通过文本传递他们的信息，尤其是越来越多的作者选择[拥抱纯文本](https://plaintextproject.online)。

在使用图形界面写作的世界同样有命令行工具的一席之地。这些命令行工具可以帮助他们进行写作，检查他们的拼写等等 —— 无论是在写一篇文章、博客或者故事；写一个 README 文件；或者准备一份技术文档的时候。

下面是一些在任何写作情况下都有用的命令行工具。

### 编辑器

没错，你可以在命令行进行真正的写作。我知道一些写作者会使用 [Nano](https://www.nano-editor.org/)、[Vim](https://www.vim.org)、[Emacs](https://www.gnu.org/software/emacs/)、以及 [Jove](https://opensource.com/article/17/1/jove-lightweight-alternative-vim) 等编辑器在终端窗口中进行工作。而这些编辑器[并非屈指可数](https://en.wikipedia.org/wiki/List_of_text_editors#Text_user_interface)。文本编辑器的优势在于它们简单易用以及更专注于文本。非常适合用于编辑任何文本的初稿甚至完成一个漫长而复杂的写作项目。

如果你想在命令行中获得更像文字编辑器的体验，不妨了解一下 [WordGrinder](https://cowlark.com/wordgrinder/)。它是一款简单但拥有足够的编写和发布功能的文字编辑器。它支持基本的格式和样式，并且你可以将你的文字以 Markdown、ODT、LaTeX 或者 HTML 等格式导出。

### 拼写检查

每个写作者在完成他们的工作前至少要（或者说应该要）进行一次拼写检查。为什么呢？在写作的世界里有个永恒的定律，无论你检查了多少次手稿，拼写错误和错字依然会存在。

我曾经详细[介绍](https://opensource.com/article/18/2/how-check-spelling-linux-command-line-aspell)过我最喜欢的命令行拼写检查工具 [GNU Aspell](http://aspell.net/)。交互式检测文本文档的 Aspell 不仅能够高亮显示拼写错误还能在拼写错误的上方提供正确的拼写建议。Aspell 在进行拼写检查时同样能够忽略许多语法标记。

另一个够老但仍然有用的代替品是 [Ispell](https://www.cs.hmc.edu/%7Egeoff/ispell.html)。虽然它比 Aspell 稍慢一点，但它们都以相同的方式工作。当你在你的文本文件上工作时，Ispell 将提供正确的建议。Ispell 同样也对英语以外的语言提供了良好的支持。

### 文章 linter

软件开发人员使用 [linter](https://en.wikipedia.org/wiki/Lint_(software)) 来检查他们的代码是否存在错误或者 bug。同样也有用于检查文本样式或语法错误的 linter；而该命令行工具会认为这些错误是样式元素。任何写作者都可以（也应该）使用它，一个文章 linter 对于要求文档风格和样式一致的文档团队项目而言尤其有用。

[Proselint](http://proselint.com/) 是一款全能的实时检查工具。它会找出行话、大话、不正确日期和时间格式、滥用的术语[等等](http://proselint.com/checks/)。它也很容易运行并忽略文本中的标记。

[Alex](https://github.com/get-alex/alex) 是一个简单但有用的文章 linter。 对明文文本或者格式为 Markdown 或 HTML 的文档使用它。Alex 会对“性别偏好、极端主义、种族相关、宗教，或者文章中其他不平等的措辞”产生警告。如果你想要试试看 Alex，这里有一个在线 [demo](https://alexjs.com/#demo)。

### 其他工具

有时候你找不到一个单词的恰当的同义词。但你不需要去呆板的词库中抓取或者去专门的网站完善你的单词完整。仅仅需要对你想要替换的单词运行 [Aiksaurus](http://aiksaurus.sourceforge.net/)，然后它就会为你完成这个工作。但是，这个程序最大的缺点是它只支持英语。

即使是只会很少（甚至只有一项）技术技能的写作者都能接受 [Markdown](https://en.wikipedia.org/wiki/Markdown) 来快速而简单地格式化他们的作品。但是，有时候你也需要将使用 Markdown 格式的文件转换成其他格式。这就是 [Pandoc](https://pandoc.org) 的用武之地。你可以用它来将你的文档转换成 HTML、Word、LibreOffice Writer、LaTeX、EPUB 以及其他格式。你甚至可以用 Pandoc 来生成书籍和[研究论文](https://opensource.com/article/18/9/pandoc-research-paper)。

你有最喜欢的命令行写作工具吗？在社区发表评论分享它吧。

---

via: <https://opensource.com/article/18/11/command-line-tools-writers>

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

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


Bash 中使用控制运算符连接命令

Summary: 在命令行中，使用控制运算符为复合命令添加逻辑。

在命令行中，使用控制运算符为复合命令添加逻辑。

>
> 在命令行中，使用控制运算符为复合命令添加逻辑。
>
>
>

![](/data/attachment/album/201812/13/234800qr3691m74995qz56.jpg)

经常会使用一些简单的复合指令，比如说在一个命令行中连接几个命令。这些命令使用分号分隔，表示一个命令结束。为了在一个命令行中创建一系列简单的 shell 命令，只需要使用分号把每一条命令分隔开，就像下面这样：

```
command1 ; command2 ; command3 ; command4 ;
```

最后一个分号你可以不用添加，因为当你按下回车键时就表示一个命令的结束，但是为了和其它的保持一致，还是建议加上比较好。

所有的命令执行都没有什么问题 —— 只要没有什么意外发生。但是当出问题时到底发生了什么呢？我们可以预测，并且通过 Bash 中内置的 `&&` 和 `||` 运算符跟踪这些错误。这两个控制运算符提供了一些流控制，可以让我们改变代码执行队列的顺序。分号和换行符也被认为是 Bash 的控制运算符。

`&&` 运算符意义简单来说就是“如果 `command1` 执行成功，就接着执行 `command2`。”如果 `command1` 因为任何原因执行失败，那么 `command2` 将不执行。这个语法看下来像这样：

```
command1 && command2
```

这样写是允许的，因为每一个命令都会返回一个值（RC）给 shell 来表示这个命令在执行的过程中是否执行成功或者失败。通常，返回值是 `0` 表示成功，而一个正数值表示不同种类的错误。有一些系统管理工具仅仅返回一个 `1` 来表示所有的错误，但是也有很多工具使用其它的正数的返回值来表示各种类型错误。

我们可以很容易的使用脚本来检查 shell 变量 `$?`，可以通过命令列表中的下一个命令，或者可以直接使用系统管理工具检查。我们一起来看这些返回值。运行一个简单的命令然后立即检查它的返回值，这个返回值始终是属于最后一个运行的命令。

```
[student@studentvm1 ~]$ ll ; echo "RC = $?"
total 284
-rw-rw-r--  1 student student   130 Sep 15 16:21 ascii-program.sh
drwxrwxr-x  2 student student  4096 Nov 10 11:09 bin
<snip>
drwxr-xr-x. 2 student student  4096 Aug 18 10:21 Videos
RC = 0
[student@studentvm1 ~]$
```

这个返回值是 `0`，表示这个命令执行成功了。现在尝试使用同样的命令在一些我们没有权限的目录上。

```
[student@studentvm1 ~]$ ll /root ; echo "RC = $?"
ls: cannot open directory '/root': Permission denied
RC = 2
[student@studentvm1 ~]$
```

这个返回值的含义可以在 [ls 命令的 man 页面](http://man7.org/linux/man-pages/man1/ls.1.html) 中找到。

现在我们来试试 `&&` 这个控制运算符，因为它也可能会被用在一个命令行程序中。我们将从一个简单的示例开始：创建一个新目录，如果创建成功就在这个目录中创建一个文件。

我们需要一个目录可以创建其它的目录。首先，在你的家目录中创建一个临时的目录用来做测试。

```
[student@studentvm1 ~]$ cd ; mkdir testdir
```

在 `~/testdir` 中新建一个目录，这也应该是一个空目录，因为是你刚刚创建的，然后创建一个新的空文件在这个新目录中。下面的命令可以做这些事情。

```
[student@studentvm1 ~]$ mkdir ~/testdir/testdir2 && touch ~/testdir/testdir2/testfile1
[student@studentvm1 ~]$ ll ~/testdir/testdir2/
total 0
-rw-rw-r-- 1 student student 0 Nov 12 14:13 testfile1
[student@studentvm1 ~]$
```

我们看到一切都运行得很好，因为 `testdir` 目录是访问且可写的。然后我们改变 `testdir` 目录的权限，让用户 `student` 不再具有访问的权限。操作如下：

```
[student@studentvm1 ~]$ chmod 076 testdir ; ll | grep testdir
d---rwxrw-. 3 student student  4096 Nov 12 14:13 testdir
[student@studentvm1 ~]$
```

在长列表（`ll`）命令后面使用 `grep` 命令来列出 `testdir` 目录。你可以看到用户 `student` 不再有 `testdir` 目录的访问权限。现在我们像之前一样运行同样的命令，但是在 `testdir` 目录中创建的是一个不同的目录。

```
[student@studentvm1 ~]$ mkdir ~/testdir/testdir3 && touch ~/testdir/testdir3/testfile1
mkdir: cannot create directory ‘/home/student/testdir/testdir3’: Permission denied
[student@studentvm1 ~]$
```

尽管我们也同样得到了一个错误的消息，但 `&&` 控制运算符阻止了 `touch` 命令的运行，因为在创建 `testdir3` 目录的时候发生了错误。通过这种复合的流控制可以阻止一些错误的发生使事情变乱。但是这样看起来变得稍微复杂了一些。

`||` 控制运算符允许添加另一个命令，这个命令在初始程序语句返回值大于 0 时执行。

```
[student@studentvm1 ~]$ mkdir ~/testdir/testdir3 && touch ~/testdir/testdir3/testfile1 || echo "An error occurred while creating the directory."
mkdir: cannot create directory ‘/home/student/testdir/testdir3’: Permission denied
An error occurred while creating the directory.
[student@studentvm1 ~]$
```

当我们使用 `&&` 和 `||` 控制运算符时，使用流控制的复合命令的语法格式通常是下面这样的形式。

```
preceding commands ; command1 && command2 || command3 ; following commands
```

使用控制运算符的复合命令可以在其它命令之前或者之后，这些和控制运算符流控制有关系，但是不受控制运算符流控制的影响。如果不考虑复合命令的流控制中发生的任何事情那么所有的命令都将执行。

当程序出问题时，这些流控制运算符使得在命令中处理出错和通知我们变得更有效率。我直接在命令行中使用它们，也在脚本中使用。

你可以以 root 用户的身份来删除这个目录和它里面的内容。

```
[root@studentvm1 ~]# rm -rf /home/student/testdir
```

你是怎样使用 Bash 控制运算符的呢？在评论区中告诉我们。

---

via: <https://opensource.com/article/18/11/control-operators-bash-shell>

作者：[David Both](https://opensource.com/users/dboth) 选题：[lujun9972](https://github.com/lujun9972) 译者：[Jamskr](https://github.com/Jamskr) 校对：[wxy](https://github.com/wxy)

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


一种新的安全检测的方法

Summary: 不要只测试已有系统，强安全要求更积极主动的策略。

不要只测试已有系统，强安全要求更积极主动的策略。

>
> 不要只测试已有系统，强安全要求更积极主动的策略。
>
>
>

![](/data/attachment/album/201812/14/123745dk16klj99jpkjw2w.png)

我们当中有多少人曾说出过下面这句话：“我希望这能起到作用！”？

毫无疑问，我们中的大多数人可能都不止一次地说过这句话。这句话不是用来激发信心的，相反它揭示了我们对自身能力和当前正在测试的功能的怀疑。不幸的是，这句话非常好地描述了我们传统的安全模型。我们的运营基于这样的假设，并希望我们实施的控制措施 —— 从 web 应用的漏扫到终端上的杀毒软件 —— 防止恶意的病毒和软件进入我们的系统，损坏或偷取我们的信息。

渗透测试通过积极地尝试侵入网络、向 web 应用注入恶意代码或者通过发送钓鱼邮件来传播病毒等等这些步骤来避免我们对假设的依赖。由于我们在不同的安全层面上来发现和渗透漏洞，手动测试无法解决漏洞被主动打开的情况。在安全实验中，我们故意在受控的情形下创造混乱，模拟事故的情形，来客观地检测我们检测、阻止这类问题的能力。

>
> “安全实验为分布式系统的安全性实验提供了一种方法，以建立对抗恶意攻击的能力的信心。”
>
>
>

在分布式系统的安全性和复杂性方面，需要反复地重申混沌工程界的一句名言，“希望不是一种有效的策略”。我们多久会主动测试一次我们设计或构建的系统，来确定我们是否已失去对它的控制？大多数组织都不会发现他们的安全控制措施失效了，直到安全事件的发生。我们相信“安全事件不是侦察措施”，而且“希望不要出事也不是一个有效的策略”应该是 IT 专业人士执行有效安全实践的口号。

行业在传统上强调预防性的安全措施和纵深防御，但我们的任务是通过侦探实验来驱动对安全工具链的新知识和见解。因为过于专注于预防机制，我们很少尝试一次以上地或者年度性地手动测试要求的安全措施，来验证这些控件是否按设计的那样执行。

随着现代分布式系统中的无状态变量的不断改变，人们很难充分理解他们的系统的行为，因为会随时变化。解决这个问题的一种途径是通过强大的系统性的设备进行检测，对于安全性检测，你可以将这个问题分成两个主要方面：**测试**，和我们称之为**实验**的部分。测试是对我们已知部分的验证和评估，简单来说，就是我们在开始找之前，要先弄清楚我们在找什么。另一方面，实验是去寻找获得我们之前并不清楚的见解和知识。虽然测试对于一个成熟的安全团队来说是一项重要实践，但以下示例会有助于进一步地阐述两者之间的差异，并对实验的附加价值提供一个更为贴切的描述。

### 示例场景：精酿啤酒

思考一个用于接收精酿啤酒订单的 web 服务或者 web 应用。

这是这家精酿啤酒运输公司的一项重要服务，这些订单来自客户的移动设备、网页，和通过为这家公司精酿啤酒提供服务的餐厅的 API。这项重要服务运行在 AWS EC2 环境上，并且公司认为它是安全的。这家公司去年成功地通过了 PCI 规则，并且每年都会请第三方进行渗透测试，所以公司认为这个系统是安全的。

这家公司有时一天两次部署来进行 DevOps 和持续交付工作，公司为其感到自豪。

在了解了混沌工程和安全实验方面的东西后，该公司的开发团队希望能确定，在一个连续不断的基础上，他们的安全系统对真实世界事件的有效性和快速恢复性怎么样。与此同时，确保他们不会把安全控件不能检测到的新问题引入到系统中。

该团队希望能小规模地通过评估端口安全和防火墙设置来让他们能够检测、阻止和警告他们 EC2 安全组上端口设置的错误配置更改。

* 该团队首先对他们正常状态下的假设进行总结。
* 在 EC2 实例里为端口安全进行一个假设。
* 为未认证的端口改变实验选择和配置 YAML 文件。
* 该配置会从已选择的目标中随机指定对象，同时端口的范围和数量也会被改变。
* 团队还会设置进行实验的时间并缩小爆破攻击的范围，来确保对业务的影响最小。
* 对于第一次测试，团队选择在他们的测试环境中运行实验并运行一个单独的测试。
* 在真实的<ruby> 游戏日 <rt>  Game Day </rt></ruby>风格里，团队在预先计划好的两个小时的窗口期内，选择<ruby> 灾难大师 <rt>  Master of Disaster </rt></ruby>来运行实验。在那段窗口期内，灾难大师会在 EC2 实例安全组中的一个实例上执行这次实验。
* 一旦游戏日结束，团队就会开始进行一个彻底的、免于指责的事后练习。它的重点在于针对稳定状态和原始假设的实验结果。问题会类似于下面这些：

### 事后验证问题

* 防火墙是否检测到未经授权的端口更改？
* 如果更改被检测到，更改是否会被阻止？
* 防火墙是否会将有用的日志信息记录到日志聚合工具中？
* SIEM 是否会对未经授权的更改发出警告？
* 如果防火墙没有检测到未经授权的更改，那么配置的管理工具是否发现了这次更改？
* 配置管理工具是否向日志聚合工具报告了完善的信息？
* SIEM 最后是否进行了关联报警？
* 如果 SIEM 发出了警报，安全运营中心是否能收到这个警报？
* 获得警报的 SOC 分析师是否能对警报采取措施，还是缺少必要的信息？
* 如果 SOC 确定警报是真实的，那么安全事件响应是否能简单地从数据中进行分类活动？

我们系统中对失败的承认和预期已经开始揭示我们对系统工作的假设。我们的使命是利用我们所学到的，并更加广泛地应用它。以此来真正主动地解决安全问题，来超越当前传统主流的被动处理问题的安全模型。

随着我们继续在这个新领域内进行探索，我们一定会发布我们的研究成果。如果您有兴趣想了解更多有关研究的信息或是想参与进来，请随时联系 Aaron Rinehart 或者 Grayson Brewer。

特别感谢 Samuel Roden 对本文提供的见解和想法。

* [看我们相关的文章：是否需要 DevSecOps 这个词？](https://opensource.com/article/18/4/devsecops)

---

via: <https://opensource.com/article/18/4/new-approach-security-instrumentation>

作者：[Aaron Rinehart](https://opensource.com/users/aaronrinehart) 选题：[lujun9972](https://github.com/lujun9972) 译者：[hopefully2333](https://github.com/hopefully2333) 校对：[wxy](https://github.com/wxy)

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


在 Linux 命令行上拥有一头奶牛

Summary: 使用 cowsay 实用程序将牛的话语带到你的终端输出。

使用 cowsay 实用程序将牛的话语带到你的终端输出。

>
> 使用 cowsay 实用程序将牛的话语带到你的终端输出。
>
>
>

![](/data/attachment/album/201812/14/132737a4vjqeyp1ndm41vw.png)

欢迎来到 Linux 命令行玩具第四天。如果这是你第一次访问这个系列，你可能会问自己，什么是命令行玩具。我们也在考虑这一点，但是一般来说，这可能是一个游戏，或者任何简单的娱乐，可以帮助你在终端玩得开心。

你们中的一些人会见过我们之前的选中的各种玩具，但是我们希望至少有一个对每个人来说都是新的。因为几乎所有我告诉他这个系列的人都已经问过它了，所以今天的选中的玩具是必须提及的。

你也不会认为我们会在不提及 `cowsay` 的情况下完成这个系列，对吧？

`cowsay` 是一个神奇的实用程序，它将文本作为 ASCII 艺术牛的讲话文本输出。

你可能会发现 `cowsay` 打包在你的默认存储库中，甚至可能已经安装了。对我来说，在 Fedora，像这样安装:

```
$ sudo dnf install -y cowsay
```

然后，用 `cowsay` 调用它，然后是你的消息。也许你想到昨天我们谈到的 [fortune 应用](https://opensource.com/article/18/12/linux-toy-fortune) 连接起来。

```
$ fortune | cowsay
 _________________________________________
/ If at first you do succeed, try to hide \
\ your astonishment.                      /
 -----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
```

就这样！`cowsay` 还有点小变体，称为 cow 文件，通常可以在 `/usr/share/cowsay` 找到 ，要查看系统上可用的 cow 文件，请在 `cowsay` 之后使用 `-l` 。然后，用 `-f` 试试其中之一。

```
$ cowsay -f dragon "Run for cover, I feel a sneeze coming on."
 _______________________________________
/ Run for cover, I feel a sneeze coming \
\ on.                                   /
 ---------------------------------------
      \                    / \  //\
       \    |\___/|      /   \//  \\
            /0  0  \__  /    //  | \ \
           /     /  \/_/    //   |  \  \
           @_^_@'/   \/_   //    |   \   \
           //_^_/     \/_ //     |    \    \
        ( //) |        \///      |     \     \
      ( / /) _|_ /   )  //       |      \     _\
    ( // /) '/,_ _ _/  ( ; -.    |    _ _\.-~        .-~~~^-.
  (( / / )) ,-{        _      `-.|.-~-.           .~         `.
 (( // / ))  '/\      /                 ~-. _ .-~      .-~^-.  \
 (( /// ))      `.   {            }                   /      \  \
  (( / ))     .----~-.\        \-'                 .~         \  `. \^-.
             ///.----..>        \             _ -~             `.  ^-`  ^-_
               ///-._ _ _ _ _ _ _}^ - - - - ~                     ~-- ,.-~
                                                                  /.-~
```

我对 `cowsay` 的真正不满是，我今天没有足够的时间来为牛的挤奶 —— 一语双关。牛排价格太高了，我只是开个玩笑。

更严重的是，我已经完全忘记了 `cowsay` 直到我在学习 Ansible 的剧本时再次遇到它。如果你碰巧安装了 `cowsay`，当你运行Ansible 的剧本时，你会从一队奶牛那里获得输出。例如，运行这个剧本：

```
- hosts:
    - localhost
  tasks:
    - action: ping
```

可能会给你以下信息：

```
$ ansible-playbook playbook.yml
 __________________
< PLAY [localhost] >
 ------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

 ________________________
< TASK [Gathering Facts] >
 ------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

ok: [localhost]
 _____________
< TASK [ping] >
 -------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

ok: [localhost]
 ____________
< PLAY RECAP >
 ------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

localhost                  : ok=2    changed=0    unreachable=0    failed=0
```

`cowsay` 在 GPLv3 许可证下可用，您可以在 GitHub 上找到 它的 Perl [源代码](https://github.com/tnalpgge/rank-amateur-cowsay)。我也见过其他语言的版本，所以可以看看其他变体；例如，这是 [R 语言版](https://github.com/sckott/cowsay)。用你选择的语言实现你自己的版本可能是一项有趣的编程学习任务。

既然讲完了 `cowsay`，我们可以去更绿色的牧场了。

你有希望我来介绍的喜欢的命令行玩具吗？这个系列的排期大部分都填好了，但我还有一些空位方。在下面的评论中让我知道，我会来看看。如果有空间，我会尝试把它包括进去。如果没有，但是我收到了一些好的意见，我在结尾提及。

看看昨天的玩具，[如何给你的 Linux 终端带来好运](https://opensource.com/article/18/12/linux-toy-fortune)，明天再来看看另一个！

---

via: <https://opensource.com/article/18/12/linux-toy-cowsay>

作者：[Jason Baker](https://opensource.com/users/jason-baker) 选题：[lujun9972](https://github.com/lujun9972) 译者：[heguangzhi](https://github.com/heguangzhi) 校对：[wxy](https://github.com/wxy)

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


你所不知道的知识共享（CC）

Summary: 知识共享为艺术家提供访问权限和原始素材。大公司也从中受益。

知识共享为艺术家提供访问权限和原始素材。大公司也从中受益。

>
> 知识共享为艺术家提供访问权限和原始素材。大公司也从中受益。
>
>
>

![](/data/attachment/album/201812/14/182859vzilqpgm4bvdktck.png)

我毕业于电影学院，毕业后在一所电影学校教书，之后进入一家主流电影工作室，我一直在从事电影相关的工作。创意产业的方方面面面临着同一个问题：创作者需要原材料。有趣的是，自由文化运动提出了解决方案，具体来说是在自由文化运动中出现的<ruby> 知识共享 <rt>  Creative Commons </rt></ruby>组织。

### 知识共享能够为我们提供展示片段和小样

和其他事情一样，创造力也需要反复练习。幸运的是，在我刚开始接触电脑时，就在一本关于渲染工场的专业杂志中接触到了开源这个存在。当时我并不理解所谓的“开源”是什么，但我知道只有开源工具能帮助我在领域内稳定发展。对我来说，知识共享也是如此。知识共享可以为艺术家们提供充满丰富艺术资源的工作室。

我在电影学院任教时，经常需要给学生们准备练习编辑、录音、拟音、分级、评分的示例录像。在 Jim Munroe 的独立作品 [Infest Wisely](http://infestwisely.com) 中和 [Vimeo](https://vimeo.com/creativecommons) 上的知识共享内容里我总能找到我想要的。这些逼真的镜头覆盖内容十分广泛，从独立制作到昂贵的高品质的升降镜头（一般都会用无人机代替）都有。

![](/data/attachment/album/201812/14/182902ijudjzfjd4r5pncp.png)

对实验主义艺术来说，确有无尽可能。知识共享提供了丰富的素材，这些材料可以用来整合、混剪等等，可以满足一位视觉先锋能够想到的任何用途。

在接触知识共享之前，如果我想要使用写实镜头，如果在大学，只能用之前的学生和老师拍摄的或者直接使用版权库里的镜头，或者使用有受限的版权保护的镜头。

### 坚守版权的底线很重要

知识共享同样能够创造经济效益。在某大型计算机公司的渲染工场工作时，我负责在某些硬件设施上测试渲染的运行情况，而这个测试时刻面临着被搁置的风险。做这些测试时，我用的都是[大雄兔](https://peach.blender.org/)的资源，因为这个电影和它的组件都是可以免费使用和分享的。如果没有这个小短片，在接触写实资源之前我都没法完成我的实验，因为对于一个计算机公司来说，雇佣一只 3D 艺术家来按需布景是不太现实的。

令我震惊的是，与开源类似，知识共享已经用我们难以想象的方式支撑起了大公司。知识共享的使用可能会也可能不会影响公司的日常流程，但它填补了不足，让工作流程顺利进行。我没见到谁在他们的书中将流畅工作归功于知识共享的应用，但它确实无处不在。

![](/data/attachment/album/201812/14/182905yrp2t2n88fyzei2y.png)

我也见过一些开放版权的电影，比如[辛特尔](https://durian.blender.org/)，在最近的电视节目中播放了它的短片，电视的分辨率已经超过了标准媒体。

### 知识共享可以提供大量原材料

艺术家需要原材料。画家需要颜料、画笔和画布。雕塑家需要陶土和工具。数字内容编辑师需要数字内容，无论它是剪贴画还是音效或者是电子游戏里的现成的精灵。

数字媒介赋予了人们超能力，让一个人就能完成需要一组人员才能完成的工作。事实上，我们大部分都好高骛远。我们想做高大上的项目，想让我们的成果不论是视觉上还是听觉上都无与伦比。我们想塑造的是宏大的世界，紧张的情节，能引起共鸣的作品，但我们所拥有的时间精力和技能与之都不匹配，达不到想要的效果。

是知识共享再一次拯救了我们，在 [Freesound.org](http://freesound.org)、 [Openclipart.org](http://openclipart.org)、 [OpenGameArt.org](http://opengameart.org) 等等网站上都有大量的开放版权艺术材料。通过知识共享，艺术家可以使用各种他们自己没办法创造的原材料，来完成他们原本完不成的工作。

最神奇的是，不用自己投资，你放在网上给大家使用的原材料就能变成精美的作品，而这是你从没想过的。我在知识共享上面分享了很多音乐素材，它们现在用于无数的专辑和电子游戏里。有些人用了我的材料会通知我，有些是我自己发现的，所以这些材料的应用可能比我知道的还有多得多。有时我会偶然看到我亲手画的标志出现在我从没听说过的软件里。我见到过我为 [Opensource.com](https://opensource.com/) 写的文章在别处发表，有的是论文的参考文献，白皮书或者参考资料中。

### 知识共享所代表的自由文化也是一种文化

“自由文化”这个说法过于累赘，文化，从概念上来说，是一个有机的整体。在这种文化中社会逐渐成长发展，从一个人到另一个。它是人与人之间的互动和思想交流。自由文化是自由缺失的现代世界里的特殊产物。

如果你也想对这样的局限进行反抗，想把你的思想、作品、你自己的文化分享给全世界的人，那么就来和我们一起，使用知识共享吧！

---

via: <https://opensource.com/article/18/1/creative-commons-real-world>

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

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


使用 gorilla/mux 进行 HTTP 请求路由和验证

>
> gorilla/mux 包以直观的 API 提供了 HTTP 请求路由、验证和其它服务。
>
>
>

![](/data/attachment/album/201812/15/091045efoxfuuutxkos2xo.png)

Go 网络库包括 `http.ServeMux` 结构类型，它支持 HTTP 请求多路复用（路由）：Web 服务器将托管资源的 HTTP 请求与诸如 `/sales4today` 之类的 URI 路由到代码处理程序；处理程序在发送 HTTP 响应（通常是 HTML 页面）之前执行适当的逻辑。 这是该体系的草图：

```
             +-----------+     +--------+     +---------+
HTTP 请求---->| web 服务器 |---->| 路由   |---->| 处理程序  |
             +-----------+     +--------+     +---------+
```

调用 `ListenAndServe` 方法后启动 HTTP 服务器：

```
http.ListenAndServe(":8888", nil) // args: port & router
```

第二个参数 `nil` 意味着 `DefaultServeMux` 用于请求路由。

`gorilla/mux` 库包含 `mux.Router` 类型，可替代 `DefaultServeMux` 或自定义请求多路复用器。 在 `ListenAndServe` 调用中，`mux.Router` 实例将代替 `nil` 作为第二个参数。 下面的示例代码很好的说明了为什么 `mux.Router`如此吸引人：

### 1、一个简单的 CRUD web 应用程序

crud web 应用程序（见下文）支持四种 CRUD（创建/读取/更新/删除）操作，它们分别对应四种 HTTP 请求方法：POST、GET、PUT 和 DELETE。 在这个 CRUD 应用程序中，所管理的资源是套话与反套话的列表，每个都是套话及其反面的的套话，例如这对：

```
Out of sight, out of mind. Absence makes the heart grow fonder.
```

可以添加新的套话对，可以编辑或删除现有的套话对。

CRUD web 应用程序：

```
package main

import (
   "gorilla/mux"
   "net/http"
   "fmt"
   "strconv"
)

const GETALL string = "GETALL"
const GETONE string = "GETONE"
const POST string   = "POST"
const PUT string    = "PUT"
const DELETE string = "DELETE"

type clichePair struct {
   Id      int
   Cliche  string
   Counter string
}

// Message sent to goroutine that accesses the requested resource.
type crudRequest struct {
   verb     string
   cp       *clichePair
   id       int
   cliche   string
   counter  string
   confirm  chan string
}

var clichesList = []*clichePair{}
var masterId = 1
var crudRequests chan *crudRequest

// GET /
// GET /cliches
func ClichesAll(res http.ResponseWriter, req *http.Request) {
   cr := &crudRequest{verb: GETALL, confirm: make(chan string)}
   completeRequest(cr, res, "read all")
}

// GET /cliches/id
func ClichesOne(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cr := &crudRequest{verb: GETONE, id: id, confirm: make(chan string)}
   completeRequest(cr, res, "read one")
}

// POST /cliches
func ClichesCreate(res http.ResponseWriter, req *http.Request) {
   cliche, counter := getDataFromRequest(req)
   cp := new(clichePair)
   cp.Cliche = cliche
   cp.Counter = counter
   cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
   completeRequest(cr, res, "create")
}

// PUT /cliches/id
func ClichesEdit(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cliche, counter := getDataFromRequest(req)
   cr := &crudRequest{verb: PUT, id: id, cliche: cliche, counter: counter, confirm: make(chan string)}
   completeRequest(cr, res, "edit")
}

// DELETE /cliches/id
func ClichesDelete(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cr := &crudRequest{verb: DELETE, id: id, confirm: make(chan string)}
   completeRequest(cr, res, "delete")
}

func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr
   msg := <-cr.confirm
   res.Write([]byte(msg))
   logIt(logMsg)
}

func main() {
   populateClichesList()

   // From now on, this gorountine alone accesses the clichesList.
   crudRequests = make(chan *crudRequest, 8)
   go func() { // resource manager
      for {
         select {
         case req := <-crudRequests:
         if req.verb == GETALL {
            req.confirm<-readAll()
         } else if req.verb == GETONE {
            req.confirm<-readOne(req.id)
         } else if req.verb == POST {
            req.confirm<-addPair(req.cp)
         } else if req.verb == PUT {
            req.confirm<-editPair(req.id, req.cliche, req.counter)
         } else if req.verb == DELETE {
            req.confirm<-deletePair(req.id)
         }
      }
   }()
   startServer()
}

func startServer() {
   router := mux.NewRouter()

   // Dispatch map for CRUD operations.
   router.HandleFunc("/", ClichesAll).Methods("GET")
   router.HandleFunc("/cliches", ClichesAll).Methods("GET")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")

   router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesDelete).Methods("DELETE")

   http.Handle("/", router) // enable the router

   // Start the server.
   port := ":8888"
   fmt.Println("\nListening on port " + port)
   http.ListenAndServe(port, router); // mux.Router now in play
}

// Return entire list to requester.
func readAll() string {
   msg := "\n"
   for _, cliche := range clichesList {
      next := strconv.Itoa(cliche.Id) + ": " + cliche.Cliche + "  " + cliche.Counter + "\n"
      msg += next
   }
   return msg
}

// Return specified clichePair to requester.
func readOne(id int) string {
   msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"

   index := findCliche(id)
   if index >= 0 {
      cliche := clichesList[index]
      msg = "\n" + strconv.Itoa(id) + ": " + cliche.Cliche + "  " + cliche.Counter + "\n"
   }
   return msg
}

// Create a new clichePair and add to list
func addPair(cp *clichePair) string {
   cp.Id = masterId
   masterId++
   clichesList = append(clichesList, cp)
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}

// Edit an existing clichePair
func editPair(id int, cliche string, counter string) string {
   msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"
   index := findCliche(id)
   if index >= 0 {
      clichesList[index].Cliche = cliche
      clichesList[index].Counter = counter
      msg = "\nCliche edited: " + cliche + " " + counter + "\n"
   }
   return msg
}

// Delete a clichePair
func deletePair(id int) string {
   idStr := strconv.Itoa(id)
   msg := "\n" + "Bad Id: " + idStr + "\n"
   index := findCliche(id)
   if index >= 0 {
      clichesList = append(clichesList[:index], clichesList[index + 1:]...)
      msg = "\nCliche " + idStr + " deleted\n"
   }
   return msg
}

//*** utility functions
func findCliche(id int) int {
   for i := 0; i < len(clichesList); i++ {
      if id == clichesList[i].Id {
         return i;
      }
   }
   return -1 // not found
}

func getIdFromRequest(req *http.Request) int {
   vars := mux.Vars(req)
   id, _ := strconv.Atoi(vars["id"])
   return id
}

func getDataFromRequest(req *http.Request) (string, string) {
   // Extract the user-provided data for the new clichePair
   req.ParseForm()
   form := req.Form
   cliche := form["cliche"][0]    // 1st and only member of a list
   counter := form["counter"][0]  // ditto
   return cliche, counter
}

func logIt(msg string) {
   fmt.Println(msg)
}

func populateClichesList() {
   var cliches = []string {
      "Out of sight, out of mind.",
      "A penny saved is a penny earned.",
      "He who hesitates is lost.",
   }
   var counterCliches = []string {
      "Absence makes the heart grow fonder.",
      "Penny-wise and dollar-foolish.",
      "Look before you leap.",
   }

   for i := 0; i < len(cliches); i++ {
      cp := new(clichePair)
      cp.Id = masterId
      masterId++
      cp.Cliche = cliches[i]
      cp.Counter = counterCliches[i]
      clichesList = append(clichesList, cp)
   }
}
```

为了专注于请求路由和验证，CRUD 应用程序不使用 HTML 页面作为请求响应。 相反，请求会产生明文响应消息：套话对的列表是对 GET 请求的响应，确认新的套话对已添加到列表中是对 POST 请求的响应，依此类推。 这种简化使得使用命令行实用程序（如 [curl](https://curl.haxx.se/)）可以轻松地测试应用程序，尤其是 `gorilla/mux` 组件。

`gorilla/mux` 包可以从 [GitHub](https://github.com/gorilla/mux) 安装。 CRUD app 无限期运行；因此，应使用 `Control-C` 或同等命令终止。 CRUD 应用程序的代码，以及自述文件和简单的 curl 测试，可以在[我的网站](http://condor.depaul.edu/mkalin)上找到。

### 2、请求路由

`mux.Router` 扩展了 REST 风格的路由，它赋给 HTTP 方法（例如，GET）和 URL 末尾的 URI 或路径（例如 `/cliches`）相同的权重。 URI 用作 HTTP 动词（方法）的名词。 例如，在HTTP请求中有一个起始行，例如：

```
GET /cliches
```

意味着得到所有的套话对，而一个起始线，如：

```
POST /cliches
```

意味着从 HTTP 正文中的数据创建一个套话对。

在 CRUD web 应用程序中，有五个函数充当 HTTP 请求的五种变体的请求处理程序：

```
ClichesAll(...)    # GET: 获取所有的套话对
ClichesOne(...)    # GET: 获取指定的套话对
ClichesCreate(...) # POST: 创建新的套话对
ClichesEdit(...)   # PUT: 编辑现有的套话对
ClichesDelete(...) # DELETE: 删除指定的套话对
```

每个函数都有两个参数：一个 `http.ResponseWriter` 用于向请求者发送一个响应，一个指向 `http.Request` 的指针，该指针封装了底层 HTTP 请求的信息。 使用 `gorilla/mux` 包可以轻松地将这些请求处理程序注册到Web服务器，并执行基于正则表达式的验证。

CRUD 应用程序中的 `startServer` 函数注册请求处理程序。 考虑这对注册，`router` 作为 `mux.Router` 实例：

```
router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
```

这些语句意味着对单斜线 `/` 或 `/cliches` 的 GET 请求应该路由到 `ClichesAll` 函数，然后处理请求。 例如，curl 请求（使用 `％` 作为命令行提示符）：

```
% curl --request GET localhost:8888/
```

会产生如下结果：

```
1: Out of sight, out of mind.  Absence makes the heart grow fonder.
2: A penny saved is a penny earned.  Penny-wise and dollar-foolish.
3: He who hesitates is lost.  Look before you leap.
```

这三个套话对是 CRUD 应用程序中的初始数据。

在这句注册语句中：

```
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
```

URI 是相同的（`/cliches`），但动词不同：第一种情况下为 GET 请求，第二种情况下为 POST 请求。 此注册举例说明了 REST 样式的路由，因为仅动词的不同就足以将请求分派给两个不同的处理程序。

注册中允许多个 HTTP 方法，尽管这会影响 REST 风格路由的精髓：

```
router.HandleFunc("/cliches", DoItAll).Methods("POST", "GET")
```

除了动词和 URI 之外，还可以在功能上路由 HTTP 请求。 例如，注册

```
router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")
```

要求对 POST 请求进行 HTTPS 访问以创建新的套话对。以类似的方式，注册可能需要具有指定的 HTTP 头元素（例如，认证凭证）的请求。

### 3、 Request validation

`gorilla/mux` 包采用简单，直观的方法通过正则表达式进行请求验证。 考虑此请求处理程序以获取一个操作：

```
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")
```

此注册排除了 HTTP 请求，例如：

```
% curl --request GET localhost:8888/cliches/foo
```

因为 foo 不是十进制数字。该请求导致熟悉的 404（未找到）状态码。 在此处理程序注册中包含正则表达式模式可确保仅在请求 URI 以十进制整数值结束时才调用 `ClichesOne` 函数来处理请求：

```
% curl --request GET localhost:8888/cliches/3  # ok
```

另一个例子，请求如下：

```
% curl --request PUT --data "..." localhost:8888/cliches
```

此请求导致状态代码为 405（错误方法），因为 /cliches URI 在 CRUD 应用程序中仅在 GET 和 POST 请求中注册。 像 GET 请求一样，PUT 请求必须在 URI 的末尾包含一个数字 id：

```
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
```

### 4、并发问题

`gorilla/mux` 路由器作为单独的 Go 协程执行对已注册的请求处理程序的每次调用，这意味着并发性被内置于包中。 例如，如果有十个同时发出的请求，例如

```
% curl --request POST --data "..." localhost:8888/cliches
```

然后 `mux.Router` 启动十个 Go 协程来执行 `ClichesCreate` 处理程序。

GET all、GET one、POST、PUT 和 DELETE 中的五个请求操作中，最后三个改变了所请求的资源，即包含套话对的共享 `clichesList`。 因此，CRUD app 需要通过协调对 `clichesList` 的访问来保证安全的并发性。 在不同但等效的术语中，CRUD app 必须防止 `clichesList` 上的竞争条件。 在生产环境中，可以使用数据库系统来存储诸如 `clichesList` 之类的资源，然后可以通过数据库事务来管理安全并发。

CRUD 应用程序采用推荐的Go方法来实现安全并发：

* 只有一个 Go 协程，资源管理器在 CRUD app `startServer` 函数中启动，一旦 Web 服务器开始侦听请求，就可以访问 `clichesList`。
* 诸如 `ClichesCreate` 和 `ClichesAll` 之类的请求处理程序向 Go 通道发送（指向）`crudRequest` 实例（默认情况下是线程安全的），并且资源管理器单独从该通道读取。 然后，资源管理器对 `clichesList` 执行请求的操作。

安全并发体系结构绘制如下：

```
            crudRequest                读/写

请求处理程序 -------------> 资源托管者 ------------> 套话列表

```

在这种架构中，不需要显式锁定 `clichesList`，因为一旦 CRUD 请求开始进入，只有一个 Go 协程（资源管理器）访问 `clichesList`。

为了使 CRUD 应用程序尽可能保持并发，在一方请求处理程序与另一方的单一资源管理器之间进行有效的分工至关重要。 在这里，为了审查，是 `ClichesCreate` 请求处理程序：

```
func ClichesCreate(res http.ResponseWriter, req *http.Request) {
   cliche, counter := getDataFromRequest(req)
   cp := new(clichePair)
   cp.Cliche = cliche
   cp.Counter = counter
   cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
   completeRequest(cr, res, "create")
}
```

`ClichesCreate` 调用实用函数 `getDataFromRequest`，它从 POST 请求中提取新的套话和反套话。 然后 `ClichesCreate` 函数创建一个新的 `ClichePair`，设置两个字段，并创建一个 `crudRequest` 发送给单个资源管理器。 此请求包括一个确认通道，资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成，因为尚未访问 `clichesList`。

请求处理程序调用实用程序函数，该函数从 POST 请求中提取新的套话和反套话。 然后，该函数创建一个新的，设置两个字段，并创建一个 crudRequest 发送到单个资源管理器。 此请求包括一个确认通道，资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成，因为尚未访问它。

`completeRequest` 实用程序函数在 `ClichesCreate` 函数和其他请求处理程序的末尾调用：

```
completeRequest(cr, res, "create") // shown above
```

通过将 `crudRequest` 放入 `crudRequests` 频道，使资源管理器发挥作用：

```
func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr          // 向资源托管者发送请求
   msg := <-cr.confirm       // 等待确认
   res.Write([]byte(msg))    // 向请求方发送确认
   logIt(logMsg)             // 打印到标准输出
}
```

对于 POST 请求，资源管理器调用实用程序函数 `addPair`，它会更改 `clichesList` 资源：

```
func addPair(cp *clichePair) string {
   cp.Id = masterId  // 分配一个唯一的 ID
   masterId++        // 更新 ID 计数器
   clichesList = append(clichesList, cp) // 更新列表
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}
```

资源管理器为其他 CRUD 操作调用类似的实用程序函数。 值得重复的是，一旦 Web 服务器开始接受请求，资源管理器就是唯一可以读取或写入 `clichesList` 的 goroutine。

对于任何类型的 Web 应用程序，`gorilla/mux` 包在简单直观的 API 中提供请求路由、请求验证和相关服务。 CRUD web 应用程序突出了软件包的主要功能。

---

via: <https://opensource.com/article/18/8/http-request-routing-validation-gorillamux>

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

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


创建一个容器化的机器学习模型

![](/data/attachment/album/201812/15/091829w45rgg1peoawzce7.jpg)

数据科学家在创建机器学习模型后，必须将其部署到生产中。要在不同的基础架构上运行它，使用容器并通过 REST API 公开模型是部署机器学习模型的常用方法。本文演示了如何在 [Podman](https://fedoramagazine.org/running-containers-with-podman/) 容器中使用 [Connexion](https://connexion.readthedocs.io/en/latest/) 推出使用 REST API 的 [TensorFlow](https://www.tensorflow.org) 机器学习模型。

### 准备

首先，使用以下命令安装 Podman：

```
sudo dnf -y install podman
```

接下来，为容器创建一个新文件夹并切换到该目录。

```
mkdir deployment_container && cd deployment_container
```

### TensorFlow 模型的 REST API

下一步是为机器学习模型创建 REST API。这个 [github 仓库](https://github.com/svenboesiger/titanic_tf_ml_model)包含一个预训练模型，以及能让 REST API 工作的设置。

使用以下命令在 `deployment_container` 目录中克隆它：

```
git clone https://github.com/svenboesiger/titanic_tf_ml_model.git
```

#### prediction.py 和 ml\_model/

[prediction.py](https://github.com/svenboesiger/titanic_tf_ml_model/blob/master/prediction.py) 能进行 Tensorflow 预测，而 20x20x20 神经网络的权重位于文件夹 [ml\_model/](https://github.com/svenboesiger/titanic_tf_ml_model/tree/master/ml_model/titanic) 中。

#### swagger.yaml

[swagger.yaml](https://github.com/svenboesiger/titanic_tf_ml_model/blob/master/swagger.yaml) 使用 [Swagger规范](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md) 定义 Connexion 库的 API。此文件包含让你的服务器提供输入参数验证、输出响应数据验证、URL 端点定义所需的所有信息。

额外地，Connexion 还将给你提供一个简单但有用的单页 Web 应用，它演示了如何使用 Javascript 调用 API 和更新 DOM。

```
swagger: "2.0"
info:
  description: This is the swagger file that goes with our server code
  version: "1.0.0"
  title: Tensorflow Podman Article
consumes:
  - "application/json"
produces:
  - "application/json"

basePath: "/"

paths:
  /survival_probability:
    post:
      operationId: "prediction.post"
      tags:
        - "Prediction"
      summary: "The prediction data structure provided by the server application"
      description: "Retrieve the chance of surviving the titanic disaster"
      parameters:
        - in: body
          name: passenger
          required: true
          schema:
            $ref: '#/definitions/PredictionPost'
      responses:
        '201':
          description: 'Survival probability of an individual Titanic passenger'

definitions:
  PredictionPost:
    type: object
```

#### server.py 和 requirements.txt

[server.py](https://github.com/svenboesiger/titanic_tf_ml_model/blob/master/server.py) 定义了启动 Connexion 服务器的入口点。

```
import connexion

app = connexion.App(__name__, specification_dir='./')

app.add_api('swagger.yaml')

if __name__ == '__main__':
 app.run(debug=True)
```

[requirements.txt](https://github.com/svenboesiger/titanic_tf_ml_model/blob/master/requirements.txt) 定义了运行程序所需的 python 包。

```
connexion
tensorflow
pandas
```

### 容器化！

为了让 Podman 构建映像，请在上面的准备步骤中创建的 `deployment_container` 目录中创建一个名为 `Dockerfile` 的新文件：

```
FROM fedora:28

# File Author / Maintainer
MAINTAINER Sven Boesiger <donotspam@ujelang.com>

# Update the sources
RUN dnf -y update --refresh

# Install additional dependencies
RUN dnf -y install libstdc++

RUN dnf -y autoremove

# Copy the application folder inside the container
ADD /titanic_tf_ml_model /titanic_tf_ml_model

# Get pip to download and install requirements:
RUN pip3 install -r /titanic_tf_ml_model/requirements.txt

# Expose ports
EXPOSE 5000

# Set the default directory where CMD will execute
WORKDIR /titanic_tf_ml_model

# Set the default command to execute
# when creating a new container
CMD python3 server.py
```

接下来，使用以下命令构建容器镜像：

```
podman build -t ml_deployment .
```

### 运行容器

随着容器镜像的构建和准备就绪，你可以使用以下命令在本地运行它：

```
podman run -p 5000:5000 ml_deployment
```

在 Web 浏览器中输入 [http://0.0.0.0:5000/ui](http://0.0.0.0:5000/) 访问 Swagger/Connexion UI 并测试模型：

![](/data/attachment/album/201812/15/091830ggg293pc3q19z377.png)

当然，你现在也可以在应用中通过 REST API 访问模型。

---

via: <https://fedoramagazine.org/create-containerized-machine-learning-model/>

作者：[Sven Bösiger](https://fedoramagazine.org/author/r00nz/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


在 Linux 中使用 SoundConverter 轻松转换音频文件格式

>
> 如果你正在寻找将音频文件格式转换为 wav、mp3、ogg 或任何其他格式，SoundConverter 是你在 Linux 中需要的工具。
>
>
>

![Audio Converter in Linux](/data/attachment/album/201812/15/150512n4pw4zibi0mnciwo.png)

最近我购买了一些没有 DRM 的音乐。我是从 [SaReGaMa](https://en.wikipedia.org/wiki/Saregama) 那里买的，这是一家印度历史最悠久，规模最大的音乐品牌。下载的文件采用高清质量的 WAV 格式。

不幸的是，Rhythmbox 无法播放 WAV。最重要的是，单个文件大小约为 70MB。想象一下，将这么大的音乐传输到智能手机。它会不必要地占用大量空间。

所以我认为是时候将 WAV 文件转换为 MP3 —— 这个长青且最流行的音乐文件格式。

为此，我需要一个在 Linux 中的音频转换器。在这个教程中，我将向你展示如何使用名为 SoundCoverter 的 GUI 工具轻松地将音频文件从一种格式转换为另一种格式。

### 在 Linux 中安装 SoundConverter

[SoundConverter](http://soundconverter.org/) 是一款流行的自由开源软件。它应该可以在大多数 Linux 发行版的官方仓库中找到。

Ubuntu/Linux Mint 用户只需在软件中心搜索 SoundConverter 并从那里安装即可。

![SoundConverter application in Software Center of Ubuntu](/data/attachment/album/201812/15/150513ulebo0bf63e3e9wf.png)

*SoundConverter 可以从软件中心安装*

或者，你可以使用命令行方式。在基于 Debian 和 Ubuntu 的系统中，你可以使用以下命令：

```
sudo apt install soundconverter
```

在 Arch、Fedora 和其他非基于 Debian 的发行版中，你可以使用你的发行版的软件中心或软件包管理器。

### 在 Linux 中使用 SoundConverter 转换音频文件格式

安装完 SoundConverter 后，在菜单中搜索并启动它。

默认界面看起来像这样，它不能比这简单：

![SoundConverter application interface in Linux](/data/attachment/album/201812/15/150515yihi5tiigt5kodd5.jpg)

*简单的界面*

转换音频文件格式只要选择文件并单击转换。

但是，我建议你至少在第一次运行时检查下默认设置。默认情况下，它会将音频文件转换为 OGG 文件格式，你可能不希望这样。

![Preferences in SoundConverter](/data/attachment/album/201812/15/150516l15c52135c5szy5z.jpg)

*可以在“首选项”中更改默认输出设置*

要更改默认输出设置，请单击界面上的“首选项”图标。你会在这里看到很多可更改的选择。

你可以更改默认输出格式、比特率、质量等。你还可以选择是否要将转换后的文件保存在与原始文件相同的文件夹中。

转换后还可以选择自动删除原始文件。我不认为你应该使用那个选项。

你还可以更改输出文件名。默认情况下，它只会更改后缀，但你也可以选择根据曲目编号、标题、艺术家等进行命名。为此，原始文件中应包含适当的元数据。

说到元数据，你听说过 [MusicBrainz Picard](https://itsfoss.com/musicbrainz-picard/) 吗？此工具可帮助你自动更新本地音乐文件的元数据。

### 总结

我之前用讨论过用一个小程序 [在 Linux 中录制音频](https://itsfoss.com/record-streaming-audio/)。这些很棒的工具通过专注某个特定的任务使得生活更轻松。你可以使用成熟和更好的音频编辑工具，如 [Audacity](https://www.audacityteam.org/)，但对于较小的任务，如转换音频文件格式，它可能用起来很复杂。

我希望你喜欢 SoundConverter。如果你使用其他工具，请在评论中提及，我会在 FOSS 中提及。使用开心！

---

via: <https://itsfoss.com/sound-converter-linux/>

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

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