使用命令行
本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.1 的内容。这个主题的权值是 5。
在本节中,学习以下主题:
本节简要地介绍 bash shell 的一些主要特性,重点是那些对于认证很重要的特性。但是这个 shell 是一个非常丰富的环境,我们鼓励您进一步探索它。有许多优秀的书籍讨论 UNIX 和 Linux shell,尤其是 bash shell。
bash shell
bash shell 是 Linux 可以使用的几种 shell 之一。它也称为 Bourne-again shell,这个名字来自 Stephen Bourne,以前的一个 shell(/bin/sh)的创建者。bash 大体上与 sh 兼容,但是在函数和编程功能方面提供了许多改进。它结合了来自 Korn shell(ksh)和 C shell(csh)的特性,是一种符合 POSIX 的 shell。
在深入研究 bash 之前,要记住 shell 是一种接受并执行命令的程序。它还支持编程构造,允许从比较小的部分构建出复杂的命令。这些复杂的命令(即脚本)可以保存为文件,就成了新的命令。实际上,典型 Linux 系统上的许多命令 就是 脚本。
shell 有一些内置的 命令,比如 cd
、break
和 exec
。其他命令是外部的。
shell 还使用三种标准 I/O 流:
输入流向命令提供输入,输入通常来自终端键盘。输出流输出文本字符,一般是在终端上。终端原来是 ASCII 打字机或显示终端,但是现在常常是图形桌面上的窗口。关于如何对这些标准 I/O 流进行重定向的更多细节,在本教程后面的 流、管道和重定向 一节中讨论。本节主要在较高层面上讨论重定向。
对于本教程的其余部分,假设您知道如何获得 shell 提示符。如果您不知道,developerWorks 文章 “Basic tasks for new Linux developers” 会教您如何执行这个任务和其他基本任务。
如果使用没有图形桌面的 Linux 系统,或者在图形桌面上打开了终端窗口,那么就会进入提示符,可能像清单 1 所示的这样。
|
如果作为根用户(即超级用户)登录,那么提示符可能像清单 2 所示的一样。
|
根用户有相当大的能力,所以使用它要谨慎。在 具有根特权时,大多数提示符的末尾有一个磅符号(#)。一般用户特权常常用另一个字符表示,通常是美圆符号($)。您机器上的实际提示符可能看起来与本教 程中的例子不一样。您的提示符可能包含用户名、主机名、当前目录、输出提示符的日期或时间等等。
|
本教程中的约定
针对 LPI 101 和 102 考试的这些 developerWorks 教程包含一些取自真实 Linux 系统的代码示例,使用了这些系统的默认提示符。我们的根提示符的末尾有 #,所以可以将它们与一般用户提示符(末尾有 $)区分开。在关于这个主题的许多书中都采用这种约定。在任何例子中都要仔细注意提示符。
|
命令和序列
现在有提示符了,我们来看看可以用它做什么。shell 的主要功能是解释用户的命令,从而使用户可以与 Linux 系统进行交互。在 Linux(和 UNIX)系统上,命令有一个命令名,然后是选项 和参数。一些命令没有选项和参数,一些命令有选项但没有参数,其他命令没有选项但有参数。
如果一行包含 # 字符,那么此行上的所有其余字符都被忽略。所以 # 字符既可以表示注释,也可以代表根提示符。具体意义应该很容易从上下文判断出来。
echo
echo
命令将它的参数输出(即回显)到终端上,如清单 3 所示。
|
在清单 3 的第 3 个示例中,所有多余的空白在输出中压缩成单一空格。要避免这种情况,需要引用 字符串,可以使用双引号(")或单引号(')。bash 使用空白 (比如空格、制表符和新行字符)将输入行分割为记号(token),然后传递给命令。对字符串进行引用会保留多余的空白,并使整个字符串成为一个记号。在上面的例子中,命令名后面的每个记号都是一个参数,所以分别有 1、2、4 和 1 个参数。
echo 命令有两个选项。一般情况下,echo 将在最后在输出中附加一个新行字符。可以使用 -n
选项抑制这种行为。使用 -e
选项使某些用反斜线进行转义的字符具有特殊意义。表 3 显示了一部分转义字符。
转义 序列 |
功能 |
---|---|
/a | 警报(铃声) |
/b | 退格 |
/c | 抑制末尾的新行字符(与 -n 选项的功能相同) |
/f | 换页(在视频显示器上就会清空屏幕) |
/n | 新行 |
/r | 回车 |
/t | 水平制表符 |
转义和行延续
在 bash 中使用反斜线有一个小问题。如果反斜线字符(/)不在引号中,那么它就作为转义,让 bash 保留后面字符的字面意义。这对于特殊的 shell 元字符是必要的,这个问题稍后讨论。这个规则有一个例外:反斜线后面跟着新行会使 bash 把这些字符序列视为行延续请求。这对于将长的行进行分割很方便,尤其是在 shell 脚本中。
要想让 echo
命令或使用相似转义控制字符的众多命令能够正确地处理上面描述的序列,必须将转义序列包含在引号中,或者作为引号中的字符串的一部分,否则就要使用第二个反斜线,让 shell 保留序列的字面意义。清单 4 显示 / 的各种用法。
|
注意,如果在输入的行中有不匹配的引号,那么 bash 会显示一个特殊的提示符(>)。输入字符串延续到第二行并包含一个新行字符。
bash shell 元字符和控制操作符
bash 有几个元字符, 如果不在引号中的话,这些字符也将输入分割为单词。 除了空格之外,还有 '|'、'&'、';'、'('、')'、'<' 和 '>'。在本教程的其他几节中将详细讨论其中一部分元字符。目前要注意,如果希望在文本中包含元字符,就必须将它放在引号中,或者用反斜线(/)进 行转义,如清单 4 所示。
新行字符和某些元字符或元字符对也作为控制操作符。 这包括 '||'、'&&'、'&'、';'、';;'、'|'、'(' 和 ')'。这些控制操作符中的一部分允许创建命令的序列 或列表。
最 简单的命令序列只是由分号(;)分隔的两个命令。每个命令依次执行。在任何可编程环境中,命令都会返回一个表示成功或失败的标志;Linux 命令常常返回零值来表示成功,返回非零值表示失败。可以使用 && 和 || 控制操作符在命令列表中引入一些条件处理。如果用控制操作符 && 分隔两个命令,那么第二个命令当且仅当第一个命令返回值为零的退出码时执行。如果用 || 分隔命令,那么第二个命令当且仅当第一个命令返回非零退出码时执行。清单 5 显示了使用 echo 命令的一些命令序列。这里没有什么有意思的事儿,因为 echo 只是返回 0,但是在后面使用更多命令时您将看到更多的例子。
|
退出
可以使用 exit
命令终止 shell。还可以可选地通过参数提供退出码。如果在图形桌面上的终端窗口中运行 shell,那么窗口将关闭。同样,如果使用 ssh 或 telnet 等命令连接到了远程系统,那么连接将终止。在 bash shell 中,还可以按住 Ctrl 键并按下 d 键来退出。
我 们来看看另一个控制操作符。如果将命令或命令列表放在圆括号中,那么命令或命令序列会在一个子 shell 中执行,所以 exit 命令会退出子 shell,而不是退出您当前工作的 shell。清单 6 显示用 && 和 || 进行组合的一个例子。
|
本教程中稍后会使用更多的命令序列。
|
环境变量
在 bash shell 中运行时,许多东西组成了环境,比如提示符的形式、主目录、工作目录、shell 的名称、已经打开的文件、已经定义的函数等等。环境包含许多变量,可能由 bash 设置,也可能由用户设置。bash shell 还允许有 shell 变量,可以将这些变量导出 到环境中,供 shell 中运行的其他进程使用,或者供从当前 shell 产生的其他 shell 使用。
环境变量和 shell 变量都有名称。通过在变量名前面加 '$' 来引用变量值。表 4 显示了一些常见的 bash 环境变量。
名称 | 功能 |
---|---|
USER | 已登录用户的名称 |
UID | 已登录用户的数字用户 id |
HOME | 用户的主目录 |
PWD | 当前工作目录 |
SHELL | shell 的名称 |
$ | 进程 id(即正在运行的 bash shell 进程或其他进程的 PID) |
PPID | 启动这个进程的进程的进程 id(即父进程的 id) |
? | 最后一个命令的退出码 |
清单 7 显示了通过这些常见的 bash 变量可以看到什么。
|
|
创建或设置 shell 变量的方式是输入名称,后面直接跟着等号(=)。变量是大小写敏感的,所以 var1 和 VAR1 是不同的变量。按照约定,变量(尤其是导出的变量)是大写的,但这不是必须的。从技术上说,$$ 和 $? 是 shell 参数 而不是变量。只能引用它们,不能赋值。
在创建 shell 变量时,常常希望将它导出 到环境中,使得从这个 shell 启动的其他进程可以使用它。导出的变量对于父 shell 是 不 可用的。使用 export
命令导出变量名。作为 bash 中的快捷方式,可以在一步中同时对变量进行赋值和导出。
为了说明赋值和导出,我们在 bash shell 中运行 bash 命令,然后从这个新的 bash shell 运行 Korn shell(ksh)。我们将使用 ps
命令来显示正在运行的命令的相关信息。在本教程后面学习 进程状态 时,将进一步了解 ps
。
|
注意:
echo
命令只显示了 VAR2、VAR3 和 VAR4 的值,这证明 VAR1 没有导出。尽管提示符改变了,但是 SHELL 变量的值没有改变。您觉得吃惊吗?不能总是依赖于 SHELL 来了解正在运行哪个 shell,但是 ps
命令可以指出实际的命令。注意,ps
在第一个 bash shell 前面放了一个连字符(-),表示这是登录 shell。 前 面讨论引用时提到,可以使用单引号,也可以使用双引号。这两种引号之间有一个重要的差异。shell 将双引号(")之间的 shell 变量展开,但是在使用单引号(')时不进行展开。在前面的示例中,我们在 shell 中启动另一个 shell,并获得新的进程 id。使用 -c
选项,可以将命令传递给另一个 shell,这个 shell 将执行命令并返回。如果将带引号的字符串作为命令传递,那么外层 shell 将去掉引号并传递字符串。如果使用双引号,那么变量展开发生在传递字符串 之前,所以结果可能不是您希望的。shell 和命令将运行在另一个进程中,所以它们有不同的 PID。清单 9 演示了这些概念。顶层 bash shell 的 PID 突出显示。
|
到 目前为止,所有的变量引用都以空格结束,所以变量名在哪里结束是很明显的。实际上,变量名只能由字母、数字或下划线字符组成。当找到另一个字符 时,shell 就知道变量名结束了。有时候,可能需要在含义不明确的表达式中使用变量。在这种情况下,可以使用花括号使变量名明确,见清单 10。
|
env
env
命令如果不带任何选项或参数,就显示当前的环境变量。还可以使用它在定制的环境中执行命令。-i
(或只是 -
)选项在运行命令之前清理当前环境,而 -u
选项清除您不希望传递的环境变量。
清单 11 显示了不带任何参数的 env
命令的部分输出,然后是三个不用父环境调用不同 shell 的例子。在讨论之前,先认真看看这些例子。
|
注意,bash 已经设置了 SHELL 变量,但是没有将它导出到环境中,尽管 bash 在环境中创建了另外三个变量。在 ksh 示例中,有两个环境变量,但是试图回显 SHELL 变量的值时只得到了一个空行。最后,tcsh 没有创建任何环境变量,并在我们试图引用 SHELL 的值时产生一个错误。
清除和设置
清单 11 显示了几种 shell 在处理变量和环境方面的行为差异。本教程主要关注 bash,但是您应该明白所有 shell 并不采用相同的处理方式。另外,根据 shell 是否是登录 shell,处理方式也会有差异。在目前,我们只需知道登录 shell 就是在系统上进行登录时获得的 shell;如果愿意,可以作为登录 shell 启动其他 shell。上面使用 env -i
启动的三个 shell 不是登录 shell。请尝试将 -l
选项传递给 shell 命令本身,从而体会登录 shell 的行为有什么不同。
现在,我们来研究在这三个非登录 shell 中尝试显示 SHELL 变量值的结果:
可以使用 unset
命令将变量从 shell 变量列表中清除。如果变量已经导出到环境中,那么也会从环境中删除它。可以使用 set
命令控制 bash(或其他 shell)的工作方式的许多方面。set 是 shell 内置的命令,所以各种选项是与 shell 相关的。在 bash 中,-u
选项让 bash 在遇到未定义变量时报告错误,而不是像对待具有空值的已定义变量一样。可以在 set
中使用 -
打开各种选项,使用 +
关闭它们。可以使用 echo $-
显示当前设置的选项。
|
如果不带任何选项使用 set
命令,那么它显示所有 shell 变量及其值(如果有的话)。还有另一个命令,declare
,可以使用它创建、导出和显示 shell 变量的值。可以使用手册页研究其他 set
选项和 declare
命令。在本节后面我们将讨论 手册页。
exec
本节中研究的最后一个命令是 exec
。可以使用 exec
命令运行另一个程序来 替代 当前 shell。清单 13 启动一个子 bash shell,然后使用 exec
用 Korn shell 替代它。在退出 Korn shell 时,会回到原来的 bash shell(在这个例子中,PID 是 22985)。
|
|
命令历史
如果您一边阅读本教程,一边输入命令,那么可能会发现常常要多次使用同一个命令,要么是完全一样,要么是只有细微差异。好消息是 bash shell 可以维护命令的历史。 在默认情况下,历史功能是打开的。可以使用 set +o history
命令关闭它,使用 set -o history
重新打开。环境变量 HISTSIZE 告诉 bash 保留多少历史行。还有许多其他设置可以控制历史如何工作以及如何管理历史。完整的细节请参考 bash 手册页。
可以通过历史设施使用的一些命令如下:
|
清单 14 中的命令做了下面这些事:
还可以交互式地编辑历史。bash shell 使用 readline 库来管理命令编辑和历史。在默认情况下,用来在历史中进行移动和编辑的键和键组合与 GNU Emacs 编辑器中的相似。Emacs 键组合常常表示成 C-x 或 M-x,其中的 x 是常规键,C 和 M 分别是控制 和元 键。在典型的 PC 系统上,Ctrl 键作为 Emacs 控制键,Alt 键作为元键。表 5 总结了一些历史编辑功能。除了表 5 所示的键组合以外,光标移动键(比如右、左、上和下箭头)以及 Home 和 End 键常常按照符合逻辑的方式工作。在手册页中可以找到其他功能以及如何使用 readline init 文件(常常是主目录中的 inputrc)来定制这些选项。
命令 | 常用 PC 键 | 说明 |
---|---|---|
C-f | 右箭头 | 向右移动一格 |
C-b | 左箭头 | 向左移动一格 |
M-f | Alt-f | 移动到下一个单词的开头;GUI 环境常常用这个键组合来打开窗口的 File 菜单 |
M-b | Alt-b | 移动到前一个单词的开头 |
C-a | Home | 移动到行的开头 |
C-e | End | 移动到行的末尾 |
Backspace | Backspace | 删除光标前面的字符 |
C-d | Del | 删除光标后面的字符(Del 和 Backspace 功能可能配置为相反的意义) |
C-k | Ctrl-k | 删除(kill)到行的末尾并保存删除的文本供以后使用 |
M-d | Alt-d | 删除(kill)到单词的末尾并保存删除的文本供以后使用 |
C-y | Ctrl-y | 取回由删除命令删除的文本 |
如果您喜欢使用与 vi 相似的编辑模式操作历史,那么可以使用命令 set -o vi
切换到 vi 模式。使用 set -o emacs
返回到 emacs 模式。在 vi 模式中获取命令时,最初处于 vi 的插入模式中。关于 vi 编辑器的更多细节在 用 vi 进行文件编辑 一节中介绍。
|
路径
一些 bash 命令是内置的,其他命令是外部的。我们现在来看看外部命令,如何运行它们,以及如何知道命令是否是内部的。
shell 在哪里寻找命令?
外部命令只是文件系统中的文件。本教程后面的一节 基本文件管理 和针对主题 104 的教程讨论了更多细节。在 Linux 和 UNIX 系统上,所有文件都作为一个大型树结构的一部分,这个树结构的根是 /。在到目前为止提供的示例中,我们的当前目录都是用户的主目录。非根用户常常在 /home 目录中有自己的主目录,比如我的主目录是 /home/ian。根用户的主目录常常是 /root。如果输入命令名,那么 bash 会在您的路径 中寻找这个命令,路径是在 PATH 环境变量中指定的以分号分隔的目录列表。
如果想知道在输入某一字符串时执行的是什么命令,使用 which
或 type
命令。清单 15 显示我的默认路径以及几个命令的位置。
|
注意,路径中的所有目录都以 /bin 结尾。这是一种常见的约定,但不是要求。which
命令报告 ls
命令是一个别名(alias),而 set
命令无法找到。在这种情况下,可以认为它不存在或者是内置的命令。type
命令报告 ls
命令是一个别名,但是它识别出 set
命令是一个 shell 内置命令。它还报告有一个内置的 echo
命令以及 /bin 目录中的命令,which
也找到了这些命令。这两个命令还以不同的次序产生输出。
我们看到 ls
命令(用于列出目录内容)是一个别名。别名可以方便地将某些命令配置为使用不同的默认设置集,或者为命令提供替代名。在我们的示例中,--color=tty
选项使得目录列表按照文件或目录类型以不同颜色显示。尝试运行 dircolors --print-database
,从而了解如何控制颜色编码以及用哪种颜色表示哪种文件。
这些命令都有其他选项。根据需要,可以使用其中任意一个命令。如果我确定要寻找的是可执行文件并只需要它的完整路径,那么倾向于使用 which
。我发现 type
会提供更精确的信息,有时需要在 shell 脚本中使用。
运行其他命令
在清单 15 中看到,可执行文件的完整路径以 /(根目录)开头。例如,xclock 程序实际上是 /usr/X11R6/bin/xclock,即 /usr/X11R6/bin 目录中的一个文件。如果命令 不 在 PATH 设置中,那么仍然可以通过指定路径以及命令名来运行它。可以使用两种类型的路径:
pwd
命令报告)。这些路径不以 / 开头,但是至少包含一个 /。使用绝对路径时可以不管当前工作目录是什么,但是只有当命令在当前目录中时才可能使用相对路径。假设您正在主目录的 mytestbin 子目录中开发传统 “Hello World!” 程序的一个新版本。可以使用相对路径以 mytestbin/hello
的形式运行这个命令。在路径中可以使用两个特殊名称;一个点(.)是指当前目录,两个点(..)是指当前目录的父目录。因为主目录常常不在 PATH 中(而且一般来说应该不在),对于希望从主目录运行的任何可执行文件,需要显式地提供路径。例如,如果在主目录中有 hello 程序的副本,那么可以使用命令 ./hello
运行它。可以在绝对路径中使用 . 和 ..,尽管单个 . 在这种情况下不太有用。还可以使用波浪号(~)表示自己的主目录,用 ~username 表示名为 username 的用户的主目录。清单 16 中给出了一些示例。
|
改变工作目录
正如可以从系统中的各个目录执行程序,也可以使用 cd
命令改变当前的工作目录。cd
的参数必须是目录的绝对路径或相对路径。对于这个命令,可以在路径中使用 .、..、~ 和 ~username。如果不带参数使用 cd
,那么当前工作目录将改变为主目录。用一个连字符(-)作为参数意味着恢复原来的工作目录。主目录存储在 HOME 环境变量中,原来的工作目录存储在 OLDPWD 变量中,所以 cd
相当于 cd $HOME
,cd -
相当于 cd $OLDPWD
。通常我们会说改变目录,而不完整地说改变当前工作目录。
对于这个命令,还有一个环境变量 CDPATH,它包含在解析相对路径时应该搜索的以分号分隔的路径集(除了当前工作目录之外)。如果解析使用了来自 CDPATH 的路径,那么 cd
将输出得到的目录的完整路径。在正常情况下,成功的目录改变不会产生输出,只是提示符可能会改变。清单 17 给出了一些示例。
|
|
递归地应用命令
许多 Linux 命令可以递归地应用于一个目录树中的所有文件。例如,ls
命令有一个 -R
选项用于递归地列出目录内容,cp
、mv
、rm
和 diff
命令都有 -r
选项用于递归地应用它们。基本文件管理 一节将详细地讨论命令的递归应用。
|
命令替换
bash shell 有一种非常强大的功能,允许将一个命令的结果用作另一个命令的输入;这称为命令替换。实现方法是将您希望使用其结果的命令封闭在反单引号(`)中。这仍然是常用方法,但是有另一个方法可以更容易处理多个嵌套的命令,即将命令封闭在 $( 和 ) 之间。
在前一个教程 “LPI 101 考试准备(主题 102):Linux 安装与包管理” 中,我们看到 rpm
命令可以指出一个命令来自哪个包;我们使用命令替换功能作为简化技术。现在您知道我们当时究竟在做什么了。
在 shell 脚本中,命令替换是一种很有价值的工具,在命令行上也有用。清单 18 给出了一些示例,它们从一个相对路径获得绝对路径,寻找哪个 RPM 提供了 /bin/echo 命令,并(作为根用户)列出了硬盘上三个分区的标签。最后一个示例使用 seq
命令产生一系列整数。
|
|
手册页
本教程这一节中的最后一个主题是,如何通过手册页和其他文档来源获得 Linux 命令的文档。
手册页和小节
文档的主要(且传统的)来源是手册页,可以使用 man
命令访问手册页。图 1 展示了 man
命令本身的手册页。使用命令 man man
来显示这一信息。
图 1 显示了手册页中的一些典型项目:
可能会找到使用方法、如何报告 bug 、作者信息以及相关命令列表等其他信息。例如,man
的手册页告诉我们相关命令(及其手册页小节)是:
apropos(1)、whatis(1)、less(1)、groff(1) 和 man.conf(5)。
有 8 个常用的手册页小节。在安装包时常常会安装手册页,所以如果还没有安装某个包,就可能还没有它的手册页。同样,某些手‹‹册页小节可能是空的,或者几乎是空的。常用手册页小节以及一些内容示例如下:
可能有的其他小节包括 9(Linux 内核文档)、n(新文档)、o(旧文档)和 l(本地文档)。
一些项目会在多个小节中出现。我们的示例说明 mkdir 在小节 1 和 2 中都出现了,tty 也同时出现在小节 1 和 4 中。可以指定某个小节,例如 man 4 tty
或 man 2 mkdir
,还可以指定 -a
选项来列出所有可应用的手册页小节。
在图中可能会注意到,man
有许多选项,可以自己研究。目前,让我们看看与 man
相关的一些“参见”命令。
参见
与 man
相关的两个重要命令是 whatis
和 apropos
。whatis
命令搜索您提供的名称的手册页并显示来自适当手册页的名称信息。apropos
命令对手册页进行关键词搜索并列出包含关键词的手册页。清单 19 演示这些命令。
|
顺便说一下,如果无法找到 man.conf 的手册页,可以试着运行 man man.config
。
man
命令使用分页程序让输出在显示器上分页显示。在大多数 Linux 系统上,分页程序可能是 less
程序。另一个选择是比较老的 more
程序。如果希望打印手册页,那么指定 -t
选项来对手册页进行格式化,以便使用 groff
或 troff
程序进行打印。
less 分页程序有几个命令,可以帮助在显示的输出中搜索字符串。请使用 man less
来进一步了解 /(向前搜索)、?(向后搜索)、n(重复前一次搜索)以及其他命令。
其他文档来源
除了可以从命令行访问的手册页之外,Free Software Foundation 创建了许多 info 文件,这些文件用 info 程序处理。这些文件提供了丰富的导航功能,包括跳到其他小节。请用 man info
或 info info
了解更多信息。并不是所有命令都有 info 文档,所以如果您成为 info 用户,也常常需要使用手册页。
手册页还有一些图形化界面,比如 xman
(来自 XFree86 项目)和 yelp
(Gnome 2.0 帮助浏览器)。
如果无法找到某一命令的帮助,可以试着带 --help
选项运行这个命令。这可能会提供此命令的帮助,或者告诉您如何获得需要的帮助。
下一节讨论使用过滤器处理文本流。
|
文本流和过滤器
本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.2 的内容。这个主题的权值是 6。
在本节中,学习以下主题:
文本过滤
文本过滤 就是获取文本输入流,在文本上执行某些转换,然后将它发送到输出流的过程。尽管输入或输出可以是文件,但是在 Linux 和 UNIX 环境中,进行过滤的最常用方式是构造命令的管道,也就是对一个命令的输出进行管道连接(即 重定向),用作下一个命令的输入。管道和重定向将在 流、管道和重定向 一节中更完整地讨论,目前,我们来看看使用 | 和 > 操作符的管道和基本输出重定向。
用 | 构造管道
前面一节提到过,shell 使用三种标准 I/O 流:
在 本教程中到目前为止,输入都是来自我们提供给命令的参数,输出都显示在终端上。许多文本处理命令(过滤器)可以从标准输入流或文件获得输入。要想将一个命 令(command1)的输出用作一个过滤器(command2)的输入,应该使用管道操作符(|)连接这两个命令,如清单 20 所示。
|
正如在本节后面会看到的,这两个命令都可能有 选项或参数。还可以使用 | 将这个管道中 command2 的输出重定向到另一个命令,command3。将功能有限的多个命令连接成长的管道是在 Linux 和 UNIX 上完成任务的常用方式。有时还会看到连字符(-)用来替代文件名作为命令参数,这意味着输入应该来自 stdin 而不是文件。
用 > 进行输出重定向
能够用几个命令创建管道并在终端上看到输出固然很不错,但是有时候希望将输出保存到文件中。这要使用输出重定向操作符(>)来完成。
对于本节的其余部分,我们将使用一些小文件,所以先创建一个称为 lpi103 的目录,然后进入这个目录。然后使用 > 把 echo
命令的输出重定向到 text1 文件。这些操作见清单 21。注意,输出没有显示在终端上,因为它被重定向到文件了。
|
既然我们已经有了进行管道连接和重定向的基本工具,就来看一些常用的 UNIX 和 Linux 文本处理命令和过滤器。本节只展示一些基本功能;请通过适当的手册页进一步了解这些命令。
|
cat、tac、od 和 split
既然已经创建了 text1 文件,您可能想看看其中的内容。使用 cat
(catenate 的简写)命令将文件的内容显示在 stdout 上。清单 22 检查上面创建的文件的内容。
|
如果不指定文件名(或者指定 - 为文件名),那么 cat
命令从 stdin 接收输入。让我们使用这种方式和输出重定向来创建另一个文本文件,如清单 23 所示。
|
在清单 23 中,cat
从 stdin 读取输入,直到文件的末尾。使用 Ctrl-d(按住 Ctrl 并按下 d)组合键来表示文件的末尾。这个组合键也用来退出 bash shell。还要注意,制表符键帮助将这些水果名按列排列起来。
有时候,可能希望按相反的次序显示文件。很自然,也有一个用于此目的的文本过滤器,称为 tac
(cat
的反序)。清单 24 按相反的次序显示新的 text2 文件和原来的 text1 文件。注意显示中如何简单地连接这两个文件。
|
现在,假设使用 cat 或 tac 显示这两个文本文件并注意到对齐方式不一样。要了解造成这个问题的原因,需要看到文件中的控制字符。因为这些控制字符的作用是进行文本显示输出,而控制字符本身没有显示,所以需要将文件转储 为另一种格式,从而能够寻找和解释这些特殊字符。GNU 文本实用程序包含的 od
(即 Octal Dump)命令用于这个目的。
od
有几个选项,比如 -A
选项用来控制文件偏移量的基数,-t
选项用来控制显示的文件内容的形式。基数可以指定为 o(八进制 - 默认)、d(十进制)、x(十六进制)或 n(不显示偏移量)。可以将输出显示为八进制、十六进制、十进制、浮点、用反斜线转义的 ASCII 或命名的字符(nl 表示新行,ht 表示水平制表符,等等)。清单 25 显示一些可以用来对 text2 示例文件进行转储的格式。
|
注意:
cat
的 -A
选项也可以用来查看制表符和行末的位置。更多信息参见手册页。 我们的示例文件非常小,但是有时候有大型文件需要分割为比较小的块。例如,可能希望将大文件分割为 CD 容量的块,以便将它写到 CD 中。split
命令会完成这个任务,可以使用 cat
命令轻松地重新创建文件。在默认情况下,split
命令产生的文件的名称前面有前缀 'x',后面有后缀 'aa'、'ab'、'ac'、...'ba'、'bb' 等等。可以用选项控制这些默认前缀和后缀。还可以控制输出文件的大小,以及产生的文件是包含整行,还是只按字节计数。清单 26 演示将我们的两个文本文件进行分割,输出文件具有不同的前缀。我们将 text1 分割为最多包含两行的文件,将 text2 分割为最多包含 18 字节的文件。然后使用 cat
单独显示一些片段,并使用 globbing 显示完整的文件,这在本教程后面的 通配符和 globbing 一节中讨论。
|
注意,分割产生的文件 yaa 并不以新行字符结束,所以在用 cat
显示它之后提示符发生了偏移。
|
wc、head 和 tail
cat
和 tac
显示整个文件。这对于我们的示例这样的小文件是合适的,但是对于大文件就不合适了。那么,可能希望先用 wc
(Word Count)命令看看文件有多大。wc
命令显示文件中的行数、单词数和字节数。还可以使用 ls -l
了解字节数。清单 27 显示以长格式列出两个文本文件的目录清单,以及 wc
的输出。
|
可以使用选项控制 wc
的输出,或者显示其他信息,比如最大行长度。详情请参考手册页。
有两个命令可以显示第一部分(头)或最后一部分(尾)。这些命令是 head
和 tail
命令。它们可以用作过滤器,也可以以文件名作为参数。在默认情况下,它们显示文件或流的前 10 行(或最后 10 行)。清单 28 使用 dmesg
命令显示引导消息,并使用 wc
、tail
和 head
来发现共有 177 个消息,然后显示最后 10 个消息,最后显示最后 15 个消息中的前 6 个。在输出中,一些行被截断了(用 ... 表示)。
|
tail
的另一种常见用法是使用 -f
选项(常常带行计数 1)跟随 一个文件。如果有一个后台进程会在文件中产生输出,而您希望检查这个文件来了解进程正在做什么,那么就可以采用这种做法。在这种模式中,tail
将一直运行到取消它为止(使用 Ctrl-c),每当行被写到文件时就显示它们。
|
expand、unexpand 和 tr
在创建 text1 和 text2 文件时,我们使用制表符创建 text2。有时候希望将制表符转换成空格,或者相反。expand
和 unexpand
命令用于此目的。这两个命令的 -t
选项允许设置制表符对应的空格数。清单 29 显示如何将 text2 中的制表符展开为空格,以及用另一个奇特的 expand
和 unexpand
序列使 text2 中的文本不对齐。
|
不幸的是,不能使用 unexpand
将 text1 中的空格替换为制表符,因为 unexpand
需要至少两个空格才能转换为制表符。但是,可以使用 tr
命令来完成,这个命令将一个集合(set1)中的字符转换为另一个集合(set2)中对应的字符。清单 30 显示如何使用 tr
将空格转换为制表符。因为 tr
是一个纯粹的过滤器,所以要使用 cat
命令为它产生输出。这个示例还演示通过使用 - 让 cat
使用标准输入。
|
如果您不确定最后这两个示例中发生了什么,那么尝试使用 od
依次终止管道的每个阶段;例如
cat text1 |tr ' ' '/t' | od -tc
|
pr、nl 和 fmt
pr
命令用于对文件进行格式化以便打印。默认的页眉包括文件名、创建文件的日期和时间以及页号,页脚是两个空行。当从多个文件或标准输入流创建输出时,当前日期和时间替代文件名和创建日期。可以通过选项分栏并列打印文件并控制格式化的许多方面。详情请参考手册页。
nl
命令对行进行编号,这在打印文件时很方便。还可以用 cat
命令的 -n
选项对行进行编号。清单 31 显示如何打印 text1,然后给 text2 加行号并将它打印在 text1 旁边。
|
另一个对于文本格式化有帮助的命令是 fmt
命令,它使文本适合页面界限。可以将几个短行合并以及将长行分割。在清单 32 中,使用 !#:*
历史特性的变体创建具有一长行文本的 text3,它把我们输入的句子保存了四次。还创建 text4,其中每个单词占一行。然后使用 cat
显示没有经过格式化时的情况,包括显示 '$' 字符来表示行末。最后,使用 fmt
将它们格式化为最大宽度 60 个字符。同样,关于其他选项的详情请参考手册页。
|
|
sort 和 uniq
sort
命令使用系统地区的整理序列(LC_COLLATE)对输入进行排序。sort
命令还可以对已经排序的文件进行合并,以及检查文件是排序的还是未排序的。
清单 33 先将 text1 中的空格转换为制表符,然后使用 sort
命令对两个文本文件进行排序。因为排序次序是按照字符决定的,您可能会对结果感到吃惊。幸运的是,sort
命令既可以按照数字值,也可以按照字符值进行排序。可以针对整个记录,也可以针对每个字段 指定这一选择。除非指定了另一种字段分隔符,否则字段是由空格或制表符分隔的。清单 33 中的第二个示例对第一个字段进行数字式排序,对第二个字段按照整理序列(字母表次序)排序。它还演示了使用 -u
选项消除任何重复的行,只保留独特的行。
|
注意,仍然有两行都包含水果名“apple”。另一个命令 uniq
使我们能够更进一步控制对重复行的消除。uniq
命令一般在已排序的文件上进行操作,但是无论文件是否已经排序,它都会删除 连续的 相同行。uniq
命令还可以忽略一些字段。清单 34 按照第二个字段(水果名)对两个文本文件进行排序,并消除那些从第二个字段开始的内容都相同的行(也就是说,在测试 uniq
时我们跳过了第一个字段)。
|
排序是按照整理序列进行的,所以 uniq
显示 “10 apple” 这一行而不是 “1 apple”。请尝试添加对第一个字段的数字式排序,从而改变这种情况。
|
cut、paste 和 join
现在,我们来看另外三个对文本数据中的字段进行处理的命令。这些命令对于处理表格数据尤其有帮助。第一个命令是 cut
命令,它从文本文件中提取字段。默认的字段分隔符是制表符。清单 35 使用 cut
分隔 text2 的两列,然后使用空格作为输出分隔符,这是将每行中的制表符转换为空格的另一种方式。
|
paste
命令将来自两个或更多文件的行并列粘贴在一起,这与 pr
命令使用 -m
选项对文件进行合并的方式相似。清单 36 显示粘贴两个文本文件的结果。
|
这些示例只展示了简单的粘贴,但是 paste
可以以几种其他方式从两个或更多文件粘贴数据。详情请参考手册页。
最后一个字段操作命令是 join
, 它根据匹配的字段对文件进行联结。文件应该是按照联结字段排序的。因为 text2 没有按照数字次序排序,我们先对它进行排序,然后将具有匹配的联结字段(在这个示例中是 3)的行联结起来。我们还创建一个新文件 text5,创建的办法是按照第二个字段(水果名)对 text1 排序,然后用制表符替换空格。然后对 text2 排序并使用第二个字段与 text5 联结,应该有两个匹配(apple 和 banana)。清单 37 演示这两个联结。
|
用来进行联结的字段是分别针对每个文件指定的。例如,可以根据一个文件中的字段 3 和另一个文件中的字段 10 进行联结。
|
sed
sed 是 stream editor(流编辑器)。有关于 sed 的几篇 developerWorks 文章和许多书籍(参见 参考资料)。sed 非常强大,它能够完成的任务超出了您的想像。这里的简短介绍应该激发您对 sed 的兴趣,但是并不全面。
与到目前为止看到的许多文本命令一样,sed 可以作为过滤器,也可以从文件接收输入。输出是标准输出流。sed 将来自输入的行装载到模式空间 中,将 sed 编辑命令应用于模式空间的内容,然后将模式空间写到标准输出。sed 可以将模式空间中的几行组合起来,可以写文件、只写选择的输出或者根本不写输出。
sed 使用正则表达式语法(参见本教程后面的 用正则表达式进行搜索)来搜索和选择性地替换模式空间中的文本,以及通过编辑命令集来控制应该操作哪些文本行。保留缓冲区(hold buffer) 为文本提供临时存储。保留缓冲区可以替换模式空间、添加到模式空间中或与模式空间交换。sed 的命令比较有限,但是这些命令与正则表达式语法和保留缓冲区结合起来就可以实现某些令人吃惊的功能。sed 命令集常常称为 sed 脚本。
清单 38 显示了三个简单的 sed 脚本。在第一个脚本中,使用 s
(替换)命令用大写替换每行上的小写 'a'。这个示例只替换第一个 'a',所以在第二个示例中,添加一个 'g'(全局)标志使 sed 修改文本中出现的所有 'a'。在第三个脚本中,使用 d
(删除)命令删除一行。在示例中,使用地址 2 表示只应该删除第二行。用分号(;)分隔命令并使用第二个脚本中使用过的全局替换。
|
除了操作单独的行之外,sed 还可以操作一个范围内的行。范围的开始和结束由一个逗号(,)分隔,可以用行号指定,脱字符(^)表示文件的开头,美圆符号($)表示文件的末尾。给出一 个地址或地址范围,就可以在花括号({ 和 })之间组合几个命令,让这些命令只应用于范围内的行。清单 39 演示了将全局替换只应用于文件的最后两行的两种方式。它还演示了使用 -e
选项将命令添加到模式空间中。在使用花括号时,必须以这种方式分隔命令。
|
sed 脚本还可以存储在文件中。实际上,对于频繁使用的脚本,很可能希望这么做。在前面,我们使用 tr
命令将 text1 中的空格改为制表符。现在用一个存储在文件中的 sed 脚本来完成这个任务。将使用 echo
命令创建文件。结果见清单 40。
|
有许多像清单 40 这样的方便的 sed one-liner。参见 参考资料 中的链接。
最后一个 sed 示例使用 =
命令打印行号,然后通过 sed 对产生的输出进行过滤,从而模仿 nl
命令对行进行编号的效果。清单 41 使用 = 来打印行号,然后使用 N
命令将第二个输入行读入模式空间,最后删除模式空间中两行之间的新行字符(/n)。
|
结果不太理想!我们其实希望将行号在一栏中对齐,与文件中的行分开。在清单 42 中,我们输入几行命令(注意 > 辅助提示符)。请研究这个示例并参考下面的解释。
|
我们采取的步骤是这样的:
cat
创建一个有 12 行的文件,其内容来自 text1 和 text2 文件的两个副本。产生 12 行是为了显示两位数的行号,从而表现栏中数字格式化的效果。 echo
命令实现这个字符表示并将它存储在 shell 变量 'ht' 中。 sed 最近的版本(version 4)包含 info
格式的文档,还有许多出色的示例。在 version 3.02 中没有这些。GNU sed 在接到 sed --version
命令时会显示版本。
|
基本文件管理
本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.3 的内容。这个主题的权值是 3。
在本节中,学习以下主题:
find
命令根据类型、大小或时间来定位和操作文件列出目录
在前面关于 使用命令行 的一节中,在讨论路径时说过,Linux 和 UNIX® 系统上的所有文件都是一个大型树结构的文件系统的一部分,这个文件系统的根是 /。
列出目录项
如果您在前面的一节按照我们的说明进行操作,就已经在主目录中创建了一个目录,lpi103。文件和目录名可以是绝对的(意味着它们以 / 开头),也可以是相对 于当前工作目录 的(意味着它们不以 / 开头)。文件或目录的绝对路径包括一个 /,后面跟着零个或几个目录名,每个目录名后面跟着一个 /,然后是最后一个名称。如果给出了相对于当前工作目录的文件或目录名,只需将工作目录的绝对路径、/ 和这个目录的相对路径组合起来。例如,在前一节中在我的主目录 /home/ian 中创建了 lpi103 目录,所以它的完整(即绝对)路径是 /home/ian/lpi103。清单 43 演示了三种使用 ls
命令列出这个目录中文件的方式。
|
可以看到,可以将目录名作为参数提供给 ls
命令,它会列出这个目录的内容。
列出细节
在存储设备上,文件或目录包含在块 的集合中。关于文件的信息包含在 inode 中,记录的信息包括拥有者、最近一次访问文件的时间、文件的大小、它是否是目录以及谁可以读或写它。inode 号也称为文件序列号(file serial number) ,这个编号在特定的文件系统中是惟一的。可以使用 -l
(或 --format=long
)选项来显示 inode 中存储的一些信息。
在默认情况下,ls
命令不列出特殊文件,即名称以点(.)开头的文件。除根目录之外的每个目录都至少有两个特殊项,目录本身(.)和父目录(..)。根目录没有父目录。
清单 44 使用 -l
和 -a
选项以长格式列出所有文件,包括 . 和 .. 目录项。
|
在清单 44 中,第一行显示列出的文件所使用的磁盘块的总数(56)。余下的字段说明文件的情况。
ls
命令的 -i
选项显示 inode 号。在本教程后面以及在针对主题 104 的教程中讨论硬链接和符号链接时,我们将使用这个命令。
多个文件
还可以为 ls
命令指定多个参数,其中每个参数是文件或目录的名称。如果参数是目录名称,那么 ls
命令列出这个目录的内容,而不是列出关于目录本身的信息。在我们的示例中,假设希望获得关于 lpi103 目录项本身的信息,就像父目录中列出的一样。命令 ls -l ../lpi103
会给出像前一个示例一样的清单。清单 45 显示如何使用 ls -ld
以及如何列出多个文件或目录。
|
注意,lpi103 的修改时间与前一个清单中不一样。另外,与前一个清单一样,目录中文件的时间戳不一样。您预料到这种情况了吗?正常情况下不会这样的。但是,在开发本教程时,我创建了一些额外的示例,然后删除了,所以目录时间戳反映了这一事实。在本节后面讨论 搜索文件 时会进一步讨论文件时间。
对输出进行排序
在默认情况下,ls
按照字母表次序列出文件。有许多用于对输出进行排序的选项。例如, ls -t
按照修改时间排序(从最新的到最旧的),而 ls -lS
将按大小次序产生一个长清单(从最大的到最小的)。添加 -r
选项将反转排序次序。例如,使用 ls -lrt
产生从最旧到最新的长清单。列出文件和目录的其他方式请参考手册页。
|
复制、移动和删除
我们已经学习了一些创建文件的方式,但是可能希望复制文件、对文件进行重命名、在文件系统层次结构中移动文件,甚至删除文件。使用下面这三个命令来完成这些任务。
cp
命令相同的规则;可以对单个文件进行重命名,或者将一组文件移动到新目录中。因为名称只是一个链接到 inode 的目录项,所以 inode 号应该不变,
除非 文件移动到了另一个文件系统中,在这种情况下,移动文件实际上是复制然后删除原来的文件。
清单 46 演示如何使用 cp
和 mv
创建文本文件的一些备份副本。还使用 ls -i
显示一些文件的 inode。
mkdir
命令创建一个备份目录。
|
在正常情况下,cp
命令可以覆盖现有的副本,如果现有的文件是可写的话。另一方面,如果目标文件存在,那么 mv
将不会移动或重命名这个文件。对于 cp
和 mv
的这种行为,有几个有用的相关选项可以进行控制。
在清单 47 中,演示带备份操作的复制以及文件删除。
|
注意,rm
命令还接受 -i
(交互式)和 -f
(强迫)选项。使用 rm
删除文件之后,文件系统就不能再访问它。一些系统默认设置一个别名 alias rm='rm -i'
,以便帮助根用户避免意外的文件删除。如果您很担心意外地删除文件,那么也可以这么做。
在结束对这三个命令的讨论之前,还应该注意,cp
命令在默认情况下为新文件创建新的时间戳。拥有者和组也设置为进行复制的用户及其组。可以使用 -p
选项保留选择的属性。注意,根用户是可以保留拥有关系的惟一用户。细节请参考手册页。
|
mkdir 和 rmdir
我们已经看到了如何用 mkdir
创建目录。现在进一步讨论 mkdir 以及用于删除目录的命令 rmdir
。
mkdir
假设我们在 lpi103 目录中,希望创建子目录 dir1 和 dir2。与我们已经讨论过的那些命令一样,mkdir
可以在一个命令中处理多个目录创建请求,如清单 48 所示。
|
注意,成功完成时并没有输出,但是可以使用 echo $?
检查退出码是否确实是 0。
如果希望创建嵌套的子目录,比如 d1/d2/d3,那么这个操作会失败,因为 d1 和 d2 目录还不存在。幸运的是,mkdir
有一个 -p
选项,它允许创建所需的任何父目录。清单 49 演示了这种做法。
|
rmdir
使用 rmdir
命令删除目录与创建目录的过程相反。同样,也有 -p
选项用来删除父目录。只有在目录是空的时候,才能使用 rmdir
删除目录,因为没有强迫删除选项。在讨论 递归操作 时,会看到删除目录的另一种方式。学会这种方法之后,就可能不常在命令行上使用 rmdir
了,但是了解这个命令是有好处的。
为了演示目录删除,我们将 text1 文件复制到目录 d1/d2 中,这样这个目录就不空了。然后使用 rmdir
删除刚才用 mkdir
创建的所有目录。可以看到,d1 和 d2 没有被删除,因为 d2 不是空的。另一个目录被删除了。从 d2 中删除了 text1 的副本之后,就可以通过调用一次 rmdir -p
删除 d1 和 d2。
|
|
递归操作
在本节余下的部分中,讨论处理多个文件以及递归地操作目录树的一部分的各种操作。
递归地列出信息
ls
命令有一个 -R
(注意,是大写的 'R')选项,用于列出一个目录及其所有子目录的内容。递归选项只应用于目录名;不能用来在目录树中搜索具有某一名称(比如 'text1')的所有文件。-R
可以与前面介绍的其他选项一起使用。清单 51 递归地列出 lpi103 目录的信息,包括 inode 号。
|
递归地复制
可以使用 -r
(或 -R
或 --recursive
)选项让 cp
命令探索源目录并递归地复制内容。为了避免无限地递归,源目录本身可能不被复制。清单 52 显示如何将 lpi103 目录中的所有内容复制到 copy1 子目录中。然后使用 ls -R
显示了产生的目录树。
|
递归地删除
前面提到过,rmdir
只删除空目录。可以使用 -r
(或 -R
或 --recursive
)选项让 rm
命令同时删除文件 和 目录。在清单 53 中,删除刚创建的 copy1 目录及其内容,包括 backup 子目录及其内容。
|
如果有不可写的文件,那么可能需要添加 -f
选项来进行强迫删除。根用户在清理文件系统时常常这么做,但是如果不小心,就可能丢失有价值的数据。
|
通配符和 globbing
常常需要在许多文件系统对象上执行操作,但不是操作整个目录树(就像刚才用递归操作所做的)。例如,可能希望寻找在 lpi103 中创建的所有文本文件的修改时间,但是不列出分割的文件。对于小目录这很容易,但是对于大型文件系统就很困难了。
要解决这个问题,可以使用 bash shell 内置的通配符支持。这种支持也称为 “globbing”(因为它原来是作为 /etc/glob 程序实现的),允许使用通配符模式指定多个文件。
包含字符 '?'、'*' 或 '[' 的字符串是通配符模式。shell(也可能是另一个程序)利用 globbing 过程将这些模式扩展成与模式匹配的路径名列表。匹配规则如下:
globbing 单独应用于路径名的每个成分。不能匹配 '/',也不能在范围中包含这个字符。可以在能够指定多个文件或目录名的任何地方使用通配符,例如,在 ls
、cp
、mv
或 rm
命令中。在清单 54 中,先创建两个名字很怪的文件,然后使用带通配符的 ls
和 rm
命令。
|
注意:
ls
示例一样,如果模式扩展产生目录名,而且没有指定 -d
选项,那么会列出这个目录的内容(上面的模式 '*b*' 示例就是这种情况)。 ls
命令列出了两个特殊目录项目(. 和 ..)。 记 住,命令中的任何通配符都由 shell 负责进行扩展,这可能会产生出乎意料的结果。因此,如果指定的模式不匹配任何文件系统对象,那么 POSIX 要求将原来的模式字符串传递给命令。清单 55 演示了这种情况。一些比较早的实现把一个空列表传递给命令,所以您可能会遇到一些行为异常的旧脚本。清单 55 中也演示了这些问题。
|
man 7 glob
。需要加小节号是因为在第 3 小节中也有 glob 信息。理解所有 shell 交互的最佳方式是实践,所以只要有机会就尝试使用通配符。记住先用
ls
检查通配符模式的结果,然后再在
cp
、
mv
或
rm
命令中使用,以免产生意外的效果。
|
接触文件
现在来看看 touch
命令,它更新文件访问和修改时间或创建空文件。在下一部分中,将看到如何使用这一信息搜索文件和目录。我们将使用本教程前面创建的 lpi103 目录。
touch
不带选项的 touch
以一个或多个文件名作为参数,并更新文件的 修改 时间。这就是一般情况下长目录清单显示的时间戳。在清单 56 中,使用 echo
创建一个小文件 f1,然后使用长目录清单显示修改时间(即 mtime)。在这种情况下,这个时间正好就是创建文件的时间。然后使用 sleep
命令等待 60 秒并再次运行 ls
。注意,文件时间戳增加了一分钟。
|
如果指定一个不存在的文件的文件名,那么 touch
在一般情况下会创建一个空文件,除非指定了 -c
或 --no-create
选项。清单 57 演示了这些命令。注意只创建了 f2。
|
touch
命令还可以使用 -d
或 -t
选项将文件的 mtime 设置为特定的日期和时间。-d
在可接受的日期和时间方面非常灵活,而 -t
选项至少需要 MMDDhhmm 时间,年和秒值是可选的。清单 58 显示一些示例。
|
如果不确定日期表达式会解析成什么日期,可以使用 date
命令来检查。它还接受 -d
选项并可以解析与 touch
相同的日期格式。
可以使用 -r
(或 --reference
)选项加上一个参考文件名 来表示 touch
(或 date
)应该使用一个现有文件的时间戳。清单 59 显示一些示例。
|
Linux 系统会记录文件修改 时间和文件访问 时间。在创建文件时,这两个时间戳设置为相同的值,在修改文件时同时重新设置这两者。只要访问文件,即使没有修改文件,访问时间也会更新。对于最后一个 touch
示例,我们将看看文件访问 时间。-a
(或 --time=atime
、--time=access
或 --time=use
)选项指定访问时间应该更新。清单 60 使用 cat
命令访问 f1 文件并显示它的内容。然后使用 ls -l
和 ls -lu
分别显示 f1 和 f1a 的修改时间和访问时间,f1a 是使用 f1 作为参考文件创建的。然后使用 touch -a
将 f1 的访问时间重新设置为 f1a 的访问时间。
|
关于允许使用的许多日期和时间格式的完整信息,请参考 touch 和 date 命令的手册页或 info 页。
|
搜索文件
在本节的最后一个主题中,将讨论 find
命令,它用于在一个或多个目录树中搜索文件,搜索条件包括名称、时间或大小等等。同样,还是使用前面创建的 lpi103 目录。
find
find
命令使用完整名称或部分名称,或者根据其他条件(比如大小、类型、文件拥有者、创建日期或最后访问日期)搜索文件或目录。最基本的搜索是根据名称或名称的 一部分进行搜索。在清单 61 给出的示例中,先在 lpi103 目录中搜索名称里有 ‘1’ 或 ‘k’ 的所有文件,然后执行一些路径搜索,这在稍后解释。
|
注意:
-path
而不是 -name
以匹配完整的路径而不只是基本文件名。在这种情况下,模式 可能 跨越路径成分。 ipath
示例所示),那么在搜索字符串或模式的 find
选项前面加一个 ‘i’。 在上面的第一个示例中,找到了两个文件和一个目录(./backup)。使用 -type
参数加上一个字母的类型对搜索进行限制。‘f’ 表示常规文件,‘d’表示目录,‘l’ 表示符号链接。其他类型请参考 find
的手册页。清单 62 显示搜索目录(-type d
)的结果。
|
注意,如果在 -type d
搜索中没有对名称进行限制,那么就会显示名称以点开头的目录(在这个示例中只有当前目录)。
还可以按文件大小进行搜索,可以针对特定大小(n),也可以针对大于(+n)或小于(-n)给定值的文件。通过给出文件大小的上界和下界,可以搜索大小在给定范围内的文件。在默认情况下,find
的 -size
选项假设单位是 ‘b’,即 512 字节的块。指定 ‘c’ 表示字节,‘k’ 表示 kb。在清单 63 中,先搜索大小为 0 的所有文件,然后搜索大小为 24 或 25 字节的所有文件。注意,指定 -empty
而不是 -size 0
也可以搜索空文件。
|
清单 63 引入了 -print
选项,这是在搜索返回的结果上执行动作 的例子。在 bash shell 中,如果没有指定动作,这就是默认动作。在某些系统上和某些 shell 上,要求指定动作,否则就没有输出。
其他动作包括 -ls
(打印文件信息,相当于 ls -lids
命令)或 -exec
(对每个文件执行一个命令)。-exec
必须用一个分号终止,这个分号必须进行转义,以避免 shell 解释它。如果在命令中要使用返回的文件,就指定 {}。正如前面所说的,花括号对于 shell 也有特殊意义,所以需要转义或加上引号。清单 64 显示如何使用 -ls
和 -exec
选项列出文件信息。
|
-exec
选项可以用于您能够想像得到的任何用途。例如:
find . -empty -exec rm '{}' /;
删除目录树中的所有空文件,
find . -name "*.htm" -exec mv '{}' '{}l' /;
将所有 .htm 文件重命名为 .html 文件。
最后,使用 touch
命令描述的时间戳来搜索具有特定时间戳的文件。清单 65 给出了三个示例:
-mtime -2
时,find
命令搜索最近两天中修改过的所有文件。在这种情况下,一天是相对于当前日期和时间的 24 小时时间段。注意,如果想要根据访问时间而不是修改时间搜索文件,那么要使用 -atime
。 -daystart
选项意味着天是日历天,从午夜开始。现在 f3 文件从列表中排除了。
|
find
命令的手册页可以帮助您学习这里没有提到的众多选项。
|
流、管道和重定向
本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.4 的内容。这个主题的权值是 5。
在本节中,学习以下主题:
对标准 IO 进行重定向
shell 使用三种标准 I/O 流:
正如在 文本流和过滤器 一节中看到的,可以将标准输出重定向到文件或者另一个命令的标准输入,还可以将标准输入重定向为来自文件或者来自另一个命令的输出。
对输出进行重定向
对输出进行重定向有两种方法:
ls
的标准输出和标准错误分别放进两个文件,这些文件是在 lpi103 目录中创建的。还演示了如何将输出附加到现有的文件中。
|
使用 n> 进行输出重定向常常会覆盖现有文件。可以使用内置命令 set
的 noclobber
选项控制这种行为。如果设置了这个选项,就会在覆盖现有文件时发出警告;如果确实要覆盖,可以使用 n>|,如清单 67 所示。
|
有时候希望将标准输出和标准错误重定向到同一个文件中。自动化进程或后台作业常常采用这种做法,这样就能够在以后查看输出。使用 &> 或 &>> 将标准输出和标准错误重定向到同一个位置。另一种方法是对文件描述符 n 进行重定向,然后使用 m>&n 或 m>>&n 将文件描述符 m 重定向到同一个位置。对输出进行重定向的次序很重要。例如,
command 2>&1 >output.txt
与
command >output.txt 2>&1
不一样。清单 68 中演示了这些重定向。注意在最后一个命令中,标准输出在标准错误之后进行重定向,所以标准错误输出仍然发送到终端窗口。
|
有时候希望完全忽略标准输出或标准错误。为此,可以把适当的流重定向到 /dev/null 中。在清单 69 中,演示如何忽略来自 ls
命令的错误输出。
|
对输入进行重定向
正如可以对 stdout 和 stderr 流进行重定向,也可以使用 < 操作符将 stdin 重定向为来自文件。在前面讨论 sort 和 uniq 时,曾经使用 tr
命令将 text1 文件中的空格替换为制表符。在这个示例中,我们使用来自 cat
命令的输出为 tr
创建标准输入。无需不断地调用 cat
,现在可以使用输入重定向将空格转换为制表符,如清单 70 所示。
|
shell(包括 bash)还有 here-document 的概念,这是输入重定向的另一种形式。这使用 << 以及一个单词(比如 END),这个单词作为输入结束的标志。清单 71 演示了这种做法。
|
回忆一下在 清单 23 中是如何创建 text2 文件的。您可能会奇怪为什么不能只是输入 sort -k2
,输入数据,然后按下 Ctrl-d 来结束输入。简短的答案是可以这么做,但是当时还没有学习 here-document。在 shell 脚本中常常使用 here-document(这在关于 shell、脚本、编程以及编译的主题 109 教程中讨论)。脚本没有其他方法能够判断脚本的哪些行应该作为输入对待。因为 shell 脚本大量使用制表符来进行缩进,从而提高可读性,所以 here-document 还会造成另一种麻烦。如果使用 <<- 而不是 <<,那么前面的制表符会被删除。在清单 72 中,使用与 清单 42 中一样的技术创建制表符的替代字符。然后创建一个非常小的 shell 脚本,它包含两个读取 here-document 的 cat
命令。最后,使用 .
(点)命令引用(source) 脚本,这意味着在当前 shell 上下文中运行它。
|
|
管道
在 文本流和过滤器 一节中指出,文本过滤 过程就是取得文本输入流,在文本上执行某些转换,然后将它发送到输出流中。还指出,过滤常常是通过构造命令的管道 来完成的,也就是对一个命令的输出进行管道连接 或重定向,作为下一个命令的输入使用。以这种方式使用管道并不只限于文本流,尽管文本流是最常使用这些方式的地方。
将 stdout 管道连接到 stdin
我们已经看到,在两个命令之间使用 |(管道)操作符可以将第一个命令的 stdout 定向到第二个命令的 stdin。可以通过添加更多的命令和更多的管道操作符来构造长管道,如清单 73 所示。
|
要注意的是,管道 只能 将 stdout 管道连接到 stdin。不能使用 2| 对 stderr 进行管道连接,至少用到目前为止学习的工具无法完成这种任务。如果 stderr 已经被重定向到 stdout,那么这两个流都被管道连接。在清单 74 中,使用管道对错误消息和正常输出消息进行排序,这些消息来自一个奇怪的 ls
命令,其中的通配符参数并没有按照字母表的次序排列。
|
管道中的任何命令都可以有选项或参数。许多命令使用连字符(-)替代文件名参数,这表示输入应该来自 stdin 而不是文件。请查看命令的手册页。将功能有限的多个命令连接成长的管道是在 Linux 和 UNIX 上完成任务的常用方式。
在 Linux 和 UNIX 系统上管道的一个优点是,与某些其他流行的操作系统不同,管道不涉及中间文件。第一个命令的 stdout 并不是 写到文件中,然后再由第二个命令读取。如果您的 tar
版本碰巧不支持使用 bzip2
对压缩的文件进行解压,那么没关系。在主题 102 的教程中可以看到,可以使用管道
bunzip2 -c drgeo-1.1.0.tar.bz2 | tar -xvf -
来完成这个任务。
|
输出作为参数
在 使用命令行 一节中,我们学习了命令替换以及如何将命令的输出作为另一个命令的一部分使用。在前面的 基本文件管理 一节中,学习了如何使用 find
命令的 -exec
选项,将 find
命令的输出作为另一个命令的输入使用。清单 75 使用这些技术以三种方式显示 text1 和 text2 文件的内容。
|
上面这些方法是有效的,但是有一些限制。我们来考虑文件名包含空白(在这个示例中是空格)的情况。看看清单 76 ,试着自己理解每个命令发生的情况,然后再看后面的解释。
|
这里执行的任务如下:
cat
命令提供 一个 参数,这个参数等于 需要有办法清楚地识别各个文件名,无论它们是由单一单词还是由多个单词组成的。我们在前面没有提到过,但是在管道或命令替换中使用命令(比如 ls
)的输出时,常常是每行一个项目地提交输出。处理这种情况的一种方法是,在内置命令 while
建立的循环中使用内置命令 read
。尽管这超出了本教程的目标,但是我们仍然给出一个示例,以此引发您的兴趣。
|
xargs
在很多时候,我们希望处理文件列表,所以确实需要用某种方式搜索和处理文件。幸运的是,find
命令有一个选项 -print0
,它以 null 字符分隔输出的文件名而不是用新行字符。tar
和 xargs
等命令有 -0
(或 --null
)选项,这使它们能够理解这种形式的参数。我们已经看到了 tar
命令。xargs
命令的工作方式有点儿像 find
的 -exec
选项,但是有一些重要的差异。我们先来看一个示例。
|
注意,现在将 find
的输出管道连接到 xargs
。不需要在命令的末尾加分号进行分隔,而且在默认情况下 xargs 将参数附加到命令字符串后面。但是,我们看到了 7 行输出,而不是期望的 4 行。什么地方错了?
再次搜索
可以使用 wc
命令检查我们认为会打印的那两个文件,确实只有 4 行。问题的原因是 find
会搜索备份目录,在那里还会找到 backup/text1.bkp.2,这个文件也与通配符模式匹配。为了解决这个问题,使用 find
的 -maxdepth
选项将搜索的深度限制为一个目录,即当前目录。还有一个对应的 -mindepth
选项,允许更具体地指定在哪里进行搜索。清单 79 演示了最后这个解决方案。
|
关于 xargs 的其他问题
在 xargs
和 find -exec
之间还有其他一些差异。
xargs
命令在默认情况下向命令传递尽可能多的参数。可以使用 -l
或 --max-lines
和一个数字限制输入行的数量。也可以使用 -n
或 --max-args
限制传递的参数数量,或者使用 -s
或 --max-chars
限制参数字符串中使用的最大字符数量。如果命令可以处理多个参数,那么同时处理尽可能多的参数一般来说效率更高。 find -exec
一样,如果指定 -i
或 --replace
选项,那么可以使用 '{}'。可以通过为 -i
指定值,改变表示在哪里替换输入参数的默认字符串 '{}'。这意味着 -l 1
。 xargs
的最后一个示例见清单 80。
|
注意,这里没有使用 -print0
。您能解释清单 80 中的示例吗?
|
对输出进行转储
本节的最后简短地讨论另一个命令。有时候希望在屏幕上看到输出,同时保存一个副本。可以 在一个窗口中将命令输出重定向到文件,然后在另一个窗口中使用 tail -fn1
跟踪输出,但是使用 tee
命令更容易。
与管道一起使用 tee
。参数是作为标准输出的一个文件(或多个文件)。-a
选项要求将输出附加到文件,而不是覆盖文件。与前面讨论管道时看到的一样,如果希望同时保存 stderr 和 stdout,需要将 stderr 重定向到 stdout,然后再管道连接到 tee
。清单 81 使用 tee
将输出保存到两个文件 f1 和 f2。
|
|
创建、监视和杀死进程
本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.5 的内容。这个主题的权值是 5。
在本节中,学习以下主题:
显 然,除了正在运行的终端程序之外,计算机上还运行着许多程序。如果正在使用图形桌面,可能会同时打开多个终端窗口,可能打开文件浏览器、互联网浏览器、游 戏、电子表格或其他应用程序。到目前为止,我们的示例都是在终端窗口中输入命令。命令运行,我们等待它完成,在此之前我们不能做其他任何事情。在 使用命令行 一节中,我们遇到了显示进程状态的 ps
命令,并看到进程有进程 ID(PID)和父进程 ID(PPID)。在本节中,学习如何使用终端窗口同时执行多个任务。
前台和后台作业
在终端窗口中运行命令时,比如在此之前我们给出的那些命令示例,是在前台 运行它。我们的命令都运行得非常快,所以进程不会长期存在。为了研究长期运行的进程,假设我们正在运行一个图形桌面,希望在桌面上显示一个数字化时钟。大多数桌面已经有了这种时钟,这没关系,我们只是以此作为示例。
如果安装了 X Window 系统,就可能有 xclock
或 xeyes
这样的实用程序。这两种程序对于这个练习都适用,但是我们使用 xclock。手册页指出,可以使用以下命令在图形桌面上启动一个数字化时钟
xclock -d -update 1
-update 1
部分要求每秒进行一次更新,如果没有这个选项,时钟每分钟只更新一次。现在在一个终端窗口中运行这个命令。应该会看到图 2 所示的时钟,终端窗口的内容看起来应该像清单 82 这样。如果您没有 xclock 或 X Window 系统,我们稍后会解释如何用终端创建一个简单的数字化时钟,所以您可以先学下去,在创建了那个时钟之后,用它做这些练习。
|
不幸的是,终端窗口不再有提示符,所以确实需要重新获得控制。幸运的是,bash shell 有一个暂停 键,Ctrl-z。按下这个组合键会重新出现终端提示符,如清单 83 所示。
|
时钟仍然在桌面上,但是已经停止运行了。它确实已经暂停了。实际上,如果将另一个窗口拖过时钟上面,它甚至不会重新绘制。还会看到一个终端输出消息,表示 “[1]+ Stopped”。这个消息中的 1 是作业号。可以通过输入 fg %1
重新启动时钟。还可以使用命令名或命令名的一部分,即 fg %xclock
或 fg %?clo
。最后,如果使用没有参数的 fg
,那么就会重新启动最近停止的作业,在这个示例中就是作业 1。用 fg
进行重新启动也将作业带回前台,并不再有 shell 提示符。需要做的事是将这个作业放到后台;bg
命令可以完成这个任务,它采用与 fg
命令相同的作业指定方式。清单 84 显示如何使用两种形式的 fg
命令将 xclock 作业带回前台。可以再次暂停它并将它放到后台;时钟继续运行,同时您可以在终端中做其他工作。
|
使用 “&”
您 可能会注意到,在将 xclock 作业放到后台时,消息不再指出 “Stopped”,而是以一个 & 结束。实际上,根本不需要为了将进程放到后台而暂停它。只需要将一个 & 放在命令后面,shell 就会在后台启动命令。我们使用这种方法启动一个模拟时钟,它具有小麦背景和红色的指针。应该会看到图 3 这样的时钟和清单 85 这样的终端输出。
|
注意,这一次消息有点儿不一样。它代表一个作业号和一个进程 id(PID)。稍后会讨论 PID 和状态。现在,使用 jobs
命令了解正在运行什么作业。添加 -l
选项来列出 PID,会看到作业 2 的 PID 确实是 5659,见清单 86。还要注意,作业 2 在作业号旁边有一个加号(+),这表示它是当前作业。如果使用 fg
命令时没有指定作业,这个作业就会转到前台。
|
在解决与后台作业相关的其他问题之前,我们先创建一个简单的数字化时钟。使用 sleep
命令产生两秒的延迟,并使用 date
命令打印当前日期和时间。将这些命令放在一个 while
循环中,并使用 do/done
块创建一个无限循环。最后,将所有这些放在圆括号中构成命令列表,并使用 & 让整个命令列表在后台运行。
|
这个命令列表作为作业 1 运行,PID 是 16291。每隔两秒,date 命令运行并在终端上打印一个日期和时间。用户的输入以粗体显示。对于输入很慢的人,在输入一条完整的命令之前,这个命令列表可能已经产生了几行输出,所以 输入的字符可能与输出混在一起。实际上,我们输入的 ‘f’‘g’ 是 fg 命令,用于将这个命令列表带到前台,但是这个命令被两行输出分开了。当最终输入完 fg
命令时,bash 显示正在 shell 中运行的命令,也就是这个命令列表,之后仍然每两秒打印一次时间。
成功地将作业带到后台之后,可以终止(即杀死)它,或者采取某些其他操作。在这个示例中,使用 Ctrl-c 终止时钟。
标准 IO 和后台进程
在前面的示例中,date
命令的输出与 fg
命令的回显字符混在一起。这就产生了一个有意思的问题。如果进程需要来自 stdin 的输入,那么会怎么样?
用来启动后台应用程序的终端进程称为控制终端。 除非被重定向到其他地方,否则来自后台进程的 stdout 和 stderr 流会输出到控制终端。同样,后台任务期望从控制终端获得输入,但是控制终端无法将您输入的字符转发给后台进程的 stdin。在这种情况下,bash shell 暂停进程,这样它就不再执行了。可以将它带到前台并提供必要的输入。清单 88 演示了一种简单的情况。可以将一个命令列表放到后台执行。过了一会儿,按 Enter,进程停止。将它带到前台,提供一行输入,最后按 Ctrl-d 表示输入文件结束。命令列表完成,我们显示创建的文件。
|
|
没有终端的作业
在 实践中,可能希望让后台进程的标准 IO 流重定向到文件或者来自文件。有另一个相关问题;如果控制终端关闭了或用户注销了,那么进程会怎么样?答案取决于使用的 shell。如果 shell 发送 SIGHUP(或 hangup)信号,那么应用程序可能关闭。我们稍后讨论信号,但是目前考虑解决这个问题的另一种方式。
nohup
nohup
命令用于启动一个命令,它将忽略 hangup 信号并将 stdout 和 stderr 附加到文件。默认文件是 nohup.out 或 $HOME/nohup.out。如果文件不可写,那么命令将不运行。如果希望将输出发送到别处,那么对 stdout 或 stderr 进行重定向。
nohup
的另一个特点是,它不执行管道或命令列表。在 对标准 IO 进行重定向 一节中,我们看到了如何将一组命令保存在 shell 脚本中并引用(source)它。可以将管道或列表保存在文件中,然后使用 sh
(默认 shell)或 bash
命令运行它,但是不能使用前面示例中使用的 . 或 source
命令。本系列中的下一个教程(针对主题 104,讨论设备、Linux 文件系统和 Filesystem Hierarchy Standard)将解释如何使脚本文件可执行,但是目前只通过引用(source)或者通过使用 sh
或 bash
命令来运行脚本。清单 89 显示如何用脚本运行刚才创建的数字化时钟。但是,将时间写到文件中其实没什么用,而且文件会随着脚本运行而增大,所以将时钟设置为每 30 秒更新一次而不是每秒一次。
|
如果显示 nohup.out 的内容,会看到第一行说明为什么在第一次尝试时会得到退出码 126。后续的行是来自在后台运行的两个 pmc.sh 版本的输出。清单 90 显示了 nohup 的输出。
|
现在,我们来讨论进程的状态。停下来思考一下,现在有了两个作业,它们在文件系统中创建不断增大的文件。可以使用 fg
命令将它们带到前台,然后使用 Ctrl-c 终止它,但是如果让它们运行时间长一些,我们就可以看看监视它们和与它们进行交互的其他方式。
|
进程状态
在本节前面的部分中,我们简单地介绍了 jobs
命令以及如何使用它列出作业的进程 id(PID)。
ps
还有另一个命令,ps
命令,可以使用它显示各种进程状态信息。“ps” 是 “process status” 的首字母缩写。ps
命令接受零个或更多 PID 参数并显示相关的进程状态。如果使用带 -p
选项的 jobs
命令,只输出每个作业的进程组主进程 的 PID。将这个输出用作 ps
命令的参数,如清单 91 所示。
|
如果使用不带选项的 ps
,那么会看到一个进程列表,它们都以我们的终端作为控制终端,如清单 92 所示。
|
其他选项包括 -f
(完整)、-j
(作业)和 -l
(长),可以控制显示多少信息。如果不指定任何 PID,那么另一个有用选项是 --forest
选项,它在一个树型层次结构中显示运行的命令,显示哪个进程是哪个进程的父进程。例如,可以看到前面列表中的 sleep
命令是后台运行的脚本的子进程。如果碰巧在另一个时候运行这个命令,有可能看到进程状态中列出 date
命令,但是出现这种情况的几率非常小,因为这个命令运行得非常快。清单 93 列出了更多的进程状态信息。
|
列出其他进程
前面使用的 ps
命令只是列出从当前终端会话启动的进程(注意清单 93 的第二个示例中的 SID 栏)。要看到所有具有控制终端的进程,应该使用 -a
选项。-x
选项显示没有控制终端的进程,-e
选项显示 每个 进程的信息。清单 94 显示所有具有控制终端的进程的完整信息。
|
注意,这个列表包括两个 xclock 进程,都是在前面从系统的主图形终端(在这里由 pts/0 表示)启动的,显示的其他进程都是与一个 ssh(Secure Shell)连接(在这个示例中是 pts/3)相关联的。
ps
还有许多选项,包括对显示哪些字段以及如何显示字段进行控制的选项。其他选项控制如何选择要显示的进程,例如选择特定用户的进程。完整的细节请参考 ps
的手册页,也可以使用 ps --help
获得简单的总结。
top
如果需要多次运行 ps
来查看变化,那么可能需要使用 top
命令。它显示不断更新的进程列表,以及有用的总结信息。关于选项的完整细节请参考 top
的手册页,包括如何按照内存使用情况或其他条件进行排序。清单 95 显示 top
输出的前几行。
|
|
信号
现在看看 Linux 信号,这是与进程进行通信的一种异步方式。我们提到过 SIGHUP 信号并使用过 Ctrl-c 和 Ctrl-z(这是向进程发送信号的另一种方式)。发送信号的一般方式是使用 kill
命令。
使用 kill 发送信号
kill
命令向指定的作业或进程发送信号。清单 96 显示使用 SIGTSTP 和 SIGCONT 信号停止并恢复后台作业。使用 SIGTSTP 信号相当于使用 fg
命令将作业带到前台,然后使用 Ctrl-z 暂停它。使用 SIGCONT 相当于使用 bg
命令。
|
在这个示例中,使用作业指示(%1),但是也可以将信号发送给进程 id(比如,作业 %1 的 PID 21709)。如果在作业 %1 停止时使用 tail
命令,就只有一个进程在更新 nohup.out 文件。
还有许多其他信号,可以使用 kill -l
在系统上显示这些信号。一些信号用于报告错误,比如非法操作代码、浮点异常或试图访问进程无权访问的内存。注意,信号具有编号(比如 20)和名称(比如 SIGTSTP)。在 -s
选项中既可以使用编号,也可以使用名称。应该检查系统上的信号编号,确定哪个编号属于哪个信号。
信号处理程序和进程终止
我们已经见过用 Ctrl-c 终止进程。实际上,这会向进程发送 SIGINT(或 interrupt)信号。如果使用 kill 而不带任何信号名,它就会发送 SIGTERM 信号。对于大多数情况,这两个信号是等效的。
nohup
命令使进程对于 SIGHUP 信号“免疫”。一般情况下,进程可以实现信号处理程序 来捕获 信号。所以进程可以实现一个信号处理程序来捕获 SIGINT 或 SIGTERM。因为信号处理程序知道信号已经发送了,它可以选择采取什么操作,例如忽略 SIGINT,只在收到 SIGTERM 时终止进程。清单 97 显示如何向作业 %1 发送 SIGTERM 信号。注意,在发送这个信号之后,进程状态显示 “Terminated”。如果发送 SIGINT,会显示 “Interrupt”。过一会儿,发生进程清理,作业将从作业列表中消失。
|
信号处理程序为进程提供了很大的灵活性,使进 程可以根据信号执行正常工作和中断,从而实现某些特殊目的。除了允许进程捕获终止请求并采取适当操作,比如关闭文件或检查正在进行的事务,还常常使用信号 让守护进程重新读取它的配置文件,还可能重新启动操作。例如,可以在修改网络参数时向 inetd 进程发送信号,或者在添加新打印机时向行打印机守护进程(lpd)发送信号。
无条件终止进程
某些信号不能被捕获,比如某些硬件异常。SIGKILL 不能被捕获,它会无条件地终止进程。一般来说,只有在其他方式都无法终止进程时才需要发送这个信号。
|
注销和 nohup
我们说过,使用 nohup
允许进程在用户注销之后继续运行。现在就这么做,然后重新登录。在重新登录之后,使用 jobs
和 ps
检查时钟进程。输出如清单 98 所示。
|
可以看到这一次我们运行在 pts/4 上,但是没有作业,只有 ps
命令以及原来从图形终端(pts/0)启动的两个 xclock 进程。我们期望的情况不是这样的。但是,所有作业都丢失了。在清单 99 中,显示如何使用 -s
选项和会话 ID 找回失去的作业,在 清单 93 中可以看到会话 ID 是 20475。如果没有会话 ID 可用,请考虑有没有其他方法可以找回作业。
|
既然已经学习了如何杀死进程,您应该能够使用进程的 PID 和 kill
杀死这些进程。
|
进程执行优先级
本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.6 的内容。这个主题的权值是 3。
在本节中,学习以下主题:
优先级
在前一节中我们看到,与大多数现代操作系统一样,Linux 可以运行多个进程。为此,Linux 会在进程之间分享 CPU 和其他资源。如果某些进程可以使用 100% 的 CPU,那么别的进程就可能没反应了。在前一节中查看 进程状态 时,我们看到 top
命令的默认输出按照 CPU 使用情况的降序列出进程。如果对我们的简单时钟脚本运行 top 命令,这个进程可能进不了这个列表,因为这个进程在大多数时候不使用 CPU。
系统上可能有许多需要大量使用 CPU 的命令。例如,视频编辑工具以及在不同图像类型或不同声音编码之间进行转换(比如从 mp3 到 ogg)的程序。
我们将创建一个小脚本,它只是使用 CPU,不做有意义的事儿。这个脚本有两个输入,一个计数值和一个标签。它打印标签以及当前日期和时间,然后递减计数值直至为 0,然后再打印标签和日期。这个脚本没有错误检查,也不很健壮,但是它可以帮助我们说明概念。
|
如果在您自己的系统上运行这个脚本,可能会看到清单 101 所示的输出。这个脚本要使用大量 CPU。如果您不是使用自己的工作站,那么在运行这个脚本之前要确定使用大量 CPU 不会对别人造成不良影响。
|
到目前为止,还算好。现在使用在本教程中学到的知识创建一个命令列表,从而在后台运行这个脚本,并启动 top
命令来查看这个脚本使用多少 CPU。命令列表如清单 102 所示,top
的输出见清单 103。
|
|
不坏啊。我们用一个简单的脚本就占用了 98.4% 的 CPU。
|
显示和设置优先级
如果有这样的作业长期运行,那么可能发现它会干扰我们(或其他用户)在系统上进行其他工作的能力。Linux 和 UNIX 系统使用一个优先级系统,共有 40 个优先级,范围从 -20(最高优先级)到 19(最低优先级)。
nice
由一般用户启动的进程的优先级常常是 0。nice
命令显示默认优先级。ps
命令也可以显示优先级(nice 或 NI 级别),例如使用 -l
选项。清单 104 显示这种信息,其中突出显示了 nice 值 0。
|
nice
命令还可以用于以不同优先级启动进程。用 -n
(或 --adjustment
)选项和一个负值来提高优先级,用正值来降低优先级。记住,具有最低优先级值的进程以最高的调度优先级运行。所以可以认为提高优先级值就是对其他进程更谦让。 注意,常常需要具有超级用户(根用户)身份才能指定负的优先级调整值。换句话说,一般用户常常只能让他们的进程更谦让。在清单 105 中,在后台以不同的调度优先级运行 count1.sh 脚本的两个副本。注意,这两个进程的完成时间相差 5 秒。尝试使用不同的 nice 值,或者对第一个进程而不是对第二个进程进行优先级调整,从而体会不同情况的效果。
|
注意,与 nohup
命令一样,不能将命令列表或管道用作 nice
的参数。
|
修改优先级
renice
如果启动了一个进程并意识到它应该以不同的优先级运行,那么也有办法在进程启动之后修改它的优先级,也就是使用 renice
命令。为要修改的进程指定绝对的优先级(而不是调整值),如清单 106 所示。
|
可以在手册页中找到关于 nice
和 renice
的更多信息。
|
用正则表达式进行搜索
本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.7 的内容。这个主题的权值是 3。
在本节中,学习以下主题:
正则表达式
正则表达式来源于计算机语言理论。大多数计算机科学系的学生都知道,可以用正则表达式表达的语言也就是有穷自动机可以接受的语言。本节讨论的正则表达式是一种表达复杂规则的方式,所以与您在计算机科学课上学到的正则表达式 不是 一回事儿,尽管它们之间确实有继承关系。
正则表达式(也称为 “regex” 或 “regexp”)是一种描述文本字符串(即模式)的方式,它使程序能够针对任意文本字符串匹配 模式,这就提供了非常强大的搜索能力。grep
(表示 generalized regular expression processor,通用正则表达式处理程序)是任何 Linux 或 UNIX 程序员或管理员的标准工具。它允许使用正则表达式搜索文件或命令输出。在 文本流和过滤器 一节中,我们介绍了 sed
(stream editor,流编辑器),这是另一个大量使用正则表达式在文件或文本流中搜索并替换文本的标准工具。本节帮助您更好地理解 grep
和 sed
所使用的正则表达式。另一个大量使用正则表达式的程序是 awk
,这个程序属于 LPIC-2 认证考试 201 的范围。
关于正则表达式和计算机语言理论有许多书籍。请参见 参考资料 中的一些学习建议。
在学习正则表达式时,您会发现正则表达式语法与 通配符和 globbing 一节讨论的通配符(即 globbing)语法之间存在相似性。但是,这种相似性只是表面的,这两者没有内在联系。
|
基本成分
大多数 Linux 系统上的 GNU grep 使用两种形式的正则表达式语法:基本的 和扩展的。对于 GNU grep,这两种形式在功能上没有区别。本节描述基本语法,以及它与扩展语法之间的差异。
正则表达式由字符 和操作符 以及元字符 组成。大多数字符匹配本身,大多数元字符必须使用反斜线(/)进行转义。基本操作如下:
我们以前面在 lpi103 目录中创建的文本文件为例。请研究清单 107 中的简单示例。注意,grep
的参数是一个正则表达式(必须有)和要搜索的零个或多个文件的列表。如果没有给出文件,grep 就会搜索 stdin,这使它成为可以在管道中使用的过滤器。如果没有匹配的行,grep
就没有输出,但是可以检查它的退出码。
|
从这些示例中可以看到,有时候可能会得到令人吃惊的结果,尤其是在使用重复的时候。您可能期望 p* 或至少 pp* 匹配多个 p 字符,但是 p*(和 x*)匹配文件中的每一行,因为 * 操作符匹配前面正则表达式的 零次 或更多次出现。
便捷方式
既然已经有了正则表达式的基本成分,我们来看几种便捷方式。
|
匹配行的开头或结尾
^(脱字符)匹配行的开头,$(美圆符号)匹配行的结尾。所以 ^..b 匹配行首的任意两个字符,后面跟着一个 b;ar$ 匹配任何以 ar 结尾的行。正则表达式 ^$ 匹配空行。
更复杂的表达式
到目前为止,我们见过了对单个字符应用重复。如果希望搜索一个多字符字符串的一次或多次出现,比如在 banana 中两次出现的 an,那么使用圆括号,这在基本语法中必须进行转义。与此相似,可能希望搜索几个字符,而不使用 . 那样宽泛的搜索或者很长的选择。可以将要搜索的字符放在方括号([])中,对于基本语法,这不需要转义。方括号中的表达式组成了一个字符类。除了后面讨论的几种例外情况之外,使用方括号还可以避免对特殊字符(. 和 *)进行转义。
|
对于字符类,有另外几种有意思的情况。
字符类是正则表达式和 globbing 相似的一个方面,但是取反符号不一样(^ 和 !)。清单 110 给出一些字符类示例。
|
|
通过 sed 使用正则表达式
前面对 sed 的简短介绍中提到过,sed 要使用正则表达式。正则表达式可以在地址表达式和替换表达式中使用。所以表达式 /abc/s/xyz/XYZ/g
意味着 只 对包含 abc 的行应用替换命令,将每个 xyz 替换为 XYZ。清单 111 给出应用于 text1 文件的两个示例,另一个示例将点(.)前面的最后一个单词替换为字符串 LAST WORD。注意,字符串 First 没有被修改,因为它前面没有空格。
|
|
扩展的正则表达式
在基本语法中使用某些字符时需要对它们进行转义,扩展的正则表达式语法消除了这些需求,这些字符包括圆括号、‘?’、‘+’、‘|’ 和 ‘{’。这意味着,如果希望将它们解释为一般字符,就必须进行转义。可以使用 grep 的 -E
(或 --extended-regexp
)选项表示您正在使用扩展的正则表达式语法。也可以使用 egrep
命令。一些比较老的 sed
版本不支持扩展的正则表达式。如果您的 sed
版本支持扩展的正则表达式,那么使用 -r
选项告诉 sed
正在使用扩展的语法。清单 112 给出本节前面使用过的一个示例,以及 egrep
使用的对应的扩展表达式。
|
|
在文件中进行搜索
本节的最后让您体会一下 grep
和 find
的强大功能,在文件系统中搜索一些东西。同样,这些示例非常简单;它们使用在 lpi103 目录及其子目录中创建的文件。
首先,grep
可以同时搜索多个文件。如果添加 -n
选项,它会指出匹配的行数。如果只希望知道匹配的行数,那么使用 -c
选项;如果只希望列出匹配的文件,那么使用 -l
选项。清单 113 给出一些示例。
|
最后一个示例使用 find
在当前目录及其子目录中搜索所有常规文件,然后使用 xargs
将文件列表传递给 grep
,以便判断每个文件中出现 banana 的次数。最后,用另一个 grep
调用对这个输出进行过滤,这一次使用 -v
选项,找出 不包含 搜索字符串的所有文件。
|
本节只触及了 Linux 命令行和正则表达式功能的皮毛。请使用手册页进一步学习这些有用的工具。
|
用 vi 进行文件编辑
本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.8 的内容。这个主题的权值是 1。
在本节中,学习以下主题:
使用 vi
vi 编辑器几乎在所有 Linux 和 UNIX 系统上都存在。实际上,如果系统只有一个编辑器,那么它可能就是 vi,所以了解 vi 的操作方式是有意义的。本节介绍一些基本的 vi 编辑命令。要想全面了解 vi,请阅读 “vi intro -- the cheat sheet method”(参见 参考资料),或者参考手册页或相关书籍。
启动 vi
大多数 Linux 发行版本现在附带 vim(表示 Vi IMproved,改进的 vi)编辑器而不是传统的 vi。vim 向上兼容 vi,并有图形模式(gvim)以及标准的 vi 文本模式界面。vi
命令常常是 vim 程序的别名或符号链接。请回顾前面的主题 shell 在哪里寻找命令?,了解使用的究竟是哪个命令。
在前面的 修改优先级 一节中,我们希望修改正在运行的 count1.sh shell 脚本的优先级。如果您自己尝试这个操作,就会发现这个命令运行得太快了,没有足够的时间可以用 renice
完成优先级修改。所以我们要使用 vi 编辑器在文件的开头添加一行,从而让进程睡眠 20 秒,这样就有时间修改优先级了。
要启动 vi 编辑器,使用 vi
命令并以文件名作为参数。有许多选项可供选用,细节请参考手册页。使用命令
vi count1.sh
应该会看到与清单 115 相似的显示。如果使用 vim,一些单词可能是不同颜色的。vim 有语法突出显示模式(原始的 vi 编辑器没有这种模式),在系统上这种模式可能是默认打开的。
|
vi 模式
vi 编辑器有两种操作模式:
这两个模式决定了编辑器的行为。在开发 vi 的时候,并不是所有的终端键盘上都有光标移动键,所以在 vi 中可以做的所有事情都可以使用标准打字机上的键加上 Esc 和 Insert 键来完成。但是,可以将 vi 配置为使用额外的键(如果这些键可用的话);键盘上的大多数键在 vi 中都有作用。vi 是在早期的终端连接时代开发的,所以 vi 使用的命令非常短,因此命令的含义比较含混。
离开 vi
在学习新编辑器时,我喜欢先了解如何退出它,以免意外退出。下面这些退出 vi 的方式包括保存或放弃修改,或者从头重新开始。如果这些命令看起来无效,那么您可能是在插入模式中,所以要按 Esc 离开插入模式并返回命令模式。
注意:
移动
以下命令用于在文件中到处移动:
如果在这些命令前面输入一个数字,那么命令将重复执行这个数字指定的次数。这个次数称为重复计数,或简称为计数。例如,5h 将向左移动 5 个字符。对于许多 vi 命令都可以使用重复计数。
移动到行
以下命令用于移动到文件中的特定行:
搜索
可以使用正则表达式搜索文件中的文本:
可以在上面的任何搜索命令前面加上一个数字,表示重复计数。所以,3/x 将搜索从当前点开始的第 3 个 x,这相当于 /x 后面跟着 2n。
修改文本
使用以下命令插入、删除或修改文本:
应用这些操作
我们打算在 count1.sh 文件中添加一行。为了保留原来的文件并将修改的版本保存为 count2.sh,可以在用 vi
打开文件之后,使用下面这些 vi 命令。注意,<Esc> 表示按 Esc 键。
|
会者不难,是吧?
本系列中的下一个教程讨论主题 104,涉及设备、Linux 文件系统和 Filesystem Hierarchy Standard(FHS)。
|