第 1 章 GNU/Linux 教程

# 第 1 章 GNU/Linux 教程

我认为学习一个计算机系统，就像学习一门新的外语。虽然教程和文档是有帮助的，但你必须自己练习。为了帮助你平滑起步，我详细说明一些基本要点。

Debian GNU/Linux 中最强大的设计来自 Unix 操作系统，一个多用户多任务的操作系统。你必须学会利用这些特性以及 Unix 和 GNU/Linux 的相似性。

别回避面向 Unix 的文档，不要只是依赖于 GNU/Linux 文档，这样做会剥夺你了解许多有用的信息。

| 注意
如果你在任何类 Unix 系统中使用过一段时间的命令行工具，你可能已经掌握了这份文档中的内容。那请把它当做一个实战检验和回顾。

## 1.1. 控制台基础

### 1.1.1. shell 提示符

启动系统之后，如果你没有安装 GUI（例如GNOME 或者 KDE），那么你会看到字符登录界面。假设你的主机名为`foo`，那么登录提示符将如下所示。

如果你安装了一个 GUI 环境，那么你仍然能够用 Ctrl-Alt-F3 进入基于字符的登录提示符，同时你能通过 Ctrl-Alt-F2 回到 GUI 环境（更多详情请参阅下文 第 1.1.6 节 “虚拟控制台”）。

    foo login:

在登录提示符下，你输入你的用户名，例如 `penguin`，然后按回车键，接下来输入你的密码并再次按回车键。

| 注意
遵循 Unix 传统，Debian 系统下的用户名和密码是大小写敏感的。用户名通常由小写字母组成。第一个用户账号通常在安装期间进行创建。额外的用户账号由 root 用户用 adduser(8) 创建。

系统以保存在 "`/etc/motd`" 中的欢迎信息（Message Of The Day）来开始，同时显示一个命令提示符。

    Debian GNU/Linux 12 foo tty3

    foo login: penguin
    Password:

    Linux foo 6.5.0-0.deb12.4-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.5.10-1~bpo12+1 (2023-11-23) x86_64

    The programs included with the Debian GNU/Linux system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.

    Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
    permitted by applicable law.

    Last login: Wed Dec 20 09:39:00 JST 2023 on tty3
    foo:~$

现在，你就在 shell 下。shell 解析你的命令。

### 1.1.2. GUI 下的 shell 提示符

如果你在安装 Debian 的过程中，安装了一个 GUI 环境，那么你在启动系统后将使用图形登录界面。输入你的用户名和密码可以登录到非特权用户帐号。使用 Tab 键（跳格键）可以在用户名和密码之间移动，也可以使用鼠标主要键点击。

要在 GUI（图形用户界面）环境下获得 shell 提示符，你必须启动一个`x 终端模拟器`程序，例如gnome-terminal(1)、rxvt(1)或xterm(1)。在 GNOME 桌面环境下，你可以按 超级键（Windows 键），在搜索提示里输入"terminal"来打开终端。

在其它一些桌面系统（如 `fluxbox`）下面，可能没有明显的开始菜单入口。如果是这种情况，试下右击桌面屏幕并希望能有弹出菜单。

### 1.1.3. root 账户

root 账户也被称作超级用户或特权用户。用这个账户，你能够履行下面的系统管理任务。

- 读、写和删除系统上的任何文件，不顾它们的文件权限

- 设置系统上任何文件的所有者和权限

- 设置系统上任何非特权用户的密码

- 免用户密码登录任何帐户

无限权力的 root 账户，要求你慎重和负责任的使用。

| 警告
千万不要和其他人共享 root 密码.

| 注意
一个文件（包括硬件设备，如 CD-ROM 等，这些对 Debian 系统来说都只是一个文件）的权限可能会导致非 root 用户无法使用或访问它 。虽然在这种情况下，使用 root 帐户是一个快速的方法，但正确的解决方法应该是对文件权限和用户组的成员进行合适的设置（参见第 1.2.3 节 “文件系统权限”）。

### 1.1.4. root shell 提示符

这里有一些基本的方法可以让你在输入 root 密码后获得 root的 shell 提示符。

- 在字符界面的登录提示符，键入 `root` 作为用户名登录。

- 在任意用户的 shell 提示符下输入“`su -l`”。

  - 这不会保存当前用户的环境设定。

- 在任意用户的 shell 提示符下输入“`su`”。

  - 这会保存当前用户的一些环境设定。

### 1.1.5. GUI 系统管理工具

如果你的桌面菜单没有使用适当权限启动 GUI（图形用户界面）的自动化管理工具，你可以在终端模拟器（例如gnome-terminal(1)、rxvt(1)或xterm(1)）中 root 的 shell 提示符下启动它。参见第 1.1.4 节 “root shell 提示符”和第 7.9 节 “X 服务端连接”。

| 警告
永远不要在显示管理器（例如gdm3(1)）的提示符下输入`root`来使用 root 账户启动 GUI 显示/会话 管理器。 永远不要在显示关键信息的 X Window下运行不受信任的远程 GUI 程序，因为它可能会监听你的 X 屏幕。

### 1.1.6. 虚拟控制台

在默认的 Debian 系统中，有6个可切换的类VT100字符控制台，可以直接在 Linux 主机上启动 shell。除非你处于 GUI 环境下，否则你可以同时按下`左 Alt 键`和`F1`—`F6`之一的键在虚拟控制台间切换。每一个字符控制台都允许独立登录账户并提供多用户环境。这个多用户环境是伟大的 Unix 的特性，很容易上瘾。

如果你处于 GUI 环境中，你可以通过 `Ctrl-Alt-F3` 键前往字符控制台 3，也就是同时按下`左 Ctrl 键`、`左 Alt 键`和`F3 键`。你可以按下 `Alt-F2` 回到 GUI 环境，它一般运行在虚拟控制台 2。

你也可以使用命令行切换到另一个虚拟控制台，例如切换到控制台 3。

    # chvt 3

### 1.1.7. 怎样退出命令行提示符

在命令行输入 `Ctrl-D`，即同时按下`左侧-Ctrl-键`和`d-键`，即可关闭 shell 活动。如果你正处于字符控制台，你将会返回到登录提示行。尽管这些控制字符 “control D" 使用了大写字母，你并不需要按住 Shift-键。`Ctrl-D` 也可以简写为 `^D`。或者，你也可以键入 “exit” 退出命令行。

如果你位于`x 终端模拟器`(1)中，你可以使用这个关闭`x 终端模拟器`窗口。

### 1.1.8. 怎样关闭系统

就像任何其他的现代操作系统一样，Debian 会通过内存中的缓存数据进行文件操作以提高性能，因此在电源被安全地关闭前需要适当的关机过程，通过将内存中的数据强制写入硬盘来维持文件的完整性。如果软件的电源控制可用，那么关机过程中会自动关闭系统电源。（否则，你可能需要在关机过程之后按电源键几秒钟。）

在普通多用户模式模式下，可以使用命令行关闭系统。

    # shutdown -h now

在单用户模式下，可以使用命令行关闭系统。

    # poweroff -i -f

参见第 6.3.8 节 “怎样通过 SSH 关闭远程系统”。

### 1.1.9. 恢复一个正常的控制台

当做了一些滑稽的事（例如“`cat二进制文件`”）后，屏幕会发狂，你可以在命令行输入“`reset`”。你可能无法在屏幕上看到你输入的命令。你也可以输入“`clear`”来清屏。

### 1.1.10. 建议新手安装的额外软件包

尽管连无需任何桌面环境的 Debian 系统最小安装都提供了基本的 Unix 功能，但对新手而言，使用 apt-get(8) 安装一些基于字符终端的命令行和 curses 软件包（例如 `mc` 和 `vim`）依旧是一个不错的主意。

    # apt-get update
     ...
    # apt-get install mc vim sudo aptitude
     ...

如果你已经安装了这些软件包，那么不会有新的软件包被安装。

表 1.1. 有趣的文本模式程序包列表

软件包 | 流行度 | 大小 | 说明
--- | --- | --- | ---
`mc` | V:39, I:179 | 1590 | 文本模式的全屏文件管理器
`sudo` | V:756, I:867 | 6774 | 给普通用户授予部分 root 权限的程序
`vim` | V:81, I:342 | 4164 | Unix 文本编辑器 Vi 的改进版，一个程序员的文本编辑器（标准版）
`vim-tiny` | V:57, I:979 | 1867 | Unix 文本编辑器 Vi 的改进版，一个程序员的文本编辑器（精简版）
`emacs-nox` | V:3, I:12 | 46564 | GNU 项目的 Emacs，基于 Lisp 的扩展文本编辑器
`w3m` | V:10, I:134 | 2853 | 文本模式的万维网浏览器
`gpm` | V:8.2, I:9.1 | 524 | Unix 风格的文本控制台复制粘贴工具（守护进程）

您也可以考虑阅读一些其他的信息文档。

表 1.2. 软件包信息文档列表

软件包 | 流行度 | 大小 | 说明
--- | --- | --- | ---
`doc-debian` | I:884 | 185 | Debian 项目文档，（Debian 常见问题）和其它文档
`debian-policy` | I:7.3 | 5147 | Debian 策略手册和相关文档
`developers-reference` | V:0.2, I:2.4 | 2647 | Debian 开发者指导方针和信息
`debmake-doc` | I:0.37 | 11803 | Debian 维护者手册
`debian-history` | I:0.56 | 6513 | Debian 项目历史
`debian-faq` | I:882 | 791 | Debian 常见问题

你可以用下面的命令安装这些包。

    # apt-get install package_name

### 1.1.11. 额外用户账号

如果你不想用你自己的主用户账户来进行下面的练习操作，你可以使用下面的方式创建一个练习用户账户，比如说，创建一个用户名为 `fish` 的账号。

    # adduser fish

回答所有问题。

这将创建一个名为 `fish` 的新账号。在你练习完成后，你可以使用下面的命令删除这个用户账号和它的用户主目录。

    # deluser --remove-home fish

在非 Debian 和特别的 Debian 系统，上面的活动需要使用底层的 useradd(8) 和 userdel(8)工具代替。

### 1.1.12. sudo 配置

对于典型的单用户工作站，例如运行在笔记本电脑上的桌面 Debian 系统，通常简单地配置 sudo(8) 来使为非特权用户（例如用户 `penguin`）只需输入用户密码而非 root 密码就能获得管理员权限。

    # echo "penguin  ALL=(ALL) ALL" >> /etc/sudoers

另外，可以使用下列命令使非特权用户（例如用户 `penguin`）无需密码就获得管理员权限。

    # echo "penguin  ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

这些技巧只对你管理的单用户工作站中那个唯一的用户有用。

| 警告
在多用户工作站中不要建立这样的普通用户账户，因为它会导致非常严重的系统安全问题。

| 小心
在上述例子中，用户 `penguin` 的密码及账号要有和 root 账号密码同样多的保护。 在这种情况下，管理员权限被赋予那些有权对工作站进行系统管理任务的人。永远不要让你的公司行政管理部门或你的老板进行管理（例如给予他们权限），除非他们获得了授权并有这样的能力。

| 注意
为了对受限的设备和文件提供访问权限，你应该考虑使用组来提供受限访问，而不是通过sudo(8)来使用 `root`权限。 随着越来越细致周密的配置，sudo(8) 可以授予一个共享系统上的其它用户有限的管理权限而不共享 root 密码。这可以帮助对有多个管理员的主机进行责任追究，你可以了解到是谁做什么。另一方面，你可能不想任何人有这样的权限。

### 1.1.13. 动手时间

现在你已经准备好在 Debian 系统上开工了，只要你使用非特权用户账号就不会有风险。

这是因为 Debian 系统（即使是默认安装）会设置适当的文件权限来防止非特权用户对系统造成破坏。当然，可能仍然有一些漏洞可以利用，但关心这些问题的人不应该阅读这一节，而应该去阅读 Debian 安全手册。

我们使用下面的方式，把 Debian 系统当作一个 类 Unix 系统来学习。

- 第 1.2 节 “类 Unix 文件系统” (基本概念)

- 第 1.3 节 “Midnight Commander (MC)” (生存方式)

- 第 1.4 节 “类 Unix 工作环境基础” (基本方式)

- 第 1.5 节 “简单 shell 命令” (shell 机制)

- 第 1.6 节 “类 Unix 的文本处理” (文本处理方式)

## 1.2. 类 Unix 文件系统

在GNU/Linux和其他类 Unix操作系统中，文件被组织到目录中。所有的文件和目录排放在以“`/`”为根的巨大的树里。叫它树是因为如果你画出文件系统，它看起来就像一棵树，但是它是颠倒过来的。

这些文件和目录可以分散在多个设备中。mount(8)用于把某个设备上找到的文件系统附着到巨大的文件树上。相反的，umount(8)把它再次分离。在最近的 Linux 内核里，mount(8)带某些参数时可以把文件树的一部分绑定到另外的地方，或者可以把文件系统挂载为共享的、私有的、从设备、或不可绑定的。对每个文件系统支持的挂载选项可以在`/usr/share/doc/linux-doc-*/Documentation/filesystems/`找到。

Unix系统上叫做目录，某些其他系统上叫做文件夹。请同样留意，在任何Unix系统上，没有的驱动器的概念，例如“`A:`”。这只有一个文件系统，并且所有东西都包含在内。这相对于 Windows 来说是一个巨大的优点。

### 1.2.1. Unix 文件基础

下面是一些 Unix 文件基础。

- 文件名是 区分大小写 的。也就是说， "`MYFILE`" 和 "`MyFile`" 是不同的文件。

- 根目录意味着文件系统的根，简单的称为“`/`”，不要把它跟 root 用户的家目录“`/root`”混淆了。

- 每个目录都有一个名字，它可以包含任意字母或除了`/`”以外的符号。根目录是个特例。它的名字是“`/`”（称作“斜线”或“根目录”），并且它不能被重命名。

- 每个文件或目录都被指定一个全限定文件名，绝对文件名，或路径，按顺序给出必须经过的目录从而到达相应目录。这三个术语是同义的。

- 所有的全限定文件名以“`/`”目录开始，并且在每个目录或文件名之间有一个“`/`”。第一个“`/`”是最顶层目录，其他的“`/`”用于分隔跟着的子目录。直到到达最后的入口，即实际文件的名称。这些话可能会令人困惑。用下面这个全限定文件名作为例子：“`/usr/share/keytables/us.map.gz`”。不过，人们也把它的基名“`us.map.gz`”单独作为文件名。

- 根目录有很多分支，例如“`/etc/`”和“`/usr/`”。这些子目录依次分出更多的子目录，例如“`/etc/systemd/`”和“`/usr/local/`”。这整体叫做“目录树”。你可以把一个绝对文件名想象成从“`/`”这棵树的基到某个分支（一个文件）的结尾的一条路径。你也听到人们谈论目录树，就好像它是一个包含所有直系后代的“家庭”树的一个图，这个图叫做根目录（“`/`”）：因此子目录有父目录，并且一条路径显示了一个文件完整的祖先。也有相对路径从其他地方开始，而不是从根目录。 你应该还记得目录“`../`”指向父目录。这个术语也适用于其他类似目录的结构，如分层数据结构。

- 对于一个物理设备, 是没有一个特定的目录路径名来对应的组成部分. 这不同于 RT-11, CP/M,OpenVMS,MS-DOS,AmigaOS, 以及微软的 Windows，这些系统存在一个路径包含了一个设备名字，比如"`C:\`"。(尽管如此, 路径条目确实存在引用了物理设备作为正常的文件系统的一部分. 参考第 1.2.2 节 “文件系统深入解析”。)

| 注意
虽然你可以在文件名中使用任意的字幕或者符号, 但是在实际情况下这样做是一个坏主意. 最好避免使用一些在命令行里面含有特殊意义的字符, 比如空格, 制表符, 换行符, 和其它的特殊字符: `{ } ( ) [ ] ' ` " \ / >< | ; ! #&^ * % @ $`. 如果你想有一个区分度良好的命名, 比较好的选择是利用 时期, 连字符和下划线. 你也可以每个单词的首字母大写, 这叫大驼峰命名法, 比如这样 "`LikeThis`". 经验丰富的Linux用户会趋向于在文件名中不使用空格.

| 注意
这个 "root" 可能既表示 "超级用户root" 又表示 " 根目录"(/root) . 应该根据上下文确定它的用法.

| 注意
单词path不仅表示包含全限定文件名, 也可能表示命令搜索的路径. 通常路径真实的意思是需要通过上下文来明确.

关于文件层次的最佳详细实践在文件系统层次标准("`/usr/share/doc/debian-policy/fhs/fhs-2.3.txt.gz`" 和 `hier` (7)). 你应该记住以下的一些标准作为开始学习的步骤.

表 1.3. 重要目录的用途列表

目录 | 目录用途
--- | ---
`/` | 根目录
`/etc/` | 系统范围的配置文件
`/var/log/` | 系统日志文件
`/home/` | 所有非特权用户的用户目录

### 1.2.2. 文件系统深入解析

按照UNIX系统的传统，Debian GNU / Linux 的文件系统是在物理数据存储设备诸如磁盘或其他存储设备上，与硬件设备的交互，如控制台和远程串口终端都是以统一的方式呈现在 “`/ dev /`” 下面。

每个文件、目录、命名管道（一种两个程序间共享数据的方法）或 Debian GNU/Linux 系统上的物理设备都有一个叫做 索引节点（inode） 的数据结构，描述了其相关属性，例如拥有它的用户（所有者），它属于的组，最后一次访问时间，等等。把所有东西都表示在文件系统中的想法是来源于 Unix，现代的 Linux 内核则将这个思路进行了扩展。现在，甚至有关计算机上正在运行的进程的信息都可以在文件系统中找到。

这个对物理实体和内部进程的统一和抽象是非常强大的，因为这允许我们用同样的命令对许多完全不同的设备进行同样的操作。甚至可以通过向链接到运行进程的特殊文件写入数据来改变内核的运行方式。

| 提示
如果您需要识别文件树和物理实体之间的对应关系，请尝试不带参数运行mount(8)。

### 1.2.3. 文件系统权限

类 Unix 系统的文件系统权限被定义给三类受影响的用户。

- 拥有这个文件的用户（u）

- 这个文件所属组的其他用户（g）

- 所有其余的用户（o），同样称为“世界”和“所有人”

对文件来说，每个对应权限允许下列动作。

- 可读（r）权限允许所有者检查文件的内容。

- 可写（w）权限允许所有者修改文件内容。

- 可执行（x）权限允许所有者把文件当做一个命令运行。

对于目录来说，每个对应权限允许下列动作。

- 可读（r）权限允许所有者列出目录内的内容。

- 可写（w）权限允许所有者添加或删除目录里面的文件。

- 可执行（x）权限允许所有者访问目录里的文件。

在这里，一个目录的可执行权限意味着不仅允许读目录里的文件，还允许显示他们的属性，例如大小和修改时间。

ls(1)用于显示文件和目录的权限信息（更多）。当运行时带有“`-l`”选项，它将按给定顺序显示下列信息。

- 文件类型（第一个字母）

- 文件的访问权限（9个字符，三个字符组成一组按照用户、组、其他的顺序表示）

- 链接到文件的硬链接数

- 文件所有者的用户名

- 这个文件所属的组名

- 以字符（字节）为单位的文件大小

- 文件的日期和时间（mtime）

- 文件的名字

表 1.4. “`ls -l`”输出的第一个字符列表

字符 | 说明
--- | ---
`-` | 普通文件
`d` | 目录
`l` | 符号链接
`c` | 字符设备节点
`b` | 块设备节点
`p` | 命名管道
`s` | 套接字

chown(1)用于 root 账户修改文件的所有者。chgrp(1)用于文件的所有者或 root 账户修改文件所属的组。chmod(1)用于文件的所有者或 root 账户修改文件和文件夹的访问权限。操作一个 `foo` 文件的基本语法如下 。

    # chown newowner foo
    # chgrp newgroup foo
    # chmod  [ugoa][+-=][rwxXst][,...] foo

例如，你可以按照下面使一个目录树被用户`foo`所有，并共享给组`bar`。

    # cd /some/location/
    # chown -R foo:bar .
    # chmod -R ug+rwX,o=rX .

有三个更加特殊的权限位。

- Set-User-ID(SUID)位（s或S替换用户的x）

- Set-Group-ID(SGID)位（s或S替换组的x）

- 粘滞位（t或T替代其他用户的x）

如果“`ls -l`”对这些位的输出是大写字母，则表示这些输出下面的执行位未设置。

给一个可执行文件设置 Set-User-ID 位将允许一个用户以他自己的ID运行这个可执行文件（例如 root 用户）。类似的，给一个可执行文件设置了Set-Group-ID 位将允许一个用户以文件所属组的 ID 运行该文件。（例如 root 组）。由于这些设置可能导致安全风险，设置它们为可用的时候需要格外留意。

在一个目录上设置“Set-Group-ID”将打开类 BSD 的文件创建计划，所有在目录里面创建的文件将属于目录所属的组。

给一个目录设置“粘滞位”将保护该目录内的文件不被其所有者之外的一个用户删除。为了保护一个在像“`/tmp`”这样所有人可写或同组可写的目录下文件内容的安全，不仅要去除可写权限，还要给其所在目录设置粘滞位。否则，该文件可以被任意对其所在目录有写权限的用户删除并创建一个同名的新文件。

这里有一点有趣的文件权限例子。

    $ ls -l /etc/passwd /etc/shadow /dev/ppp /usr/sbin/exim4
    crw------T 1 root root   108, 0 Oct 16 20:57 /dev/ppp
    -rw-r--r-- 1 root root     2761 Aug 30 10:38 /etc/passwd
    -rw-r----- 1 root shadow   1695 Aug 30 10:38 /etc/shadow
    -rwsr-xr-x 1 root root   973824 Sep 23 20:04 /usr/sbin/exim4
    $ ls -ld /tmp /var/tmp /usr/local /var/mail /usr/src
    drwxrwxrwt 14 root root  20480 Oct 16 21:25 /tmp
    drwxrwsr-x 10 root staff  4096 Sep 29 22:50 /usr/local
    drwxr-xr-x 10 root root   4096 Oct 11 00:28 /usr/src
    drwxrwsr-x  2 root mail   4096 Oct 15 21:40 /var/mail
    drwxrwxrwt  3 root root   4096 Oct 16 21:20 /var/tmp

chmod(1)有另一种数值模式来描述文件权限。这种数字模式使用3到4位八进制（底为8）数。

表 1.5. chmod(1) 命令文件权限的数字模式

数字 | 说明
--- | ---
第一个可选数字 | Set-User-ID (=4), Set-Group-ID (=2) 和 粘滞位 (=1) 之和
第二个数字 | 用户的可读 (=4), 可写 (=2)和 可执行 (=1) 权限之和
第三个数字 | 组权限同上
第四个数字位 | 其他用户权限同上

这听起来很复杂实际上相当简单。如果你把“`ls -l`”命令输出的前几列（2-10），看成以二进制（底为2）表示文件的权限（“-”看成0，“rwx”看成1），你应该可以理解用数字模式值的最后3位数字对文件权限的八进制表示。

尝试下列例子

    $ touch foo bar
    $ chmod u=rw,go=r foo
    $ chmod 644 bar
    $ ls -l foo bar
    -rw-r--r-- 1 penguin penguin 0 Oct 16 21:39 bar
    -rw-r--r-- 1 penguin penguin 0 Oct 16 21:35 foo

| 提示
如果你需要在 shell 脚本中访问“`ls -l`”显示的信息，你需要使用相关命令，如test(1)，stat(1)和readlink(1)。shell 内置命令，如“`[`”或“`test`”，可能也会用到。

### 1.2.4. 控制新建文件的权限：umask

什么权限将应用到新建文件受 shell 内置命令 `umask` 的限制。参见dash(1)，bash(1)，和`内建命令`(7)。

     (file permissions) = (requested file permissions) & ~(umask value)

表 1.6. umask 值举例

umask 值 | 创建的文件权限 | 创建的目录权限 | 用法
--- | --- | --- | ---
`0022` | `-rw-r--r--` | `-rwxr-xr-x` | 仅所属用户可写
`0002` | `-rw-rw-r--` | `-rwxrwxr-x` | 仅所属组可写

Debian 默认使用用户私人组（UPG）。每当一个新用户添加到系统的时候都会创建一个 UPG。UPG 的名字和创建它的用户相同，这个用户是这个 UPG 的唯一成员。自从每个用户都有自己的私人组之后，把 umask 设置成 `0002` 变得更安全了。（在某些 Unix 变体中，把所有普通用户设置到一个叫 `users` 的组是非常常见的做法，在这种情况下，出于安全考虑把 umask 设为 `0022` 是一个好主意）

| 提示
通过把 “`umask 002`” 写入 `~/.bashrc` 文件打开 UPG。

### 1.2.5. 一组用户的权限（组）

| 警告
在做重启或者类似行为前，确保保存没有保存的修改。

你能够增加一个用户 `penguin` 到 `bird` 组，在两个步骤内：

- 使用下面中的一个改变组配置：

  - 执行 "`sudo usermod -aG bird penguin`"。

  - 执行"`sudo adduser penguin bird`"。（只在典型的 Debian 系统）

  - 执行"`sudo vigr`"编辑 `/etc/group` 和"`sudo vigr -s`"编辑 `/etc/gshadow` 来追加 `penguin` 到`bird` 行。

- 使用下面中的一个来应用配置：

  - 冷重启再登录。（最佳选项）

  - 通过 GUI（图形用户界面）菜单登录和注销（这可能无法在现代桌面环境下工作。）

你能够从 `bird` 组移除用户 `penguin`，用两个步骤：

- 使用下面中的一个改变组配置：

  - 执行 "`sudo usermod -rG bird penguin`"。

  - 执行 "`sudo deluser penguin bird`"。（只在典型的 Debian 系统）

  - 执行"`sudo vigr`"编辑 `/etc/group` 和"`sudo vigr -s`"编辑 `/etc/gshadow` 来移除 `bird` 行里面的`penguin`。

- 使用下面中的一个来应用配置：

  - 冷重启再登录。（最佳选项）

  - 执行 "`kill -TERM -1`" 并做一些修复行为，比如"`systemctl restart NetworkManager.service`"。

  - 通过 GUI（图形用户界面）菜单注销，不是 Gnome 桌面的一个选项。

在现代桌面系统，任何热重启尝试是真实的冷重启的脆弱替代。

| 注意
或者，你可以通过添加一行 “`auth optional pam_group.so`”到 “`/etc/pam.d/common-auth`” 以及配置 “`/etc/security/group.conf`” ，使得在身份验证过程动态添加用户到组。（参见第 4 章 认证和访问控制。）

在 Debian 系统中，硬件设备是另一种文件。如果你从一个用户账户访问某些设备出现问题，例如 USB 盘 和 CD-ROM ，你需要使这个用户成为相关组的成员。

一些著名的由系统提供的组允许其成员不需要 `root` 权限访问某些特定的文件和设备。

表 1.7. 关于文件访问的由系统提供的著名组列表

组 | 可访问文件和设备的描述
--- | ---
`dialout` | 完全及直接的访问串口端口（“`/dev/ttyS[0-3]`”）
`dip` | 有限的访问串口，创建到信任点的拨号 IP 连接
`cdrom` | CD-ROM, DVD+/-RW 驱动器
`audio` | 音频设备
`video` | 视频设备
`scanner` | 扫描仪
`adm` | 系统监控日志
`staff` | 一些用于初级管理工作的目录：“`/usr/local`”，“`/home`”

| 提示
你需要属于 `dialout` 组才能重配置调制解调器、拨号到任意地方，等等。但如果 `root` 用户在 “`/etc/ppp/peers/`” 为受信任点创建了预定义配置文件的话，你只需要属于`dip` 组，就可以创建拨号 IP 来连接到那些受信任的点上，需使用的命令行工具包括 pppd(8)、pon(1)以及 poff(1)。

某些著名的由系统提供的组允许它们的成员不带 `root` 权限运行特定的命令。

表 1.8. 著名的由系统提供用于特定命令运行的组列表

组 | 可访问命令
--- | ---
`sudo` | 使用超级用户权限执行任何命令
`lpadmin` | 执行命令以从打印机数据库添加、修改、移除打印机

由系统提供的用户和组的完整列表，参见由 `base-passwd`包提供的“`/usr/share/doc/base-passwd/users-and-groups.html`”中，当前版本的“用户和组”。

用户和组系统的管理命令，参见 passwd(5)，group(5)，shadow(5)，newgrp(1)，vipw(8)，vigr(8)，以及 pam_group(8)。

### 1.2.6. 时间戳

GNU/Linux 文件有三种类型的时间戳。

表 1.9. 时间戳类型列表

类型 | 含义（历史上 Unix 的定义）
--- | ---
mtime | 文件修改时间(`ls -1`)
ctime | 文件状态修改时间 (`ls -lc`)
atime | 文件最后被访问的时间 (`ls -lu`)

| 注意
ctime 不是文件创建时间。

| 注意
atime在 GNU/Linux 系统上的真实值可能和历史上 Unix 的定义有所不同。

- 覆盖一个文件，将会改变该文件所有的 mtime, ctime, 和 atime 属性。

- 改变文件的所有者或者权限，将改变文件的 ctime 和 atime 属性。

- 在历史上的 Unix 系统中，读取一个文件将改变文件的 atime 属性。

- 读一个文件，将改变文件的 atime属性；在 GNU/Linux 系统上，这仅发生在其文件系统使用“`strictatime`”参数挂载的情况下。

- 如果 GNU/Linux 系统的文件系统使用 "`relatime`" 选项挂载，第一次读文件，或者随后读文件，将改变该文件的 atime 属性. (从 Linux 2.6.30 开始的默认行为)

- 如果 GNU/Linux 系统的文件系统使用 "`noatime`" 挂载，则读一个文件，不会改变这个文件的 atime 属性。

| 注意
为了在正常的使用场景中能够提升文件系统的读取效率，新增了 "`noatime`" 和 "`relatime`" 这两个加载选项。如使用了 "`strictatime`" 选项，即使简单的文件读操作都伴随着更新 atime 属性这个耗时的写操作。但是 atime 属性除了 mbox(5) 文件以外却很少用到。详情请看 mount(8)。

使用 touch(1) 命令修改已存在文件的时间戳。

对于时间戳，在非英语区域（“`fr_FR.UTF-8`”），`ls` 命令输出本地化字符串。

    $ LANG=C  ls -l foo
    -rw-rw-r-- 1 penguin penguin 0 Oct 16 21:35 foo
    $ LANG=en_US.UTF-8  ls -l foo
    -rw-rw-r-- 1 penguin penguin 0 Oct 16 21:35 foo
    $ LANG=fr_FR.UTF-8  ls -l foo
    -rw-rw-r-- 1 penguin penguin 0 oct. 16 21:35 foo

| 提示
参考第 9.3.4 节 “定制时间和日期的显示” 自定义 “`ls -l`” 输出 。

### 1.2.7. 链接

有两种方法把一个文件 “`foo`” 链接到一个不同的文件名 “`bar`”。

- 硬链接

  - 对现有文件重复名称

  - “`ln foo bar`”

- 符号链接或 symlink

  - 通过名字指向另一个文件的特殊文件

  - “`ln -s foo bar`”

请参阅下面的示例，`rm` 命令结果中链接数的变化和细微的差别。

    $ umask 002
    $ echo "Original Content" > foo
    $ ls -li foo
    1449840 -rw-rw-r-- 1 penguin penguin 17 Oct 16 21:42 foo
    $ ln foo bar     # hard link
    $ ln -s foo baz  # symlink
    $ ls -li foo bar baz
    1449840 -rw-rw-r-- 2 penguin penguin 17 Oct 16 21:42 bar
    1450180 lrwxrwxrwx 1 penguin penguin  3 Oct 16 21:47 baz -> foo
    1449840 -rw-rw-r-- 2 penguin penguin 17 Oct 16 21:42 foo
    $ rm foo
    $ echo "New Content" > foo
    $ ls -li foo bar baz
    1449840 -rw-rw-r-- 1 penguin penguin 17 Oct 16 21:42 bar
    1450180 lrwxrwxrwx 1 penguin penguin  3 Oct 16 21:47 baz -> foo
    1450183 -rw-rw-r-- 1 penguin penguin 12 Oct 16 21:48 foo
    $ cat bar
    Original Content
    $ cat baz
    New Content

硬链接可以在同一个文件系统内创建，并共用同一个 inode 号，由ls(1)带 “`-i`”选项显示。

符号链接总是名义上具有“`rwxrwxrwx`”的文件访问权限，如上面例子所示，实际的有效访问权限由它所指向的文件确定。

| 小心
除非你有非常好的理由，否则不要创建一个复杂的符号链接或硬链接通常是个好主意。符号链接的逻辑组合可能导致文件系统噩梦般的无限循环。

| 注意
通常使用符号链接比使用硬链接更合适，除非你有一个好理由使用硬链接。

“`.`”目录链接到它所在的目录，因此任何新建目录的链接数从2开始。“`..`”目录链接到父目录，因此目录的链接数随着新的子目录的创建而增加。

如果你刚从 Windows 迁移到Linux，你很快将清楚 Unix 的文件名链接相较于 Windows 最相近的“快捷方式”是多么精心设计的。由于它是在文件系统中实现的，应用无法看到链接文件跟原始文件之间的区别。在硬链接这种情况，这真的是毫无差别。

### 1.2.8. 命名管道（先进先出）

命名管道是一个像管道一样的文件。你把内容放进了文件，它从另一端出来。因此，它被称为FIFO，即先进先出：你从管道这端先放进去的东西会从另一端先出来。

如果对一个命名管道进行写入操作，写入的过程不会被终止，直到写入的信息从管道中被读取出来。读取过程将会持续到没有信息可以读取为止。管道的大小始终是零，它不存储数据，它只是连接两个过程，像 shell 提供的 "`|`" 语法功能一样。然而，一旦管道有了名称，这两个进程就可以不必在同一个命令行，甚至由同一个用户运行。管道是 Unix 的一个非常有影响力的创新。

尝试下列例子

    $ cd; mkfifo mypipe
    $ echo "hello" >mypipe & # put into background
    [1] 8022
    $ ls -l mypipe
    prw-rw-r-- 1 penguin penguin 0 Oct 16 21:49 mypipe
    $ cat mypipe
    hello
    [1]+  Done                    echo "hello" >mypipe
    $ ls mypipe
    mypipe
    $ rm mypipe

### 1.2.9. 套接字

套接字被广泛应用于所有的互联网通信，数据库和操作系统本身。它类似于命名管道（FIFO）并且允许进程之间甚至不同计算机之间进行信息交换。对于套接字，这些进程不需要在同一时间运行，也不需要是同一个父进程的子进程。它是进程间通信（IPC）的一个节点。信息的交换可能会通过网络发生在不同主机之间。最常见的两种是 互联网套接字 和 UNIX域套接字 。

| 提示
通过 "`ss -t -u -a`"（来自`iproute2` 软件包 ） 命令可以很方便的查看系统已经打开了哪些套接字。

### 1.2.10. 设备文件

设备文件包括系统的物理设备和虚拟设备，如硬盘、显卡、显示屏、键盘。虚拟设备的一个例子是控制台，用“`/dev/console`”来描述。

设备文件有两种类型。

- 字符设备

  - 每次访问一个字符

  - 一个字符等于一个字节

  - 如键盘、串口…

- 块设备

  - 通过更大的单元–块，进行访问

  - 一个块>一个字节

  - 如硬盘等…

你可以读写块设备文件，尽管该文件可能包含二进制数据，读取后显示出无法理解的乱码。向文件写入数据，有时可以帮助定位硬件连接故障。比如，你可以将文本文件导入打印机设备“`/dev/lp0`”，或者将调制解调命令发送到合适的串口“`/dev/ttyS0`”。但是，除非这些操作都小心完成，否则可能会导致一场大灾难。所以要特别小心。

| 注意
常规访问打印机，使用lp(1)。

设备的节点数可以通过执行ls(1)得到，如下所示。

    $ ls -l /dev/sda /dev/sr0 /dev/ttyS0 /dev/zero
    brw-rw---T  1 root disk     8,  0 Oct 16 20:57 /dev/sda
    brw-rw---T+ 1 root cdrom   11,  0 Oct 16 21:53 /dev/sr0
    crw-rw---T  1 root dialout  4, 64 Oct 16 20:57 /dev/ttyS0
    crw-rw-rw-  1 root root     1,  5 Oct 16 20:57 /dev/zero

- "`/dev/sda`"的主设备号是8，次设备号是0。它可以被`disk`群组的用户读写。

- "`/dev/sr0`"的主设备号是11，次设备号是0。它可以被`cdrom`群组的用户读写。

- "`/dev/ttyS0`"的主设备号是4，次设备号是64。它可以被`dailout`群组的用户读写。

- "`/dev/zero`"的主设备号是1，次设备号是5。它可以被任意用户读写。

在现代Linux系统中，处在"`/dev`"之下的文件系统会自动被`udev`()机制填充。

### 1.2.11. 特殊设备文件

还有一些特殊的设备文件。

表 1.10. 特殊设备文件列表

设备文件 | 操作 | 响应描述
--- | --- | ---
`/dev/null` | 读取 | 返回“文件结尾字符（EOF）“
`/dev/null` | 写入 | 无返回（一个无底的数据转存深渊）
`/dev/zero` | 读取 | 返回"`\0`空字符"（与ASCII中的数字0不同）
`/dev/random` | 读取 | 从真随机数产生器返回一个随机字符，供应真熵（缓慢）
`/dev/urandom` | 读取 | 从能够安全加密的伪随机数产生器返回一个随机字符
`/dev/full` | 写入 | 返回磁盘已满（ENOSPC）错误

这些特别设备文件经常和 shell 数据重定向联合使用（参考第 1.5.8 节 “典型的顺序命令和 shell 重定向”）。

### 1.2.12. procfs 和 sysfs

procfs和sysfs两个伪文件系统，分别加载于"`/proc`"和"`/sys`"之上，将内核中的数据结构暴露给用户空间。或者说，这些条目是虚拟的，他们打开了深入了解操作系统运行的方便之门。

目录"`/proc`"为每个正在运行的进程提供了一个子目录，目录的名字就是进程标识符（PID）。需要读取进程信息的系统工具，如`ps`()，可以从这个目录结构获得信息。

"`/proc/sys`"之下的目录，包含了可以更改某些内核运行参数的接口。（你也可以使用专门的`sysctl`()命令修改，或者使用其预加载/配置文件"`/etc/sysctl.d/*.conf`"。）

当人们看到这个特别大的文件"`/proc/kcore`"时，常常会惊慌失措。这个文件于你的的电脑内存大小相差不多。它被用来调试内核。它是一个虚拟文件，指向系统内存，所以不必担心它的大小。

"`/sys`"以下的目录包含了内核输出的数据结构，它们的属性，以及它们之间的链接。它同时也包含了改变某些内核运行时参数的接口。

参考"`proc.txt(.gz)`"，"`sysfs.txt(.gz)`"，以及其他相关的Linux内核文档（"`/usr/share/doc/linux-doc-*/Documentation/filesystems/*`"），这些文件由`linux-doc-*`软件包提供。

### 1.2.13. tmpfs

tmpfs是一个临时文件系统，它的文件都保存在虚拟内存中。必要时，位于内存页缓存的tmpfs数据可能被交换到硬盘中的交换分区。

系统启动早期阶段，"`/run`"目录挂载为tmpfs。这样即使"`/`"挂载为只读，它也是可以被写入的。它为过渡态文件提供了新的存储空间，同时也替代了Filesystem Hierarchy Standar2.3版中说明的目录位置：

- "`/var/run`" → "`/run`"

- "`/var/lock`" → "`/run/lock`"

- "`/dev/shm`" → "`/run/shm`"

参考"`tmpfs.txt(.gz)`", 文件位于Linux内核文档（"`/usr/share/doc/linux-doc-*/Documentation/filesystems/*`"）目录之下，由软件包`linux-doc-*`提供。

## 1.3. Midnight Commander (MC)

Midnight Commander (MC) 是一个 Linux 终端或其它终端环境下的 GNU 版 "瑞士军刀" 。它为新手们提供了一个菜单式样的终端使用体验，这比标准的 Unix 命令更易于学习。

你可能需要按照下面的命令来安装标题为 "`mc` " 的 Midnight Commander 软件包。

    $ sudo apt-get install mc

使用 mc(1) 命令那个来浏览 Debian 系统。这是最好的学习方式。请使用光标键和回车键来翻看一些感兴趣的内容。

- "`/etc`" 及其子目录

- "`/var/log` " 及其子目录

- "`/usr/share/doc` " 及其子目录

- "`/usr/sbin` " 和 "`/usr/bin` "

### 1.3.1. 自定义 MC

为了在退出 MC 的时候更改目录并 `cd` 到其它目录，我建议修改 "`~/.bashrc`" 包含一个由 `mc` 包提供的脚本。

    . /usr/lib/mc/mc.sh

查看mc(1) (在 "`-P`" 选项里) 的原因。 (如果你不能理解我这里说所讲的，你可以稍后回头再看)

### 1.3.2. 启动 MC

MC 可以这样启动起来。

    $ mc

MC 通过菜单覆盖了所有的文件操作，因此而让用户更省心省力。只需要按 F1 就可以跳转到帮助界面。你只需要按光标键和功能键就可以使用 MC。

| 注意
某些终端比如 gnome-terminal(1)，功能键的按键触发消息可能会被终端程序截取。在 `gnome-terminal` 里，可以通过 "首选项" →“通用”-> "快捷键" 菜单设置来禁用这些特征。

如果你遇到字符编码问题，显示出来都是乱码，通过添加"`-a`"到 MC 命令行或许有助于避免此类问题。

如果这样不能解决 MC 中的显示问题，可以参考 第 8.3.1 节 “终端配置”.

### 1.3.3. MC 文件管理

默认的两个目录面板里包含了文件列表。另一个有用的模式是设置右边窗口为 "信息" 来读取文件访问权限信息。接下来是一些必要的快捷键。守护进程 gpm(8)运行的时候，你也可以在字符命令行里用鼠标来操作。 (在 MC 里进行复制和粘贴操作的时候一定要按住 shift 键。)

表 1.11. MC 快捷键绑定

快捷键 | 键绑定功能
--- | ---
`F1` | 帮助菜单
`F3` | 内部文件查看器
`F4` | 内部编辑器
`F9` | 激活下拉菜单
`F10` | 退出 Midnight Commander
`Tab` | 在两个窗口间移动
`Insert` 或 `Ctrl-T` | 用于多文件操作的标记文件，如副本
`Del` | 删除文件 (注意---设置 MC 为安全删除模式)
光标键 | 自我解释

### 1.3.4. MC 命令行技巧

- `cd` 命令在选中的屏幕中改变目录。

- `Ctrl-Enter` or `Alt-Enter` 拷贝文件名到命令行。使用 cp(1) 和 mv(1) 两个命令来进行处理。

- `Alt-Tab` 显示文件名自动补全提示。

- 通过添加 MC 命令参数可以指定开始目录；例如，"`mc /etc /root`"。

- `Esc` + `n-key` → `Fn` (即 `Esc` + `1` → `F1` 等；`Esc` + `0` → `F10`)

- 先按 `Esc` 键 和同时按 `Alt` 是一样；例如, 输入 `Esc` + `c` 和同时 `Alt-C`是一样的。`Esc` 被称为 meta 键，有时候也称之为 "`M-`"。

### 1.3.5. MC 内部编辑器

这个内置编辑器有一个有意思的粘贴方案。按 `F3` 开始选择起始点，再按 `F3` 选择终点并高亮选择区。此刻你可以移动你的光标，使用 F6 将选区移动到当前光标下，F5 则将选区复制到当前光标下。`F2` 保存文件。`F10` 退出。多数光标键以直观的方式工作。

这个编辑器可以直接以下面的命令打开一个文件。

    $ mc -e filename_to_edit

    $ mcedit filename_to_edit

这不是一个多窗口编辑器，但是能通过复用终端来达到同样的效果。 在两个窗口间复制，需要用到 Alt-Fn 来切换虚拟终端并使用 "File→Insert file" 或者 "File→Copy to file" 来移动文本。

内部编辑器可以被外部编辑器替代。

同样，许多程序使用环境变量`$EDITOR`或`$VISUAL`来决定编辑器的使用。如果你准备使用vim(1)或者nano(1)来开始，你或许需要将下面的代码加入"`~/.bashrc`"来对`mcedit`进行设置。

    export EDITOR=mcedit
    export VISUAL=mcedit

如果可能的话我推荐用 "`vim`"。

如果你使用vim(1)并不顺手，你可以在大部分系统中继续使用mcedit(1)来进行工作。

### 1.3.6. MC 内部查看器

MC是一个非常智能的查看器。这是一个在文档中搜索文本的好工具。我经常使用它在`/usr/share/doc`目录中查找文件。这是浏览大量Linux信息的最快方式。这个查看器可以通过下列命令中的任何一个来直接启动。

    $ mc -v path/to/filename_to_view

    $ mcview path/to/filename_to_view

### 1.3.7. 自动启动 MC

在文件中输入回车， 用适当的程序来处理文件的内容 (查看 第 9.4.11 节 “自定义被启动的程序”)。这是 MC 一个非常方便的用法。

表 1.12. MC 中对回车键的响应

文件类型 | 对回车键的响应
--- | ---
可执行文件 | 执行命令
帮助文档 | 管道内容查看器软件
html 文件 | 管道内容网页浏览器
"`*.tar.gz`" 和 "`*.deb`" 文件 | 浏览其内容就像查看子目录一样

为让这些查看器和虚拟文件特征生效，可查看的文件不能够被设置为可执行。使用 chmod(1) 或通过 MC 文件菜单改变他们的状态。

### 1.3.8. MC 中的 虚拟文件系统

MC 能够跨因特网访问文件。在菜单按 `F9`, "`Enter`" 和"`h`"来激活 Shell 文件系统。按 "`sh://[user@]machine[:options]/[remote-dir]`" 的形式输入 URL,就会看起来像本地使用 `ssh` 一样来检索远程目录。

## 1.4. 类 Unix 工作环境基础

虽然 MC 差不多可以让你做任何事情，但学会从 shell 提示下使用命令行工具也是非常重要的，可以让你变得熟悉类 Unix 工作环境。

### 1.4.1. 登录 shell

因登录 shell 可以被一些系统初始化程序使用，请谨慎的把登录 shell 保持为 bash(1) ，并避免把它转换为 chsh(1)。

如果你想使用不同的 shell 交互提示符，从 GUI（图形用户界面）的终端模拟器来设置；或者从 `~/.bashrc` 启动，比如说，在里面放置"`exec /usr/bin/zsh -i -l`" 或 "`exec /usr/bin/fish -i -l`"。

表 1.13. shell 程序列表

软件包 | 流行度 | 大小 | POSIX shell | 说明
--- | --- | --- | --- | ---
`bash` | V:893, I:1000 | 7309 | 是 | Bash: GNU Bourne Again SHell (事实上的标准)
`bash-completion` | V:37, I:960 | 1952 | N/A | bash shell 编程补全
`dash` | V:928, I:998 | 207 | 是 | Debian Almquist Shell, 擅长 shell 脚本
`zsh` | V:41, I:70 | 2571 | 是 | Z shell:有许多增强的标准 shell
`tcsh` | V:4, I:15 | 1366 | No | TENEX C Shell: 一个 Berkeley csh 的增强版本
`mksh` | V:5.6, I:8.0 | 7713 | 是 | Korn shell 的一个版本
`csh` | V:1.1, I:5.1 | 348 | No | OpenBSD C Shell, Berkeley csh 的一个版本
`sash` | V:0.3, I:5.1 | 1245 | 是 | 有内置命令的 Stand-alone shell (并不意味着标准的 "`/usr/bin/sh`")
`ksh` | V:0.3, I:7.7 | 65 | 是 | Korn shell的真正的 AT&T 版本
`rc` | V:0.09, I:0.77 | 182 | No | AT&T Plan 9 rc shell 的一个实现
`posh` | V:0.00, I:0.23 | 195 | 是 | Policy-compliant Ordinary SHell 策略兼容的普通 shell(`pdksh` 派生)

| 提示
虽然类 POSIX 共享基本语法，但他们在 shell 变量和全局扩展等基本事情上，行为可以不同。细节请查阅他们的文档。

在本教程中，交互式的 shell 总是指 `bash`.

### 1.4.2. 定制 bash

你可以通过“`~/.bashrc`”来定制bash(1)的行为。

尝试下列例子。

    # enable bash-completion
    if ! shopt -oq posix; then
      if [ -f /usr/share/bash-completion/bash_completion ]; then
        . /usr/share/bash-completion/bash_completion
      elif [ -f /etc/bash_completion ]; then
        . /etc/bash_completion
      fi
    fi

    # CD upon exiting MC
    . /usr/lib/mc/mc.sh

    # set CDPATH to a good one
    CDPATH=.:/usr/share/doc:~:~/Desktop:~
    export CDPATH

    PATH="${PATH+$PATH:}/usr/sbin:/sbin"
    # set PATH so it includes user's private bin if it exists
    if [ -d ~/bin ] ; then
      PATH="~/bin${PATH+:$PATH}"
    fi
    export PATH

    EDITOR=vim
    export EDITOR

| 提示
你可以在第 9 章 系统技巧中的第 9.3.6 节 “有颜色输出的命令”找到更多关于`bash`的定制技巧。

| 提示
`bash-completion` 软件包能够让 `bash` 进行命令补全.

### 1.4.3. 特殊按键

在 类 Unix 环境，有一些具有特殊含义的按键。请注意，普通的 Linux 字符控制台，只有左手边的 `Ctrl` 和 `Alt` 键可以正常工作。其中有几个值得记住的按键。

表 1.14. bash 的按键绑定列表

快捷键 | 描述
--- | ---
`Ctrl-U` | 删除光标前到行首的字符
`Ctrl-H` | 删除光标前的一个字符
`Ctrl-D` | 终止输入（如果你在使用 shell，则退出 shell）
`Ctrl-C` | 终止一个正在运行的程序
`Ctrl-Z` | 通过将程序移动到后台来暂停程序
`Ctrl-S` | 停止屏幕输出
`Ctrl-Q` | 激活屏幕输出
`Ctrl-Alt-Del` | 重启/关闭系统，参见 inittab(5)
`左 Alt 键`（可选择同时按下 `Windows-key`） | Emacs 和相似 UI 的元键（meta-key）
`Up-arrow 向上方向键` | 开始在`bash` 中查看命令历史
`Ctrl-R` | 开始在 `bash` 的增量命令历史中搜索
`Tab` | 在 `bash` 命令行中补全文件名
`Ctrl-V` `Tab` | 在 `bash` 命令行中输入 `Tab` 而不是进行补全

| 提示
`Ctrl-S` 的终端功能可能被 stty(1) 禁用。

### 1.4.4. 鼠标操作

Debian 系统针对文本的鼠标操作混合 2 种风格，外加一些新的方法：

- 传统的 Unix 鼠标操作方式：

  - 使用 3 个按钮（单击）

  - 使用主要键

  - 由 X 应用，如 `xterm`，以及文本应用在控制台中使用

- 现代 GUI（图形用户界面）鼠标操作方式：

  - 使用 2 个按钮（拖动 + 单击）

  - 使用主要键和剪贴板

  - 用于现代的 GUI（图形用户界面）应用，比如 `gnome-terminal`

表 1.15. Debian上的鼠标操作和相关按键操作列表

操作 | 响应
--- | ---
左击并拖动鼠标 | 主要键的选择作为选择范围
单击左键 | 主要键的位置作为选择范围的开头
单击右键（传统方式） | 主要键的位置作为选择范围的结尾
单击右键（现代方式） | 依赖上下文的菜单（剪切、拷贝、粘贴）
点击中键或者 Shift-`Ins` | 在光标处插入主要键的选择
`Ctrl-X` | 剪切主要键的选择到剪贴板
`Ctrl-C` (在终端是 `Shift-Ctrl-C` ) | 拷贝主要键的选择到剪贴板
`Ctrl-V` | 粘贴剪切板的内容到光标处

这里，主要键的选择会高亮文本范围。在终端程序内，使用 `Shift-Ctrl-C` 来代替，这样可以避免终止一个运行的程序。

在现代滚轮鼠标上的中央滚轮，被认为是中间键，并可以被当做中间键使用。在 2 键鼠标系统的情况下，同时按左键和右键就相当于按中间键。

为了在 Linux 字符控制台中使用鼠标，您需要让 gpm(8) 作为后台守护进程（daemon）运行。

### 1.4.5. 分页程序

less(1) 命令是一个增强版的分页程序（文件内容查看器）。它按照指定的命令参数或标准输出来读取文件。在用 `less` 命令查看的时候如果需要帮助可以按 “`h`”。它的功能比 more(1) 命令更丰富，通过在脚本的开头执行 "`eval $(lesspipe)`" 或 "`eval $(lessfile)`" 它的功能还能变得更加强大。详细请参考 "`/usr/share/doc/less/LESSOPEN`"。 "`-R`" 选项可以实现原始的字符输出还可以启用 ANSI 颜色转义序列。详细请参考 less(1)。

| 提示
在 `less` 命令中，输入"`h`"来查看帮助屏幕，输入"`/`"或"`?`"来搜索字符串，输入"`-i`" 来改变大小写敏感性。

### 1.4.6. 文本编辑器

在使用类 Unix 系统过程中， 各种类似于 Vim 或 Emacs 的工具，你应该精通其中的一个。

我认为习惯于使用 Vim 命令是一个明智的选择，因为 Linux/Unix 系统里一般都附带了 Vi 编辑器。 (实际上最初的 `vi` 以及后来的 `nvi` 这类工具程序很常见。因为在 Vim 里提供了`F1`帮助键，在同类工具中它的功能更强大，所以我选择 Vim 而不是其它新出的一些工具。)

假设你不是用 Emacs 就是用 XEmacs 作为你的编辑器，其实还有更好的选择，尤其是在编程的时候。 Emacs 还有很多其他的特点，包括新手导读，目录编辑器，邮件客户端等等。当编写脚本或程序的时候，它能自动识别当前工作模式所对应的格式，让使用更加便利。一些人甚至坚持认为 Linux 系统里最需要配备的就是 Emacs。花十分钟来学习 Emacs 可以为后面的工作剩下更多时间。在此强烈推荐学习使用 Emacs 时候直接使用 GNU Emacs 参考手册。

在实践应用中所有这些程序都会有一个教程，输入 "`vim`" 和F1键就可以启动Vim。建议你最好阅读一下前面的 35 行。移动光标到 "`|tutor|`" 并按 `Ctrl-]` 就可以看到在线培训教程。

| 注意
好的编辑器，像 Vim 和 Emacs，可以处理 UTF-8 及其它不常用编码格式的文本。有个建议就是在 GUI（图形用户界面） 环境下使用 UTF-8 编码，并安装要求的程序和字体。编辑器里可以选择独立于 GUI（图形用户界面）环境的编码格式。关于多字节文本可以查阅参考文档。

### 1.4.7. 设置默认文本编辑器

Debian 有许多不同的编辑器。我们建议安装上面提到的 `vim` 软件包。

Debian 通过命令“`/usr/bin/editor`”提供了对系统默认编辑器的统一访问，因此其它程序（例如 reportbug(1)）可以调用它。你可以通过下列命令改变它。

    $ sudo update-alternatives --config editor

对于新手，我建议使用“`/usr/bin/vim.basic`”代替“`/usr/bin/vim.tiny`”，因为它支持格式高亮。

| 提示
许多程序使用环境变量“`$EDITOR`”或“`$VISUAL`”来决定使用那个编辑器（参见第 1.3.5 节 “MC 内部编辑器”和第 9.4.11 节 “自定义被启动的程序”）。出于 Debian 系统的一致性考虑，它们被设置到“`/usr/bin/editor`”。（在历史上，“`$EDITOR`”是“`ed`”，“`$VISUAL`”是“`vi`”。）

### 1.4.8. 使用 vim

最近的 vim(1) 用完全的 "`nocompatible`" 选项启动自己，进入到 `普通` 模式。[1]

表 1.16. 基本的 Vim 按键列表

模式 | 按键 | 操作
--- | --- | ---
`NORMAL` | `:help|only` | 显示帮助文件
`NORMAL` | `:e filename.ext` | 打开新的缓冲区来编辑 `filename.ext`
`NORMAL` | `:w` | 把目前的缓冲区改写到原始文件
`NORMAL` | `:w filename.ext` | 写入当前缓冲区到 `filename.ext`
`NORMAL` | `:q` | 退出 `vim`
`NORMAL` | `:q!` | 强制退出 `vim`
`NORMAL` | `:only` | 关闭所有其它分割打开的窗口
`NORMAL` | `:set nocompatible?` | 检查 `vim` 是否在完全的 `nocompatible` 模式
`NORMAL` | `:set nocompatible` | 设置 `vim` 到完全的 `nocompatible` 模式
`NORMAL` | `i` | 进入 `插入` 模式
`NORMAL` | `R` | 进入 `替代` 模式
`NORMAL` | `v` | 进入 `可视` 模式
`NORMAL` | `V` | 进入 `可视` 行 模式
`NORMAL` | Ctrl-`V` | 进入 `可视` 块 模式
除了 `TERMINAL-JOB` 外 | `ESC`-键 | 进入 `普通` 模式
`NORMAL` | `:term` | 进入 `TERMINAL-JOB` 模式
`TERMINAL-NORMAL` | `i` | 进入 `TERMINAL-JOB` 模式
`TERMINAL-JOB` | Ctrl-`W` `N` (或者 Ctrl-`\` Ctrl-`N`) | 进入 `TERMINAL-NORMAL` 模式
`TERMINAL-JOB` | Ctrl-`W` `:` | 在`TERMINAL-NORMAL` 模式里进入`Ex`-模式

请使用 "`vimtutor`" 程序来学习 `vim`，通过一个交互式的指导课程。

`vim` 程序基于 模式 输入的按键来改变它的行为。在 `插入`-模式和 `替代`-模式下，输入的按键大部分进入了缓冲区。移动光标大部分在 `普通`-模式下完成。交互选择在 `可视`-模式下完成。在`普通`-模式下输入 "`:`" ，改变它的 模式 进入到 `Ex`-模式。 `Ex`-接受命令。

| 提示
Vim 和 Netrw 软件包可以一起使用。Netrw 同时支持在本地和网络读写文件，浏览目录。用 "`vim .`" (一个点作为参数)来尝试 Netrw，在"`:help netrw`"读取它的文档。

`vim` 的高级配置，参见 第 9.2 节 “定制 vim”。

### 1.4.9. 记录 shell 活动

shell 命令的输出有可能滚动出了屏幕，并可能导致你无法再查看到它。将shell活动记录到文件中再来回顾它是个不错的主意。当你执行任何系统管理任务时，这种记录是必不可少的。

| 提示
新版本的 Vim (version>=8.2)能够被用来清晰的记录 shell 活动，使用`TERMINAL-JOB`-模式。参见 第 1.4.8 节 “使用 vim”。

记录 shell 活动的基本方法是在script(1)下运行 shell。

尝试下列例子

    $ script
    Script started, file is typescript

在`script`下使用任何 shell 命令。

按 `Ctrl-D` 来退出 `script`。

    $ vim typescript

参见第 9.1.1 节 “清晰的记录 shell 活动”。

### 1.4.10. 基本的 Unix 命令

让我们来学习基本的 Unix 命令。在这里，我指的是一般意义上的“UNIX”。任何 UNIX 克隆系统通常都会提供等价的命令。Debian 系统也不例外。如果有一些命令不像你想的那样起作用，请不要担心。如果 shell 中使用了`别名`，其对应的命令输出会不同。这些例子并不意味着要以这个顺序来执行。

尝试使用非特权用户账号来使用下列的命令。

表 1.17. 基本的 Unix 命令列表

命令 | 说明
--- | ---
`pwd` | 显示当前/工作目录的名称
`whoami` | 显示当前的用户名
`id` | 显示当前用户的身份（名称、uid、gid和相关组）
`file foo` | 显示“`foo`”文件的文件类型
`type -p commandname` | 显示命令的文件所处位置“`commandname`”
`which commandname` | 同上
`type commandname` | 显示“`commandname`”命令的相关信息
`apropos key-word` | 查找与“`key-word`”有关的命令
`man -k key-word` | 同上
`whatis commandname` | 用一行解释 “`commandname`” 命令
`man -a commandname` | 显示“`commandname`”命令的解释（Unix 风格）
`info commandname` | 显示“`commandname`”命令相当长的解释（GNU风格）
`ls` | 显示目录内容（不包含以 . 点号开头的文件和目录）
`ls -a` | 显示目录内容（包含所有文件和目录）
`ls -A` | 显示目录内容（包含几乎所有文件和目录，除了“`..`”和“`.`”）
`ls -la` | 显示所有的目录内容，并包含详细的信息
`ls -lai` | 显示所有的目录内容，并包含inode和详细的信息
`ls -d` | 显示当前目录下的所有目录
`tree` | 使用树状图显示目录内容
`lsof foo` | 列出处于打开状态的文件 "`foo`"
`lsof -p pid` | 列出被某进程打开的文件: "`pid`"
`mkdir foo` | 在当前目录中建立新目录“`foo`”
`rmdir foo` | 删除当前目录中的“`foo`”目录
`cd foo` | 切换到当前目录下或变量“`$CDPATH`”中的“`foo`”目录
`cd /` | 切换到根目录
`cd` | 切换到当前用户的家目录
`cd /foo` | 切换到绝对路径为“`/foo`”的目录
`cd ..` | 切换到上一级目录
`cd ~foo` | 切换到用户“`foo`”的家目录
`cd -` | 切换到之前的目录
`</etc/motd pager` | 使用默认的分页程序来显示“`/etc/motd`”的内容
`touch junkfile` | 建立一个空文件“`junkfile`”
`cp foo bar` | 将一个现有文件“`foo`”复制到一个新文件“`bar`”
`rm junkfile` | 删除文件“`junkfile`”
`mv foo bar` | 将一个现有文件“`foo`”重命名成“`bar`”（“`bar`”必须不存在）
`mv foo bar` | 将一个现有文件“`foo`”移动到新的位置“`bar/foo`”（必须存在“`bar`”目录）
`mv foo bar/baz` | 移动一个现有文件“`foo`”到新位置并重命名为“`bar/baz`”（必须存在“`bar`”目录，且不存在“`bar/baz`文件”）
`chmod 600 foo` | 使其他人无法读写现有文件“`foo`”（并且所有人都无法执行该文件）
`chmod 644 foo` | 使其他人对现有文件“`foo`”可读但不可写（并且所有人都无法执行该文件）
`chmod 755 foo` | 使其他人对“`foo`”可读而不可写（并且所有人都能执行该文件）
`find . -name pattern` | 使用 shell “`pattern`” 查找匹配的文件名（速度较慢）
`locate -d . pattern` | 使用 shell “`pattern`” 查找匹配的文件名（速度较快，使用定期生成的数据库）
`grep -e "pattern" *.html` | 在当前目录下以“`.html`”结尾的所有文件中，查找匹配“`pattern`”的文件并显示
`top` | 全屏显示进程信息，输入“`q`”退出
`ps aux | pager` | 显示所有正在运行的进程的信息（BSD风格）
`ps -ef | pager` | 显示所有正在运行的进程的信息（Unix system-V风格）
`ps aux | grep -e "[e]xim4*"` | 显示所有正在运行“`exim`”和“`exim4`”的进程
`ps axf | pager` | 显示所有正在运行的进程的信息（ASCII风格）
`kill 1234` | 杀死ID为“1234”的进程
`gzip foo` | 使用 Lempel-Ziv 编码（LZ77）将“`foo`”压缩为“`foo.gz`”
`gunzip foo.gz` | 将“`foo.gz`”解压为“`foo`”
`bzip2 foo` | 使用 Burrows-Wheeter 块排序压缩算法和 Huffman 编码将“`foo`”压缩为“`foo.bz2`”（压缩效果比`gzip`更好）
`bunzip2 foo.bz2` | 将“`foo.bz2`”解压为“`foo`”
`xz foo` | 使用 Lempel-Ziv-Markov 链算法将“`foo`”压缩为“`foo.xz`”（压缩效果比`bzip2`更好）
`unxz foo.xz` | 将“`foo.xz`”解压为“`foo`”
`tar -xvf foo.tar` | 从“`foo.tar`”档案中提取文件
`tar -xvzf foo.tar.gz` | 从被gzip压缩过的“`foo.tar.gz`”档案中提取文件
`tar -xvjf foo.tar.bz2` | 从“`foo.tar.bz2`”档案中提取文件
`tar -xvJf foo.tar.xz` | 从“`foo.tar.xz`”档案中提取文件
`tar -cvf foo.tar bar/` | 将目录“`bar/`”中的内容打包到“`foo.tar`”档案中
`tar -cvzf foo.tar.gz bar/` | 将目录 “`bar/`” 中的内容打包并压缩成 “`foo.tar.gz`” 文件
`tar -cvjf foo.tar.bz2 bar/` | 将目录“`bar/`”中的内容打包到“`foo.tar.bz2`”档案中
`tar -cvJf foo.tar.xz bar/` | 将目录”`bar/`“中的内容打包到”`foo.tar.xz`“档案中
`zcat README.gz | pager` | 使用默认的分页程序来显示 “`README.gz`” 压缩包中的内容
`zcat README.gz > foo` | 将“`README.gz`”解压后的内容输出到文件“`foo`”中
`zcat README.gz >> foo` | 将“`README.gz`”解压后的内容添加到文件“`foo`”的末尾（如果文件不存在，则会先建立该文件）

| 注意
Unix 有一个惯例，以“`.`”开头的文件将被隐藏。它们一般为包含了配置信息和用户首选项的文件。 对于`cd`命令，参见builtins(7)。 基本的 Debian 系统的默认分页程序是 more(1)，它无法往回滚动。通过命令 “`apt-get install less`" 安装 `less` 软件包后，less(1) 会成为默认的分页程序，它可以通过方向键往回滚动。 上面命令 "ps aux | grep -e "[e]xim4*"" 中正则表达式里的 "[" 和 "]" 使得 grep 可以避免匹配到自身。正则表达式中的 "4*" 表示字符 "4" 出现 0 次或多次，因此 grep 可以匹配 "exim" 和 "exim4"。虽然 "*" 同时出现在 shell 文件名通配符和正则表达式中，但它们的含义不同。可以从 grep(1) 学习正则表达式。

作为训练，请使用上述的命令来遍历目录并探究系统。如果你有任何有关控制台命令的问题，请务必阅读手册。

尝试下列例子

    $ man man
    $ man bash
    $ man builtins
    $ man grep
    $ man ls

手册的风格可能让人有点难以习惯，因为它们都相当简洁，尤其是比较老旧、非常传统的那些手册。但是，一旦你习惯了它，你来欣赏它们的简洁。

请注意，许多 类 Unix 命令（包含来自 GNU 和 BSD 的）都可以显示简短的帮助信息，你可以使用下列的其中一种方式来查看它（有时不带任何参数也可以）。

    $ commandname --help
    $ commandname -h

## 1.5. 简单 shell 命令

现在你已经对如何使用 Debian 系统有了一些感觉。让我们深入了解 Debian 系统中命令执行的机制。在这里，我已经为新手简化了现实。有关确切的解释，请参见 bash(1)。

一般的命令由有序的组件构成。

1. 设置变量值（可选）

2. 命令名

3. 参数（可选）

4. 重定向（可选：`>` , `>>` , `<` , `<<` 等等）

5. 控制操作（可选：`&&` ， `||` ， 换行符 , `;` , `&` , `(` , `)`）

### 1.5.1. 命令执行和环境变量

一些环境变量的值会改变部分 Unix 命令的行为。

环境变量的默认值由 PAM 系统初始化，其中一些会被某些应用程序重新设定。

- PAM（可插拔身份验证模块）系统的模块，比如 `pam_env` 模块，可以通过 `/etc/pam.conf`"、 "`/etc/environment`"和"`/etc/default/locale`"设置环境变量。

- The modern GUI environment run from the user mode systemd unit (see systemd.unit(5)) sets its environment variables by a file in the "`~/.config/environment.d/`" directory.

- 用户特有的程序初始化时，可以重新设置在 "`~/.profile`"、"`~/.bash_profile`" 和 "`~/.bashrc`" 中设置的环境变量。

### 1.5.2. “`$LANG`”变量

默认的语言环境是在 "`$LANG`" 环境变量中定义，它在安装的时候配置为 "`LANG=xx_YY.UTF-8`"，或者在接下来的 GUI（图形用户界面）中配置，例如在 GNOME 中是，"设置" → "区域 & 语言" → "语言" / "格式"。

| 注意
目前建议最好用变量 "`$LANG`" 来配置系统环境变量，只有在逼不得已的情况下才用 `$LC_*`" 开头的变量。

“`$LANG`” 变量的完整的语言环境值由3部分组成：“`xx_YY.ZZZZ`”。

表 1.18. 语言环境值的 3 个部分

语言环境值 | 说明
--- | ---
`xx` | ISO 639 语言代码（小写）例如“en”
`YY` | ISO 3166 国家代码（大写）例如“US”
`ZZZZ` | 编码，总是设置为“UTF-8”

表 1.19. 语言环境推荐列表

语言环境推荐 | 语言（地区）
--- | ---
`en_US.UTF-8` | 英语（美国）
`en_GB.UTF-8` | 英语（大不列颠）
`fr_FR.UTF-8` | 法语（法国）
`de_DE.UTF-8` | 德语（德国）
`it_IT.UTF-8` | 意大利语（意大利）
`es_ES.UTF-8` | 西班牙语（西班牙）
`ca_ES.UTF-8` | 加泰隆语（西班牙）
`sv_SE.UTF-8` | 瑞典语（瑞典）
`pt_BR.UTF-8` | 葡萄牙语（巴西）
`ru_RU.UTF-8` | 俄语（俄国）
`zh_CN.UTF-8` | 汉语（中华人民共和国）
`zh_TW.UTF-8` | 汉语（中国台湾）
`ja_JP.UTF-8` | 日语（日本）
`ko_KR.UTF-8` | 韩语（韩国）
`vi_VN.UTF-8` | 越南语（越南）

使用 shell 命令行按顺序执行下列典型的命令。

    $ echo $LANG
    en_US.UTF-8
    $ date -u
    Wed 19 May 2021 03:18:43 PM UTC
    $ LANG=fr_FR.UTF-8 date -u
    mer. 19 mai 2021 15:19:02 UTC

这里，date(1)程序执行时使用了不同的语言环境值。

- 第一个命令，“`$LANG`” 设置为系统的默认语言环境值 “`en_US.UTF-8`”。

- 第二个命令，“`$LANG`”设置为法语的 UTF-8 语言环境值“`fr_FR.UTF-8`”。

大多数的命令在执行时并没有预先定义环境变量。对于上面的例子，你也可以选择如下的方式。

    $ LANG=fr_FR.UTF-8
    $ date -u
    mer. 19 mai 2021 15:19:24 UTC

| 提示
提交一个 BUG 报告的时候，如果使用的是非英语的环境，在 "`LANG=en_US.UTF-8`" 语言环境环境下对命令进行运行和检查会更好一些。

对于语言环境配置的细节，参见 第 8.1 节 “语言环境”。

### 1.5.3. "`$PATH`" 变量

当你在 Shell 里输入命令的时候，Shell 会在 "`$PATH`" 变量所包含的目录列表里进行搜索，"`$PATH`" 变量的值也叫作 Shell 的搜索路径。

在默认的 Debian 安装过程中，所使用的用户账号的 "`$PATH`" 环境变量可能不包括 "`/usr/sbin`" 和"`/usr/sbin`"目录。例如，`ifconfig` 命令就需要指定完整的路径 "`/usr/sbin/ifconfig`"。(类似地， `ip` 命令是在 "`/usr/bin`" 目录下)

可以在 Bash 脚本文件 "`~/.bash_profile`" 或 "`~/.bashrc`" 中改变 "`$PATH`" 环境变量的值。

### 1.5.4. "`$HOME`" 变量

很多命令在用户目录中都存放了用户指定的配置，然后通过配置的内容来改变它的执行方式，用户目录通常用 "`$HOME`" 变量来指定。

表 1.20. "`$HOME`" 变量值列表

"`$HOME`" 变量的值 | 程序运行环境
--- | ---
`/` | 初始进程执行的程序（守护进程）
`/root` | root 用户权限 Shell 执行的程序
`/home/normal_user` | 普通用户权限Shell执行的程序
`/home/normal_user` | 普通用户 GUI 桌面菜单执行的程序
`/home/normal_user` | 用 root 用户权限来执行程序 "`sudo program`"
`/root` | 用 root 用户权限执行程序 "`sudo -H program`"

| 提示
Shell 扩展 "`~/`" 为转入当前用户的主目录，也就是"`$HOME/`"。Shell 扩展 "`~foo/`" 为`foo` 的目录，也就是 "`/home/foo/`"。

如果 `$HOME` 对你的程序不可用，参见 第 12.1.5 节 “Shell 环境变量”。

### 1.5.5. 命令行选项

一些命令附带参数。这些参数以 "`-`" 或 "`--`" 开头，通常称之为选项，用来控制命令的执行方式。

    $ date
    Thu 20 May 2021 01:08:08 AM JST
    $ date -R
    Thu, 20 May 2021 01:08:12 +0900

这里的命令参数 "`-R`" 改变 date(1) 命令输出为 RFC2822 标准的日期字符格式。

### 1.5.6. Shell 通配符

经常有这种情况你期望命令成串自动执行而不需要挨个输入，将文件名扩展为 glob，(有时候被称为 通配符)，以此来满足这方面的需求。

表 1.21. Shell glob 模式

shell glob 模式 | 匹配规则描述
--- | ---
`*` | 不以 "`.`" 开头的文件名(段)
`.*` | filename (segment) started with "`.`" (in Bash 5.2 and later, "`.`" and "`..`" are excluded by default)
`?` | 精确字符
`[…]` | 包含在括号中的任意字符都可以作为精确字符
`[a-z]` | "`a`" 到 "`z`" 之间的任意一个字符都可以作为精确字符
`[^…]` | 除了包含在括号中的任意字符 ( "`1^` 2"除外 )，其它字符都可以作为精确字符

尝试下列例子

    $ mkdir junk; cd junk; touch 1.txt 2.txt 3.c 4.h .5.txt ..6.txt
    $ echo *.txt
    1.txt 2.txt
    $ echo *
    1.txt 2.txt 3.c 4.h
    $ echo *.[hc]
    3.c 4.h
    $ echo .*
    .5.txt ..6.txt
    $ echo .*[^.]*
    .5.txt ..6.txt
    $ echo [^1-3]*
    4.h
    $ cd ..; rm -rf junk

参见 glob(7)。

| 注意
与 shell 通用的文件名匹配方式不同， 使用 "`-name` " 选项的 `find` (1)，其 shell 模式"`*` " ，匹配以"`.` "开始的文件名.（新 POSIX 的特性)

| 注意
BASH 可以使用内置的 shopt 选项如 "`dotglob` "，"`noglob` ","`nocaseglob` ","`nullglob` ","`extglob` "定制全局行为,使用 `bash` ( 1 )查看详细说明。

### 1.5.7. 命令的返回值

每个命令都会返回它的退出状态（变量：“`$?`”）作为返回值。

表 1.22. 命令的退出代码

命令的退出状态 | 数字返回值 | 逻辑返回值
--- | --- | ---
success | zero, 0 | TRUE
error | non-zero, -1 | FALSE

尝试下列例子。

    $ [ 1 = 1 ] ; echo $?
    0
    $ [ 1 = 2 ] ; echo $?
    1

| 注意
请注意，success 是逻辑 TRUE ，0（zero）则是它的值。这有些不直观，需要在这里提一下。

### 1.5.8. 典型的顺序命令和 shell 重定向

让我们试着记住下面 Shell 命令里部分命令行所使用的命令习语。

表 1.23. Shell 命令常见用法

命令常见用法 | 说明
--- | ---
`command &` | 在子 shell 的后台 中执行 `command`
`command1 | command2` | 通过管道将 `command1` 的标准输出作为 `command2` 的标准输入（并行执行）
`command1 2>&1 | command2` | 通过管道将 `command1` 的标准输出和标准错误作为 `command2` 的标准输入（并行执行）
`command1 ; command2` | 依次执行 `command1` 和 `command2`
`command1 && command2` | 执行 `command1`；如果成功，按顺序执行 `command2`（如果 `command1` 和 `command2` 都执行成功了，返回 success ）
`command1 || command2` | 执行 `command1`；如果不成功，按顺序执行 `command2`（如果 `command1` 或 `command2` 执行成功，返回 success ）
`command > foo` | 将 `command` 的标准输出重定向到文件 `foo`（覆盖）
`command 2> foo` | 将 `command` 的标准错误重定向到文件 `foo`（覆盖）
`command >> foo` | 将 `command` 的标准输出重定向到文件 `foo`（附加）
`command 2>> foo` | 将 `command` 的标准错误重定向到文件 `foo`（附加）
`command > foo 2>&1` | 将 `command` 的标准输出和标准错误重定向到文件 `foo`
`command < foo` | 将 `command` 的标准输入重定向到文件 `foo`
`command << delimiter` | 将 `command` 的标准输入重定向到下面的命令行，直到遇到“`delimiter`”（here document）
`command <<- delimiter` | 将 `command` 的标准输入重定向到下面的命令行，直到遇到“`delimiter`”（here document，命令行中开头的制表符会被忽略）

Debian 系统是一个多任务的操作系统。后台任务让用户能够在一个 shell 中执行多个程序。后台进程的管理涉及 shell 的内建命令：`jobs`、`fg`、`bg` 和 `kill`。请阅读 bash(1) 中的章节：“SIGNALS”、“JOB CONTROL” 和 “builtins(1)”。

尝试下列例子

    $ </etc/motd pager

    $ pager </etc/motd

    $ pager /etc/motd

    $ cat /etc/motd | pager

尽管4个 shell 重定向的例子都会显示相同的结果，但最后一个例子毫无意义地运行了额外的 `cat` 命令浪费了资源。

shell 允许你使用 `exec` 通过任意一个文件描述符来打开文件。

    $ echo Hello >foo
    $ exec 3<foo 4>bar  # open files
    $ cat <&3 >&4       # redirect stdin to 3, stdout to 4
    $ exec 3<&- 4>&-    # close files
    $ cat bar
    Hello

预定义的文件描述符0-2。

表 1.24. 预定义的文件描述符

设备 | 说明 | 文件描述符
--- | --- | ---
`stdin` | 标准输入 | 0
`stdout` | 标准输出 | 1
`stderr` | 标准错误 | 2

### 1.5.9. 命令别名

你可以为经常使用的命令设置一个别名。

尝试下列例子

    $ alias la='ls -la'

现在，“`la`”是“`ls -al`”的简写形式，并同样会以长列表形式列出所有的文件。

你可以使用 `alias` 来列出所有的别名（参见 bash(1) 中的“SHELL BUILTIN COMMANDS”）。

    $ alias
    ...
    alias la='ls -la'

你可以使用 `type` 来确认命令的准确路径或类型（参见 bash(1) 中的“SHELL BUILTIN COMMANDS”）。

尝试下列例子

    $ type ls
    ls is hashed (/bin/ls)
    $ type la
    la is aliased to ls -la
    $ type echo
    echo is a shell builtin
    $ type file
    file is /usr/bin/file

`ls` 在最近被使用过，而 “`file`” 没有，因此 “`ls`” 标记为 “hashed”（被录入哈希表），即 shell 有一个内部的记录用来快速访问 “`ls`” 所处的位置。

| 提示
参见第 9.3.6 节 “有颜色输出的命令”。

## 1.6. 类 Unix 的文本处理

在类 Unix 的工作环境中，文本处理是通过使用管道组成的标准文本处理工具链完成的。这是另一个重要的 Unix 创新。

### 1.6.1. Unix 文本工具

这里有一些在类 Unix 系统中经常使用到的标准文本处理工具。

- 没有使用正则表达式：

  - cat(1) 连接文件并输出全部的内容。

  - tac(1) 连接文件并反向输出。

  - cut(1) 选择行的一部分并输出。

  - head(1) 输出文件的开头。

  - tail(1) 输出文件的末尾。

  - sort(1) 对文本文件的行进行排序。

  - uniq(1) 从已排序的文件中移除相同的行。

  - tr(1) 转换或删除字符。

  - diff(1) 对文件的行进行对比。

- 默认使用基础正则表达式（ BRE ）：

  - ed(1) 是一个原始行编辑器。

  - sed(1) 是一个流编辑器。

  - grep(1) 匹配满足 pattern 的文本。

  - vim(1) 是一个屏幕编辑器。

  - emacs(1) 是一个屏幕编辑器。（有些扩展的 BRE ）

- 使用扩展的正则表达式（ ERE ）：

  - awk(1) 进行简单的文本处理。

  - egrep(1) 匹配满足多个 pattern 的文本。

  - `tcl`(3tcl) 可以进行任何你想得到的文本处理：参见 re_syntax(3) 。经常与 `tk`(3tk) 一起使用。

  - perl(1) 可以进行任何你想得到的文本处理。参见 perlre(1) 。

  - `pcre2-util` 软件包中的 pcre2grep(1) 可以匹配满足 Perl 兼容正则表达式（PCRE） 模式的文本。

  - 带有 `re` 模块的 python(1) 可以进行任何你想得到的文本处理。参见“`/usr/share/doc/python/html/index.html`”。

如果你不确定这些命令究竟做了什么，请使用“`man command`” 来自己把它搞清楚吧。

| 注意
排序的顺序和表达式的范围取决于语言环境。如果你想要获得一个命令的传统行为，可以使用 C 语言环境或 C.UTF-8 语言环境代替原来的 UTF-8 语言环境（参见 第 8.1 节 “语言环境”）。

| 注意
Perl 正则表达式（ perlre(1) ）、Perl 兼容正则表达式（PCRE） 和 Python 的`re` 模块提供的正则表达式与一般的 ERE 相比多了许多通用的扩展。

### 1.6.2. 正则表达式

正则表达式被使用在许多文本处理工具中。它们类似 shell 的通配符，但更加复杂和强大。

正则表达式描述要匹配的模式，它是由文本字符和元字符构成的。

元字符仅仅是带有特殊含义的字符。它们有两种主要的形式，BRE 和 ERE ，使用哪种取决于上述的文本工具。

表 1.25. BRE 和 ERE 中的元字符

BRE | ERE | 正则表达式的描述
--- | --- | ---
`\ . [ ] ^ $ *` | `\ . [ ] ^ $ *` | 通用的元字符
`\+ \? \( \) \{ \} \|` |  | BRE 独有的“`\`”转义元字符
| `+ ? ( ) { } |` | ERE 独有的不需要“`\`”转义的元字符
`c` | `c` | 匹配非元字符 “`c`”
`\c` | `\c` | 匹配一个字面意义上的字符 “`c`”，即使 “`c`” 本身是元字符
`.` | `.` | 匹配任意字符，包括换行符
`^` | `^` | 字符串的开始位置
`$` | `$` | 字符串的结束位置
`\<` | `\<` | 单词的开始位置
`\>` | `\>` | 单词的结束位置
`[abc…]` | `[abc…]` | 匹配在 “`abc...`” 中的任意字符
`[^abc…]` | `[^abc…]` | 匹配除了 “`abc...`” 中的任意字符
`r*` | `r*` | 匹配零个或多个 “`r`”
`r\+` | `r+` | 匹配一个或多个 “`r`”
`r\?` | `r?` | 匹配零个或一个 “`r`”
`r1\|r2` | `r1|r2` | 匹配一个 “`r1`” 或 “`r2`”
`\(r1\|r2\)` | `(r1|r2)` | 匹配一个 “`r1`” 或 “`r2`“ ，并作为括号内的正则表达式

`emacs` 中的正则表达式基本上是 BRE 但含有 ERE 中的元字符 “`+`” 和 “`?`” 。因此，在 `emacs` 中没必要使用 “`\`” 来转义它们。

grep(1) 可以使用正则表达式来进行文本搜索。

尝试下列例子

    $ egrep 'GNU.*LICENSE|Yoyodyne' /usr/share/common-licenses/GPL
    GNU GENERAL PUBLIC LICENSE
    GNU GENERAL PUBLIC LICENSE
    Yoyodyne, Inc., hereby disclaims all copyright interest in the program

| 提示
参见第 9.3.6 节 “有颜色输出的命令”。

### 1.6.3. 替换表达式

对于替换表达式，一些字符有特殊的含义。

表 1.26. 替换表达式

替换表达式 | 替换表达式替换的文本
--- | ---
`&` | 正则表达式所匹配的内容（在 `emacs` 中使用 `\&` ）
`\n` | 前 n 个括号的正则表达式匹配的内容（ “n” 是数字）

对 Perl 替换字符串来说，应使用“$&”而非“&”，应使用“$n”而非“\n”。

尝试下列例子

    $ echo zzz1abc2efg3hij4 | \
    sed -e 's/\(1[a-z]*\)[0-9]*\(.*\)$/=&=/'
    zzz=1abc2efg3hij4=
    $ echo zzz1abc2efg3hij4 | \
    sed -E -e 's/(1[a-z]*)[0-9]*(.*)$/=&=/'
    zzz=1abc2efg3hij4=
    $ echo zzz1abc2efg3hij4 | \
    perl -pe 's/(1[a-z]*)[0-9]*(.*)$/=$&=/'
    zzz=1abc2efg3hij4=
    $ echo zzz1abc2efg3hij4 | \
    sed -e 's/\(1[a-z]*\)[0-9]*\(.*\)$/\2===\1/'
    zzzefg3hij4===1abc
    $ echo zzz1abc2efg3hij4 | \
    sed -E -e 's/(1[a-z]*)[0-9]*(.*)$/\2===\1/'
    zzzefg3hij4===1abc
    $ echo zzz1abc2efg3hij4 | \
    perl -pe 's/(1[a-z]*)[0-9]*(.*)$/$2===$1/'
    zzzefg3hij4===1abc

请特别注意这些括号正则表达式的格式，以及这些被匹配的文本在不同的工具中是如何被替换的。

这些正则表达式在一些编辑器中也可以用来移动光标和替换文本。

在 shell 命令行行末的反斜杠 “`\`” 会跳脱一个换行符（作为空白符），并将光标移动到下一行的行首。

请阅读所有相关手册来学习这些命令。

### 1.6.4. 正则表达式的全局替换

ed(1) 命令可以在 “`file`” 中将所有的 “`FROM_REGEX`” 替换成 “`TO_TEXT`” 。

    $ ed file <<EOF
    ,s/FROM_REGEX/TO_TEXT/g
    w
    q
    EOF

sed(1) 命令可以在 “`file`” 中将所有的 “`FROM_REGEX`” 替换成 “`TO_TEXT`” 。

    $ sed -i -e 's/FROM_REGEX/TO_TEXT/g' file

vim(1) 命令可以通过使用 ex(1) 命令在 “`file`” 中将所有的 “`FROM_REGEX`” 替换成 “`TO_TEXT`” 。

    $ vim '+%s/FROM_REGEX/TO_TEXT/gc' '+update' '+q' file

| 提示
上面的 “`c`” 标志可以确保在每次替换时都进行交互式的确认。

多个文件（ “`file1`”，“`file2`” 和 “`file3`” ）可以使用 vim(1) 或 perl(1) 通过正则表达式进行类似的处理。

    $ vim '+argdo %s/FROM_REGEX/TO_TEXT/gce|update' '+q' file1 file2 file3

| 提示
上面的 “`e`” 标志是为了防止 “No match” 错误中断替换。

    $ perl -i -p -e 's/FROM_REGEX/TO_TEXT/g;' file1 file2 file3

在 perl(1)例子中 , "`-i`" 是在每一个目标文件的原处编辑，"`-p`" 是表示循环所有给定的文件。

| 提示
使用参数 “`-i.bak`” 代替 “`-i`” ，可以在文件名后添加 “`.bak`” 再保存。对于复杂的替换，这使得从错误中恢复变得容易。

| 注意
ed(1) 和 vim(1) 使用 BRE ； perl(1) 使用 ERE 。

### 1.6.5. 从文本文件的表格中提取数据

下面有一个文本文件 “`DPL`” ，里面含有 2004 年以前 Debian 项目的领导者名字和起始日期，并以空格分隔。

    Ian     Murdock   August  1993
    Bruce   Perens    April   1996
    Ian     Jackson   January 1998
    Wichert Akkerman  January 1999
    Ben     Collins   April   2001
    Bdale   Garbee    April   2002
    Martin  Michlmayr March   2003

| 提示
参见 “Debian 简史” 获取最新的 Debian 领导阶层历史。

Awk 经常被用来从这种类型的文件中提取数据。

尝试下列例子

    $ awk '{ print $3 }' <DPL                   # month started
    August
    April
    January
    January
    April
    April
    March
    $ awk '($1=="Ian") { print }' <DPL          # DPL called Ian
    Ian     Murdock   August  1993
    Ian     Jackson   January 1998
    $ awk '($2=="Perens") { print $3,$4 }' <DPL # When Perens started
    April 1996

Shell （例如 Bash ）也可以用来分析这种文件。

尝试下列例子

    $ while read first last month year; do
        echo $month
      done <DPL
    ... same output as the first Awk example

内建命令 `read` 使用 “`$IFS`” （内部域分隔符）中的字符来将行分隔成多个单词。

如果你将 “`$IFS`” 改变为 “`:`” ，你可以很好地使用 shell 来分析 “`/etc/passwd`”。

    $ oldIFS="$IFS"   # save old value
    $ IFS=':'
    $ while read user password uid gid rest_of_line; do
        if [ "$user" = "bozo" ]; then
          echo "$user's ID is $uid"
        fi
      done < /etc/passwd
    bozo's ID is 1000
    $ IFS="$oldIFS"   # restore old value

（如果要用 Awk 做到相同的事，使用 “`FS=':'`” 来设置域分隔符。）

IFS 也被 shell 用来分割参数扩展、命令替换和算术扩展的结果。这不会出现在双引号或单引号中。 IFS 的默认值为 空格、tab 和换行符。

请谨慎使用 shell 的 IFS 技巧。当 shell 将脚本的一部分解释为对它的输入时，会发生一些奇怪的事。

    $ IFS=":,"                        # use ":" and "," as IFS
    $ echo IFS=$IFS,   IFS="$IFS"     # echo is a Bash builtin
    IFS=  , IFS=:,
    $ date -R                         # just a command output
    Sat, 23 Aug 2003 08:30:15 +0200
    $ echo $(date -R)                 # sub shell --> input to main shell
    Sat  23 Aug 2003 08 30 36 +0200
    $ unset IFS                       # reset IFS to the default
    $ echo $(date -R)
    Sat, 23 Aug 2003 08:30:50 +0200

### 1.6.6. 用于管道命令的小片段脚本

下面的脚本作为管道的一部分，可以做一些细致的事情。

表 1.27. 管道命令的小片段脚本列表

脚本片段（在一行内输入） | 命令效果
--- | ---
`find /usr -print` | 找出"`/usr`"下的所有文件
`seq 1 100` | 显示 1 到 100
`| xargs -n 1 command` | 把从管道过来的每一项作为参数，重复执行命令
`| xargs -n 1 echo` | 把从管道过来的，用空格隔开的项，分隔成多行
`| xargs echo` | 把从管道过来的所有行合并为一行
`| grep -e regex_pattern` | 从管道过来，包含有 regex_pattern的行，提取出来
`| grep -v -e regex_pattern` | 把从管道过来，不包含有 regex_pattern的行，提取出来
`| cut -d: -f3 -` | 把从管道过来，用 "`:`"分隔的第三列提取出来 (passwd 文件等。)
`| awk '{ print $3 }'` | 把用空格隔开的第三列提取出来
`| awk -F'\t' '{ print $3 }'` | 把用 tab 键隔开的第三列提取出来
`| col -bx` | 删除退格键，扩展 tab 键为空格键
`| expand -` | 扩展 tab 键到空格键
`| sort| uniq` | 排序并删除重复行
`| tr 'A-Z' 'a-z'` | 将大小字母转换为小写字母
`| tr -d '\n'` | 将多行连接为一行
`| tr -d '\r'` | 删除换行回车符
`| sed 's/^/# /'` | 在每行行首增加一个"`#`"符
`| sed 's/\.ext//g'` | 删除 "`.ext`"
`| sed -n -e 2p` | 显示第二行
`| head -n 2 -` | 显示最前面两行
`| tail -n 2 -` | 显示最后两行

使用 find(1) 和 xargs(1)，单行 shell 脚本能够在多个文件上循环使用，可以执行相当复杂的任务。参见 第 10.1.5 节 “查找文件的语法” 和 第 9.4.9 节 “使用文件循环来重复一个命令”.

当使用 shell 交互模式变得太麻烦的时候，请考虑写一个 shell 脚本(参见 第 12.1 节 “Shell 脚本”).

[1] 即使旧的 `vim` 也能够启动完全的 "`nocompatible`" 模式，通过使用 "`-N`" 选项启动。

序言 |  | 第 2 章 Debian 软件包管理