Unix 之书
☰ 目录

第 24 章...

处理目录 (Working With Directories)

这是解释 Unix 文件系统的三章中的第二章。 在第 23 章里,我们从整体上讨论了 文件系统:它如何组织成由目录和 子目录构成的树状层次结构、文件系统的 各个部分分别用于什么,以及你在使用 Unix 过程中会遇到哪些类型的文件。

在这个整体的层次结构中,每个用户都会被分配 一个主目录,由他自己决定如何组织。 要操作属于你的那部分目录树,以及整个 文件系统,你需要能够在目录之间 快速、轻松地来回导航。你还需要能够 通过按需创建、删除、移动和重命名 子目录来组织自己的文件。最后,你还需要 能够查看各个目录内部的情况, 以便处理其中的文件和子目录。

在本章中,你将学会处理目录所需的全部 基本技能。在第 25 章,我们会通过 学习操作普通文件的命令,为这一主题的 讨论画上句号。

路径名与你的工作目录
(Pathnames and Your Working Directory)

在第 23 章中,我们讨论了如何写出一 个文件的完整名称。以一个 /(斜杠)开头, 它代表根目录。然后写出通往 该文件所必须经过的所有目录的名字, 每个名字后面都跟一个 /。 最后,写出文件本身的名字。举个例子:

/usr/share/dict/words

在这个例子中,文件 words 位于 dict 目录里,dict 目录位于 share 目录里,share 目录位于 usr 目录里,而 usr 目录位于根目录里。

当我们以这种方式写出一个文件的名称时,我们描述的是 从根目录到该文件在目录树中所经过的 路径。为此,我们要指定一列以 / 字符分隔的目录。这样的描述称为 路径名(PATHNAME)或路径(PATH)。上面你看到的 就是一个路径名的例子。

如果路径名的最后一部分是一个普通文件的名字, 我们称它为文件名(FILENAME),有时也称为 基名(BASENAME)。在我们的例子里,words 就是 一个文件名。

这里还有另一个路径名的例子。假设你的 用户 ID 是 harley,你的主目录是 /home/harley(见第 23 章)。你有一个 名为 memo 的文件,想用 vi 文本编辑器(第 22 章)来编辑它。要启动 vi,你输入命令:

vi /home/harley/memo

过了一段时间,你决定编辑另一个 文件 document。你输入:

vi /home/harley/document

在这些例子中,路径名是:

/home/harley/memo
/home/harley/document

文件名是:

memo
document

你可以想象得到,每次要访问一个文件时都输入 完整的路径名,既累人又容易出错。作为一种 便利,Unix 允许你一次把某一个目录指定为你的 工作目录(WORKING DIRECTORY,也叫作你 的当前目录(CURRENT DIRECTORY))。只要你想使用 工作目录中的某个文件,只需输入文件名即可; 不必指定完整的路径。例如,如果你告诉 Unix 你想在 /home/harley 目录中工作 (细节我暂时还不打算讲),那么下面这两条命令 是等价的:

vi /home/harley/memo
vi memo

规则是这样的:当你使用的名字以 / 开头时, Unix 认为它是一个完整的路径名,从根目录 开始。第一条命令就是这种情况。当你只使用 一个文件名时,Unix 认为你指的是 工作目录中的文件。第二条命令就是这种 情况。(等你有了经验,就会发现这条规则非常 合理。)

每次登录时,Unix 都会自动把你的工作目录设置为 你的主目录(*),这是一个很方便的 工作起点。在工作过程中,你可以随时用 cd(change directory,更改目录)命令 更改工作目录,我们在本章后面会讲到它。 在一次工作过程中,根据你正在做的事情, 不时更改工作目录是很常见的事。 不过,最后你在哪个目录并不重要。 下次登录时,你又会从自己的 主目录开始。

* 脚注

Unix 怎么知道你主目录的名字? 每个用户 ID 的主目录的路径名都保存在 Unix 口令文件 /etc/passwd 中, 第 11 章有相关介绍。

我希望你这样来理解它。把 Unix 文件系统想象成 一棵非常大的树。树干就是根目录,所有其他 目录都是这棵树的树枝。例如,目录 /home/bin 是从根上长出的 树枝。目录 /home/harley/home 上的一根树枝。在任何时刻,你都坐在 树上的某一根树枝上。那就是你的 工作目录。

你一登录,就会发现自己坐在代表你主目录的那根 树枝上。要移动到树上的另一根树枝,你 所需要做的只是更改工作目录。因此,你可以把 cd 命令想象成一块魔毯,它立刻把你从 树的一根树枝送到另一根树枝上。

绝对路径名与相对路径名
(Absolute and Relative Pathnames)

路径名或路径通过列出以 /(斜杠) 字符分隔的一串目录,来描述文件树中的一个 位置。如果这串目录是从根目录开始的,我们称它为 绝对路径名(ABSOLUTE PATHNAME)。如果这串目录 是从你的工作目录开始的,我们称它为相对路径名 (RELATIVE PATHNAME)。

为了说明两者的区别,我将使用 Figure 24-1 中的目录树。这棵树展示的是 属于用户 ID harley 的各子目录,他的 主目录是 /home/harley。(记住, 第 4 章讲过,Unix 文件的所有者是 用户 ID,而不是用户。)

Figure 24-1. 一个示例目录树

在用户 ID harley 的主目录中,有两个子目录:binessays。第一个子目录包含 两个普通文件。第二个子目录有自己的 两个子目录,每个子目录中各包含 两个普通文件。正文中使用这个小小的 树形结构来说明绝对路径名与 相对路径名之间的区别。

 

在主目录之内,我们有两个子目录, binessays。按照 Unix 的传统,bin 目录中的文件存放可执行的 程序和脚本(见第 23 章)。本例中 这样的程序有两个,funkyspacewaressays 子目录 又包含它自己的两个子目录,historyliterature。这两个目录中各有两个 普通文件。当用户 ID harley 登录时, 工作目录会自动设置为主目录 /home/harley。我们来看看可以怎样 指定其中各个文件的名字。

假设我们要使用一条命令,其中需要引用 bin 目录。Unix 假定任何以 / 开头的名字都是绝对路径名。也就是说,它给出 从根目录开始到该文件的完整路径。如果一个名字 不以 / 开头,Unix 就假定它是相对路径名。 也就是说,它描述的是从工作目录开始的路径。

我们可以用两种方式引用 bin 目录。 首先,绝对路径名是:

/home/harley/bin

另外,由于工作目录是 /home/harley, 使用相对路径名要简单得多:

bin

这是在同一个工作目录下的另一个例子。 我们要输入一条命令,其中需要指定 literature 目录里 tolstoy 文件的名字。绝对路径名是:

/home/harley/essays/literature/tolstoy

同样,相对路径名更短:

essays/literature/tolstoy

最后再举一个例子。假设我们要对 kafkatolstoy 这两个文件 做很多工作。用绝对路径名来引用这些文件 很不方便:

/home/harley/essays/literature/kafka
/home/harley/essays/literature/tolstoy

不过,改用相对路径名也只是稍微方便一点:

essays/literature/kafka
essays/literature/tolstoy

最好的做法是把工作目录更改为:

/home/harley/essays/literature

(我马上就会告诉你该怎么做。)一旦更改了 工作目录,我们就可以用更简单的名字引用这些 文件:

kafka
tolstoy

把工作目录想成一个你可以随时更换的 行动基地。登录时,你从主目录开始, 但你可以随时切换到任何你想要的目录。 思路是:选好你的工作目录,让文件名 尽可能简单、易于输入。

在全书中,你会看到很多例子,其中我使用 这样的文件名:

vi kafka

现在你明白了,在这类情况下,我实际使用的是 相对路径名。在这个例子里,这条命令启动 vi 文本编辑器,编辑工作目录中名为 kafka 的文件。当然,必要的时候,你随时可以使用 完整的路径名:

vi /home/harley/essays/literature/kafka

每当你使用一个语法上要求指定文件名的程序时, 我都希望你记住这个概念。在这种情况下,你既可以 使用绝对路径名,也可以使用相对路径名。

— 提示 —

Unix 有一条基本规则:凡是能使用普通文件名的 地方,你确实都可以使用路径名。

三个方便的路径名缩写:.. . ~
(Three Handy Pathname Abbreviations: .. . ~)

Unix 提供了三个方便的路径名缩写。第一个是 连续两个句点,读作 "dot-dot"(点点):

..

当你在路径名中使用 .. 时,它指的是 父目录。

为了说明它是如何工作的,我们参照 Figure 24-1 中的示例目录树。在主目录 /home/harley 中,有两个子目录, binessaysbin 目录包含两个文件。essays 子目录 包含它自己的两个子目录,historyliterature。这两个目录中各有两个文件。 假设你把工作目录设置为:

/home/harley/essays/literature

(要做到这一点,你要使用 cd 命令, 我们在本章后面会讲。)一旦以这种方式更改了 工作目录,你就可以用 kafkatolstoy(使用相对路径名)来指代这个 目录中的两个文件。此时,写法 .. 指的是父目录,也就是:

/home/harley/essays

假设你想引用 history 目录中的 crusades 文件。一种方法是输入 完整的绝对路径名:

/home/harley/essays/history/crusades

更简单的办法是用缩写 .. 来代表 父目录:

../history/crusades

当你使用 .. 时,它的作用就等同于 父目录的名字,所以上面这个路径名与那个 绝对路径名是等价的。

你可以多次使用 ..,从而"向上" 移动多个层级。例如,在同一个工作目录下, 假设你想引用 bin 目录。你可以使用 绝对路径名:

/home/harley/bin

或者,你可以使用两次 .. 缩写:

../../bin

第一个父目录是:

/home/harley/essays

第二个父目录(祖父目录)是:

/home/harley

再举一个例子。你想引用 bin 目录中的 funky 文件。绝对路径名是:

/home/harley/bin/funky

从同一个工作目录出发,你可以写成:

../../bin/funky

最后一个例子有点极端。(请慢慢读, 确保你理解了它。)你的工作目录是:

/home/harley/essays/literature

要引用整个文件系统的根目录,你可以使用 四次 ..:

../../../..

类似地,你可以把 /etc 目录写成:

../../../../etc

当然,现实中你大概永远不会用这些例子, 因为直接输入 //etc 要容易得多。.. 这个缩写最有用的场合, 是你想引用工作目录附近的目录,而又不想 真的更改工作目录的时候。

第二个路径名缩写是单个句点, 通常读作 "dot"(点):

.

单个 . 指的是工作目录本身。例如, 假设工作目录是:

/home/harley/essays/literature

下面这三种写法指的都是同一个文件:

/home/harley/essays/literature/kafka
./kafka
kafka

显然,输入 . 比输入工作目录的完整 名字要轻松得多。但是你可以看到,其实你 根本不必指定任何目录名。只要一个名字不以 / 开头,Unix 就认为任何路径名都是相对于 你的工作目录的。这条原则重要到我要把它 写成一条提示:

— 提示 —

任何不以 / 开头的路径名, 都被认为是相对于你的工作目录的。

你可能会问,什么情况下才需要用单个 . 缩写?有些场合你必须指定一个绝对路径名。 在这种情况下,你可以用 . 缩写来代表 工作目录的名字。这样做的目的是避免输入 很长的路径名,这不仅是因为偷懒(虽然偷懒 也是个好主意),更是为了降低拼写出错的 可能性。(我相信你现在已经清楚,输入 Unix 命令时犯拼写错误实在是太容易了。)

这里举个例子。假设你写了一个名为 plugh 的程序。(这个程序是干什么的,就留给你的 想象力吧。)该程序位于目录 /home/harley/adventure 中,而此时这个 目录正是你的工作目录。通常,你输入程序的名字 来运行它:

plugh

然而,只有当 Unix 能够找到某个程序时, 它才能运行这个程序。多数情况下,这意味着 存放该程序的文件应当位于你搜索路径中的 某个目录里(见第 13 章)。在我们的例子中, 存放这个程序的目录不在你的搜索路径中。 不过,只要你指定绝对路径名,Unix 总能 找到并运行该程序。因此,你可以通过输入下面 这行来运行 plugh 程序:

/home/harley/adventure/plugh

然而,由于这个程序就在你的工作目录里, 更省事的做法是使用 . 缩写:

./plugh

请务必理解 ... 只是 缩写。正如我们的例子所说明的,当你用 ... 开头写一个名字时, 你实际上指定的是一个完整的路径名。Unix 只不过是在帮你少打字而已。

第三个路径名缩写是 ~(波浪号)。 你可以在路径名开头使用这个符号来代表你的 主目录。例如,要用 ls 程序列出主目录中 所有文件的名字,你可以写:

ls ~

要列出主目录中 bin 子目录里的文件, 你可以写:

ls ~/bin

要引用另一个用户 ID 的主目录,你可以在 ~ 后面跟上那个用户 ID。例如,要列出 用户 ID weedly 主目录中的文件, 你会写:

ls ~weedly

假设 weedly 也有他自己的 bin 目录,而且在那个 bin 目录里有一个名为 mouse 的程序。要运行这个程序,你必须 输入绝对路径名。你有两种选择:

/home/weedly/bin/mouse
~weedly/bin/mouse

最后这几个例子引出了一个重要的问题。 任何用户都能查看别人的文件、运行别人的 程序吗?就此而言,一个用户能修改别人的 文件吗?

答案是:所有文件(包括目录)都有"权限"。 一个文件的权限规定了谁能查看和修改这个 文件。在许多系统上,默认设置是允许用户 查看别人的文件,但不允许修改或运行它们。 不过,文件权限是由文件的所有者控制的。 因此,每个 Unix 用户都可以按自己的意愿 限制或允许别人访问他的文件。我们会 在第 25 章讨论这些问题。

— 技术提示 —

指定路径名时,你可以使用三个标准缩写: .(当前目录)、..(父目录)和 ~(主目录)。它们看起来相似,但 实现方式并不相同。

名字 ... 是实实在在的目录项, 由文件系统自动创建。系统中的每个目录都 包含这两个目录项。

名字 ~ 则是 Shell 提供的一种抽象, 目的是让你方便地引用自己的主目录。

在目录树中移动:cd, pwd
(Moving Around the Directory Tree: cd, pwd)

要显示工作目录的名字,使用 pwd(print working directory,打印工作目录)命令。语法 很简单:

pwd

要更改工作目录,你使用 cd(change directory,更改目录)命令。语法是:

cd [-LP] [directory | -]

其中 directory 是你想要切换到的那个目录的 名字。

如果你输入命令时不带目录名,cd 默认会 切换到你的主目录。如果你输入命令时用 -(连字符)代替目录名,cd 会切换 到上一个目录。-L-P 选项与 符号链接有关,我们在第 25 章会讲到。

作为一条一般规则,当一个 Unix 名字很短且没有 元音字母时,我们把它的名字按字母逐个读出来。 例如,ls 命令读作 "L-S"。同样,pwdcd 命令读作 "P-W-D" 和 "C-D"。

在所有 Unix 工具中,cdpwd 是 最有用的几个之一。你会发现自己要频繁地使用它们, 所以请仔细读完本节。下面是一些 cd 命令的 使用示例。当你用自己的例子练习时,记得不时用 pwd 命令检查一下自己身在何处。

要把工作目录更改为 /home/harley/essays,使用:

cd /home/harley/essays

要切换到 /bin,使用:

cd /bin

要切换到 /(根目录),使用:

cd /

为了方便,你既可以使用相对路径名,也可以使用 缩写。例如,假设你当前的工作目录是 /home/harley。在这个目录中,你有两个 子目录,binessays。要切换到 bin(即 /home/harley/bin), 只需输入:

cd bin

由于目录名 bin 不以 / 开头,Unix 假定它是一个相对于你工作目录的相对路径名。 再举一个例子。同样,你的工作目录是 /home/harley。这一次你想切换到:

/home/harley/essays/history

使用相对路径名,你可以输入:

cd essays/history

当你使用 cd 命令而不带目录名时,它会把 你的工作目录更改为你的主目录:

cd

以这种方式用 cd,是你在文件系统遥远的 分支中探索、已经迷失方向时,回家的最快 途径。(*) 例如,假设你的工作目录恰好是 /etc/local/programs。你想移动到主目录中 的 bin 目录。只需输入:

cd
cd bin

* 脚注

或者,如果你恰好穿着一双红宝石鞋, 你可以把脚后跟相碰三次,然后念 "没有任何地方比得上家。"

第一条命令切换到你的主目录。第二条命令 切换到主目录中的 bin 目录。为了更 方便,回想一下,你可以用分号分隔各条命令, 在同一行输入多条命令(见第 10 章)。 因此,无论你在文件系统的什么位置,都可以 通过输入下面这行来到你自己的私人 bin 目录:

cd; cd bin

下面这些例子展示如何使用我们先前讲过的两个 标准路径名缩写。先从 .. 开始,它是 父目录的缩写。假设你的工作目录是:

/home/harley/essays/history

要切换到父目录 /home/harley/essays, 只需在树上向上一级:

cd ..

从最初的工作目录出发,你可以用下面这条命令 切换到 /home/harley/essays/literature:

cd ../literature

要向上跨越多个层级,就多次使用 .. 缩写。例如,从最初的工作目录出发,你可以用 下面的命令切换到 /home/harley/bin:

cd ../../bin

问:如果你正在根目录中,然后输入 下面这条命令,会发生什么?

cd ..

答:什么都不会发生。你的工作目录不会改变, 你也不会看到错误消息。为什么?因为 Unix 认为根目录的父目录就是根目录 本身。(*) 例如,尽管看起来很奇怪, 下面两个路径名指的是同一个文件:

/etc/passwd /../../../etc/passwd

* 脚注

一个有着重要神学含义的假定。

另一个有用的缩写是 ~(波浪号), 正如我们所讨论的,它代表你主目录的名字。 因此,下面两行命令的效果相同:它们都把 你的工作目录设置为主目录中的 bin 子目录:

cd; cd bin
cd ~/bin

第一条命令分两步完成切换;第二条命令一步 到位。

有时候,你会发现自己需要在两个目录之间 来回切换。在这种情况下,cd 有一个特殊的 缩写可以让事情更简单。如果你输入 - (连字符)而不是目录名,cd 会切换到你 上一个访问的目录。同时,cd 会显示新 目录的名字,这样你就知道自己身在何处。

下面是一个你可以自己动手试试的例子。首先, 用 cd 切换到 /etc 目录, 然后用 pwd 确认更改:

cd /etc; pwd

现在切换到 /usr/bin:

cd /usr/bin; pwd

最后,输入带 - 字符的 cd

cd -

你现在回到了 /etc

— 提示 —

任何时候,你都可以通过 pwd 显示 工作目录的名字,从而知道自己在树中的 什么位置。不过,还有两种替代办法。

第一,可以在 Shell 提示符中显示工作目录的 名字。当你从一个目录切换到另一个目录时, 提示符会自动更新,告诉你身在何处。细节 见第 13 章。

第二,大多数基于图形用户界面的终端窗口 会在标题栏(窗口顶部)显示你工作目录的名字。 花点时间看看你的系统是不是这样。

名称的由来

pwd, cd


在第 3 章中,我们讲过早期 Unix 开发者使用电传打字终端,把输出打印在 纸上。多年来,Unix 一直保留着用 "print" (打印)一词表示"显示信息"的习惯。因此, pwd 这个名字代表 "print working directory"(打印工作目录),尽管早就没有人 真的把工作目录的名字打印在纸上了。

如果你常和 Unix 极客混在一起,你会经常听到 他们把 cd 当动词用。(他们这样用的 时候,cd 是按两个字母 "C-D" 来读的。) 例如,有人可能会说:"要找 Unix 的基本工具, 只要 C-D 到 /bin 目录里看看就行。" 这与我们那个比喻是一致的:我们想象自己坐在 树的某一根树枝上,用 cd 移动到另一根 树枝,用 pwd 提醒我们自己在哪里。

创建新目录:mkdir
(Making a New Directory: mkdir)

要创建目录,你使用 mkdir 程序。语法是:

mkdir [-p] directory...

其中 directory 是你想要创建的目录的名字。

使用这个程序很直接。新目录你想叫什么就叫什么, 只要遵守几条简单的规则。我会在 第 25 章讲到文件命名时详细介绍这些规则。 (记住,正如我在第 23 章解释的, 目录其实也是文件。)基本上,你可以使用字母、 数字,以及那些没有特殊含义的标点符号。不过 多数时候,如果你只用小写字母,日子会好过 得多。

这里举个例子。要在工作目录中创建一个名为 extra 的目录,使用:

mkdir extra

指定目录名时,你可以使用绝对路径名或 相对路径名,也可以使用标准缩写。举个例子, 假设你想创建 Figure 24-2 中的目录树 (也就是本章前面我用作示例的那些目录)。 你希望在主目录中创建两个子目录, binessays。你希望在 essays 目录中再创建两个子目录, historyliterature

Figure 24-2. 创建一个示例目录树

要创建新目录,你使用 mkdir 程序。这里是一个示例目录树,它是由 若干示例 mkdir 命令创建的 (细节见正文)。这棵树由主目录中的 两个子目录构成,binessays。bin 目录有自己的 两个子目录,historyliterature

 

首先,确认你在自己的主目录中:

cd

现在,创建前两个子目录:

mkdir bin essays

接下来,切换到 essays 目录,创建最后 两个子目录:

cd essays
mkdir history literature

为了展示指定路径名的多种方式,我们再来看看 创建同样这些目录的另外两种方法。首先,你 完全可以不出主目录就把整件事做完:

cd
mkdir bin essays essays/history essays/literature

第一条命令切换到主目录。第二条命令相对于 你的工作目录,一次指定了全部四个名字。在 下面这个例子里,我们甚至懒得先切换到 主目录:

mkdir ~/bin ~/essays ~/essays/history ~/essays/literature

记住,~(波浪号)字符是你主目录的 一个缩写。

有些时候,用 .. 缩写来指明父目录会很 方便。例如,假设你已经切换到了 essays 目录:

cd ~/essays

此时你决定在 bin 目录中创建一个名为 extra 的子目录。由于 binessays 有同一个父目录(主目录), 你可以使用:

mkdir ../bin/extra

当你创建目录时,Unix 会让你遵守两条合理的 规则。第一,在一个目录之内,你不能创建两个 同名的子目录。例如,你不能有两个名为 ~/essays/history 的目录。(你怎么区分 它们呢?)但是,如果两个同名目录位于不同的 父目录中,那是可以的。例如:

~/essays/history
~/homework/history

第二条规则是:默认情况下,如果父目录不存在, 你就不能创建它的子目录。例如,除非你已经 创建了 ~/homework,否则你无法创建 ~/homework/history。当你在单条命令中 指定多个目录时,mkdir 会按你指定的 顺序创建这些目录。因此,下面这条命令是有效的, 因为你告诉 mkdir 先创建 homework 目录,再创建 history 目录:

mkdir ~/homework ~/homework/history

然而,下一条命令就不行了,因为你不能在创建 父目录之前先创建子目录:

mkdir ~/homework/history ~/homework

回想一下我们把文件系统比作一棵树的比喻。 主干是根目录,每根树枝是一个子目录。这两条 规则不过是在说:

1. 你不能创建两根完全相同的树枝。

2. 你不能创建一根在树上无处可接的新 树枝。

为了方便,你可以用 -p(make parent, 创建父目录)选项来绕过第二个限制。这告诉 mkdir 自动创建所有必要的父目录。 例如,假设你正在研究早期罗马人如何使用 Unix, 你需要创建下面的目录结构来存放文件:

~/essays/history/roman/unix/research

除非 unix 存在,否则你无法创建 research 目录;除非 roman 存在, 否则你无法创建 unix;以此类推。因此, 如果这些目录一个都不存在,你就得依次使用 五条命令才能创建出完整的结构:

mkdir ~/essays
mkdir ~/essays/history
mkdir ~/essays/history/roman
mkdir ~/essays/history/roman/unix
mkdir ~/essays/history/roman/unix/research

但是,如果你使用 -p,一条命令就能 创建出全部目录:

mkdir -p ~/essays/history/roman/unix/research

— 提示 —

在文件名方面,Unix 是区分大小写的,也就是说 它区分大写和小写(见第 4 章)。例如, 下面三个目录名被认为是不同的:

bin
Bin
BIN

我们会在第 25 章讨论文件命名。现在, 让我先给你下面这条建议。给目录命名时,除非 你有非常充分的理由,否则只使用小写字母。 如果你想分隔单词,请使用 -(连字符) 或 _(下划线),例如:

backups-january
backups_january

如果把整个名字用引号括起来,目录名中是可以 使用空格的。但不要这样做:那只会带来麻烦。

删除目录:rmdir
(Removing a Directory: rmdir)

要移除(删除)一个目录,使用 rmdir 程序。语法很直白:

rmdir [-p] directory...

其中 directory 是你想要移除的目录。

例如,要从工作目录中移除 extra 目录,使用:

rmdir extra

使用 rmdir 时,你可以用一个或多个目录名, 并采用绝对路径名或相对路径名。你也可以使用 标准缩写:.. 代表父目录,~ (波浪号)代表主目录。

我们来看几个例子,使用上一节中搭起的示例 目录树。(见 Figure 24-2。)在主目录中, 我们有两个子目录,binessays。在 essays 目录中,我们 又有两个子目录,historyliterature。假设你想删除这四个目录。 有好几种做法。首先,移动到 essays 目录:

cd ~/essays

从这里出发,你可以删除这两个子目录:

rmdir history literature

接下来,移动到父目录(主目录):

cd ..

删除两个主要的子目录:

rmdir bin essays

另一种做法是先移动到主目录,然后用一条命令 删除全部四个子目录:

cd
rmdir essays/history essays/literature essays bin

最后一个例子是,你完全可以不移动到主目录就 把所有活干完:

rmdir ~/essays/history ~/essays/literature ~/essays ~/bin

当你移除一个目录时,Unix 让你遵守两条合理的 规则。第一,作为一种保护措施,除非目录是空的, 否则你不能移除它。(如果目录中含有子目录或 文件,那它就不是空的。)

这里举一个现实生活中的例子。那是周日晚些 时候,你在机房里用 Linux 赶一个特殊项目。 你的主目录包含两个子目录,dataolddatadata 目录里有 100 个 重要文件。olddata 目录是空的。你决定 移除 olddata 目录。然而,就在你输入 命令的那一刻,一块陨石撞碎窗户,砸中了坐在你 旁边的一个极客。在一片混乱中,你不小心 输入了:

rmdir data

幸运的是,Unix 早就为这种意外做好了准备。 你看到这条消息:

rmdir: data: Directory not empty

多亏了这个内置的保护措施,你的 data 目录毫发无损。

如果你想一次删除一串空目录,可以用 -p(delete parent,删除父目录) 选项(*)来做到。这告诉 rmdir 自动 移除所有必要的父目录。例如,假设你有如下 目录结构,而且所有目录都是空的。

~/essays/history/roman/unix/research

* 脚注

有人把它称为俄狄浦斯选项。

你想删除全部五个子目录。如果没有 -p 选项,你就得从最里面的子目录 开始,沿着树一层层往上:

cd
rmdir essays/history/roman/unix/research
rmdir essays/history/roman/unix
rmdir essays/history/roman
rmdir essays/history
rmdir essays

而有了 -p 选项,你就可以切换到主目录, 一次完成全部工作:

cd
rmdir -p essays/history/roman/unix/research

如果目录不是空的,上面这些命令都不管用。 如我所说,这是为了保护你。不过偶尔也会有 一些少见的场合,你确实想要删除一个非空目录。 为此,你可以使用 rm 程序并加上 -r 选项。rm -r 会同时删除 所有子目录及其内容,所以你必须 非常小心。我们会在第 25 章讨论 rm 程序,所以细节我到那时再讲。

刚才我提到 rmdir 有两条限制规则。 第一,除非目录是空的,否则你不能移除它。 第二条规则是,你不能移除位于你的工作目录与 根目录之间的任何目录。例如,假设你的 工作目录是:

/home/harley/essays/literature

你不能移除 essays 目录或 harley 目录,因为它们位于你和根目录之间。不过, 你可以移除这个目录:

/home/harley/essays/history

也就是说,你可以使用这条命令:

rmdir ../history

毕竟,history 目录并不位于你和根目录 之间。如果你想移除 essays,必须先 向靠近根目录的方向移动,比如移到 /home/harley。现在你可以移除这个目录:

cd /home/harley
rmdir essays/history essays/literature essays

问:你的工作目录是 /etc。你能删除位于 主目录之内的一个子目录吗?

答:能,因为你的工作目录(/etc)并不 位于根目录与你想要删除的目录之间。

要记住这条规则,只要回想我们与真实树木的 类比即可。树干是根目录。每根树枝是一个 子目录。在任何时刻,你都坐在某一根树枝上, 那就是你的工作目录。删除目录就像锯掉树上的 一根树枝。删除目录的这条限制只是 说:你不能锯掉正托着你所坐树枝的那根树枝。

— 提示 —

删除你的工作目录是可能的。这就像锯掉你 正坐着的那根树枝。也许 Unix 本不该让你这么 做,但它确实允许。

删除你的工作目录只会给你找麻烦。别这么 干。(*)

* 脚注

虽然我告诉过你不要这么做,但我知道你一定会 为了看看会发生什么而试一试。真要试的时候, 请务必用一个临时子目录。不要删除你的 主目录,否则你真的会有麻烦。

移动或重命名目录:mv
(Moving or Renaming a Directory: mv)

要移动或重命名一个目录,使用 mv 程序。语法是:

mv directory target

其中 directory 是你想要移动或重命名的目录, target 是目标位置或新名字。

你用 mv 程序把一个目录从一处"移动"到 另一处。如果新位置在同一个目录之内,那么 实际上你就是给原目录重命名了。这就是我 为什么说 mv 既能移动也能重命名。

让我给你看几个例子。你的工作目录里有一个 名为 data 的目录,你想把它的名字改成 extra。假设同一个目录中尚不存在名为 extra 的目录,你可以使用命令:

mv data extra

原本名为 data 的目录,现在 名为 extra

如果目标目录确实存在,mv 会把原目录 移动到目标目录里面。例如,假设你有下面 两个目录:

/home/harley/data
/home/harley/storage

你想把 data 目录移动到 storage 目录中。使用:

mv /home/harley/data /home/harley/storage

当然,如果你的工作目录是 /home/harley,你可以把命令简化成:

mv data storage

此时 data 目录的路径名是:

/home/harley/storage/data

mv 移动一个目录时,它同时会移动 该目录之内的所有文件和子目录。例如,假设在 移动之前,data 目录里有一个名为 document 的文件。它的绝对路径名是:

/home/harley/data/document

移动之后,绝对路径名变成:

/home/harley/storage/data/document

如果 data 之下还有子目录 — 甚至可能是一整棵子树 — 它们也会被 一起移动。因此,你可以把 mv 程序用于 三个目的:

  1. 给目录重命名。
  2. 移动目录。
  3. 移动整棵目录树。

mv 程序既可以用来移动或重命名普通 文件,也可以用于目录。我们会在第 25 章 讨论怎么做。

使用目录栈:pushd, popd, dirs
(Using the Directory Stack: pushd, popd, dirs)

在第 13 章,我解释过 Unix 命令有两种 类型。外部命令是独立的程序。内置(或内部) 命令由 Shell 直接解释执行,只有当你的 Shell 支持它们时才可用。在本节中,我要向你展示 如何使用三条内置命令:pushdpopddirs。Bash、Tcsh 和 C-Shell 提供了这些命令,但 Korn Shell 没有。

到这里,我们已经覆盖了处理目录所需的 基本操作。你知道如何创建、删除、移动和 重命名。你还知道如何更改工作目录并显示它的 名字。尚未讲的是那个极为重要的 ls 程序的种种变化形式,它让我们能够 查看目录内部、看看里面有什么。不过在转到 ls 之前,我想花一点时间介绍一种高级 技巧,它可以帮助你在树中从一个目录 移动到另一个目录。

在第 8 章,我们谈到了数据结构的 概念:按照一组精确的规则来存储和取回数据的 实体。到目前为止,我们讨论过三种不同的 数据结构:栈(第 8 章)、队列 (第 23 章)和树(第 9 章和第 23 章)。 我们马上要再次用到栈,所以先快速复习一下。

栈(STACK)是一种数据结构,其中的元素 一次一个地存储和取回,因而在任何时刻,下一个 被取回的数据元素正是最后一个被存储的元素。 这种安排有时称为 LIFO,即"后进先出"。 当你存储一个数据元素时,我们说你把它 压入(PUSH)栈。最近压入的数据元素据说位于 栈顶(TOP)。当你从栈顶取回一个数据元素时, 我们说你把该元素弹出(POP)栈。非正式地, 你可以把栈想象成自助餐厅里那种带弹簧的 盘子柱。盘子一个一个地被压入这个 "栈"。当你想要一个盘子时,你把最上面那个 从栈上取下来。你无法拿到下面的任何一个 盘子。

Shell 提供了一个类似的机制来保存目录 名字。在任何时候,你都可以用 pushd 命令把一个目录的名字压入目录栈 (DIRECTORY STACK)。之后,你可以用 popd 命令把一个名字弹出栈。在任何 时候,你都可以用 dirs 命令显示栈的 内容。这些命令的语法如下:

pushd [directory | +n]
popd [+n]
dirs [-c] [-l] [-v]

其中 directory 是某个目录的名字, n 是一个标识号。注意:给 dirs 使用选项时,你必须把它们分开写。例如,你可以写 dirs -l -v,但不能写 dirs -lv

在本节中,我们会讲这三条命令最重要的 几种用法。还有一些比较冷门的用法,你可以 在联机手册中查阅。(去找介绍你所用 Shell 内置命令的那一页手册。)作为参考, Figure 24-3 概括了我们要讲的这些 命令。

Figure 24-3: 目录栈命令

目录栈是一种高级工具,它让你维护一份目录 清单,并且随时可以把工作目录更改为清单中的 某个目录。

在任何时刻,栈顶的名字就是你的 工作目录。更改这个名字会自动更改你的 工作目录。同样,更改工作目录也会自动更改 栈顶的名字。你通过把名字压入栈、从栈中 弹出名字,或者选定某个名字移到栈顶来 控制这个栈。每种操作都会改变栈顶, 从而改变你的工作目录。细节见正文。

命令 作用
dirs显示名字:主目录显示为 ~
dirs -l显示名字:主目录显示为完整路径名
dirs -v显示名字:每行一个,并带数字标识号
pushd directory更改工作目录:把 directory 压入栈
pushd +n更改工作目录:把第 n 号目录移到栈顶
popd更改工作目录:弹出栈顶
popd +n从栈中删除第 n 号目录
dirs -c删除栈中除工作目录以外的所有名字

学会使用目录栈需要一点练习,但值得 下功夫。一旦你掌握了其中的细节,你就能 像在摇滚演唱会上拿着后台通行证的大人物 一样,在文件系统中穿行自如。诀窍是记住 一条简单的规则:

在任何时刻,栈顶保存的都是你工作目录的 名字。

每当你更改工作目录,栈顶就会自动随之 改变。反过来,每当栈顶改变,你的工作目录 也会自动改变。(在继续往下读之前,先想 一会儿。)

这里举几个例子。先用 cd 切换到 /etc 目录。用 pwd 确认 更改:

cd /etc; pwd

现在显示栈的内容。为此,使用 dirs 命令并加上 -v(verbose,详细) 选项。这个选项告诉 dirs 把栈上的每个 元素各占一行显示出来,并带行号。栈顶是 第 #0 行。

dirs -v

输出为:

0  /etc

切换到 /usr 目录,再次显示栈:

cd /usr; dirs -v

注意,栈顶已经改变,指向了新的 工作目录:

0  /usr

现在,用 pushd 把三个新的目录名压入 栈。你必须分成三条命令来做。然后用 dirs 显示栈:

pushd /lib
pushd /var
pushd /etc
dirs -v

输出为:

0  /etc
1  /var
2  /lib
3  /usr

现在栈中含有四个目录名。接着显示你的 工作目录:

pwd

输出为:

/etc

注意,你并不需要显式地更改你的 工作目录。每当栈顶(#0)发生变化,你的工作 目录就会自动改变。(*)

* 脚注

你也许听说过传奇魔术师 Harry Houdini (1874—1926)。Houdini 常常表演一门神乎其神 的读心术:不用 pwd 命令就能猜出 某个人的工作目录。秘诀是什么?趁没人注意的 时候,Houdini 会用 dirs 命令偷偷 瞄一眼栈顶。

接下来,用 popd 从栈中弹出一个名字。 然后显示栈和你的工作目录:

popd
dirs -v
pwd

dirs 命令的输出是:

0  /var
1  /lib
2  /usr

pwd 命令的输出是:

/var

popd 命令把 /etc 弹出了 栈,于是 /var 来到了栈顶。就在这一瞬间, /var 成了你的工作目录。我们用 pwd 证实了这一点。

回头看一遍语法,你会发现 dirs 有好几个 选项可用。不带任何选项时,dirs 会以 紧凑格式显示目录栈,所有名字都在同一行上。 如果其中某个名字涉及你的主目录,dirs 会用 ~(波浪号)字符来表示它。带上 -l(long,长格式)选项时,dirs 会显示主目录的完整名字。最后,带上 -v(verbose,详细)选项时,dirs 会每行显示一个名字,并带行号。想动手试验的话, 先把你的主目录压入栈,然后依次试试每种 变化形式:

pushd ~
dirs
dirs -l
dirs -v
dirs -l -v

dirs 命令还有一个选项,但它与显示名字 无关。-c(clear,清除)选项会清空 栈。当你想清空栈从头开始时,就用这个选项。 想试验的话,先用 dirs -c(清空栈), 接着用 dirs -v(显示栈)。在你输入 这两条命令之前,看看能不能回答这个问题:第二条 命令会显示一个空栈吗?

dirs -c
dirs -v

答案是你永远看不到一个完全空的 栈。这是因为栈顶保存的是你工作目录的 名字。既然你总是有一个工作目录,目录栈上 就至少永远有一个名字。

讲到这儿,我能想象你心里在想:这些内容 挺有意思(或者挺无聊,取决于你的立场), 可它有什么用?我仿佛听到你在说,我到底会 有多少次需要把目录名一个一个压入栈、再一个 一个弹出,就只是为了更改工作目录?为什么不 干脆用 cd?

如果你正是这么想的,那你说得没错。多数 时候,只有真正的极客才会用目录栈。(*) 确实,如果你想要的只是在两个目录之间来回 切换,你可以用 cd -(本章前面讲过)。 你为什么要去学这些更冷僻的命令,花时间跟一个 栈折腾呢?

* 脚注

如果你恰好是那种经常使用目录栈的人,你 应该知道,我这里用"极客"这个词,是用它 最友善的含义。

我之所以教你这些,是因为栈有一个方面 极为有用:你可以用 pushd 命令跳到栈的 中间,把某个目录名"压"到栈顶。你一这么 做,工作目录就随之改变。

听起来复杂,其实不然:它快捷、简单,而且 非常强大。这里举个例子展示它的用法。先输入 下面这些命令:

cd
dirs -c
pushd /lib
pushd /var
pushd /etc
dirs -v

cd 命令切换到你的主目录。 dirs -c 命令清空栈。此时,除了你的 工作目录(即 ~,你的主目录)之外,栈是空 的。接下来的三条 pushd 命令把目录名压入 栈。最后一条 dirs 命令显示栈的内容。 这条命令的输出是:

0  /etc
1  /var
2  /lib
3  ~

现在栈上有四个名字,而你的工作目录就是最 上面那个,/etc(#0)。假设你已经在 /etc 里工作了一段时间,现在想换到 /lib(#2)。只需输入 pushd,后面 跟一个 +(加号)字符和数字 2:

pushd +2

这是告诉 Shell 把 #2 号目录(/lib)移到 栈顶(#0)。就在 /lib 成为 #0 的那一 瞬间,它同时也成了你的工作目录。最终效果是: 从栈中间挑出 #2 号目录,让它成为你的 工作目录。此时,如果你用 dirs -v 显示目录栈,它会看起来像这样:

0  /lib
1  ~
2  /etc
3  /var

这是怎么发生的?当你把一个目录压到栈顶时, 原本在它上面的那些目录并不会丢失。它们只是 沿着栈往下移。在本例中,当你把 #2 号目录移到 栈顶时,#0 和 #1 号目录就向下旋转到了 靠近栈底的位置。

我得承认,这个例子是刻意编出来的。毕竟, 输入一堆命令搭起一个目录栈、显示栈的内容, 然后再把名字压到栈顶,而这些事本来只要敲一条 简单的命令就能做到,比如:

cd /lib

然而,如果这些目录的名字更长呢?例如, 假设你正在主目录 /home/harley 中。 现在你输入下面这几条 pushd 命令, 把四个很长的名字压入目录栈:

pushd /home/harley/source/current/calculate/source/2-1.05
pushd /usr/include/linux/nfsd
pushd /home/harley/archive/calculate/source/1-0.31
pushd /usr/share/dict/

如果你要和这些目录打交道很多次,反复输入 这些名字实在是件烦人的事。相反,你可以把它们 一次性压入栈。之后,随时想切换到哪个名字, 就把它压到栈顶。例如,假设你正在 dict 目录中工作。过了一会儿,你想换到 nfsd 目录。先显示栈,看看该把第几号压到栈顶:

dirs -l -v

输出是:

0  /usr/share/dict
1  /home/harley/archive/calculate/source/1-0.31
2  /usr/include/linux/nfsd
3  /home/harley/source/current/calculate/source/2-1.05
4  /home/harley

你要做的只是把 #2 号目录压到栈顶:

pushd +2

以后,当你需要切换到另一个目录时,再显示一次 目录栈,然后把另一个名字压到栈顶。(每次都 显示栈是很重要的,因为每当你执行压栈操作, 编号都会变化。)

从目录栈中删除和添加都很容易。要从栈中删除一个 名字,用 popd 后跟编号。要往栈中添加一个 名字,按前面说过的用 pushd。例如,要从栈中 移走 #2 号名字,使用:

popd +2

要把 /home/weedly/bin 压入栈,使用:

pushd /home/weedly/bin

有一点值得琢磨:在第 13 章讨论历史列表 时,我向你展示过如何列出一串命令,然后 用事件编号来引用其中某一条命令。你能看出 它与目录栈的相似之处吗?你列出一串目录, 然后用编号来引用其中某一个目录。(这种 相似并非巧合。)

为了结束本节,让我再给你看一样非常酷的 东西。为了让目录栈用起来更顺手,你可以为 dirs -vpushd 创建 别名(见第 13 章)。下面这几条命令就能在 Bash 中完成这件事。(记住,Korn Shell 不支持 目录栈。)

alias d='dirs -v'
alias p=pushd

对于 C-Shell 家族,你会写成:

alias d 'dirs -v'
alias p pushd

一旦定义了这些别名,使用目录栈就很简单了。 要显示栈,只需输入:

d

要通过把一个新名字压入栈来更改工作目录, 使用类似这样的命令:

p /usr/lib

要通过把栈中已有的某个名字压到栈顶来更改 工作目录,使用类似这样的命令:

p +4

如果你现在有空,就把这些别名输入进去, 稍微试验一下。在你输入目录名时,一定要 使用自动补全(见第 13 章),尽量少 打字。如果你想让这些别名永久生效,把它们 放进你的环境文件即可(第 14 章)。 作为参考,本节讨论过的命令概括在 Figure 24-3 中。

— 提示 —

如果你打算反反复复使用同一组目录,就把相应 的 pushd 命令写进你的登录文件 (见第 14 章)。这样,每次登录时,你的 目录栈都会自动替你建好。

最重要的程序:ls
(The Most Important Program of All: ls)

在所有 Unix 工具中,最重要的是 ls 程序(读作 "L-S"),它用来显示目录 内容的信息。为什么 ls 如此重要?要回答 这个问题,我们需要考虑 Unix 的根本特性。

如我在第 6 章所述,Unix 系统内的每个 对象要么是文件,要么是进程。简单地说,文件 保存数据或提供对资源的访问;进程是正在执行的 程序。当你使用 Unix 时,我们称你为用户。 然而,Unix 自己并不知道用户这回事;Unix 只 知道用户 ID(见第 4 章)。实际上,在 Unix 系统内部,只有用户 ID 才拥有真实的身份。 因此,登录、注销、拥有文件、运行程序、 发送邮件的,是用户 ID,而不是用户。

出于这个原因,每个 Unix 系统都有一个内部和 一个外部,两者之间有清晰的边界。内部由所有 文件和进程,以及栖居在这个幽幻环境中的用户 ID 组成。外部则是你,也就是用户。边界由物理 接口定义:你的键盘、鼠标、显示器、扬声器 等等。尽管整个操作的大脑在你(用户)身上, 但无法进入 Unix 环境。因此,你无法 直接感知内部存在什么、正在发生什么。

没错,你才是主宰,你的用户 ID 是你的正式 代表。然而说到底,你其实是在蒙眼下棋, 就像雾中的飞行员,只能依赖自己的仪表。 你看不见任何文件,也看不见任何进程。 你甚至连自己的用户 ID 都看不见。你能做的 最好事情,就是输入命令并解读输出。正因如此, 最重要的工具就是那些充当你的耳朵和眼睛的 工具,也就是显示文件和进程信息的程序。 这些工具帮你回答两个问题:"那里有什么?" 和"正在发生什么?"

在第 26 章,我会向你展示如何查看你 进程的状态。(我们要用的主要工具是 ps 程序。)然而,进程虽然重要,大多数 时候你只是让它们自己干活。你投入的绝大部分 精力,都在思考和操作文件上。由于文件居住 在目录中,那些能够让你查看目录内部的工具 就格外重要,而在这些工具中,最有用的 毫无疑问是 ls

这就是为什么,在每个 Unix 和 Linux 系统自带的 数百个命令行程序之中,ls 是最重要的 那个程序。

列出目录内容:ls -CrR1
(Listing the Contents of a Directory: ls -CrR1)

要显示目录内容的信息,你使用 ls(list files,列出文件)程序。你会发现 ls 是 使用最频繁的 Unix 程序之一。因此,它有很多 选项来控制输出。例如,在我的一台 Linux 系统上,ls 有 59 个选项。(这不是 印刷错误。)非 Linux 系统的选项会少一些, 但即便如此,你通常也会发现超过 30 个。

显然,没有任何程序真的需要 30 个选项, 更不用说 59 个。在我们的讨论中,我会教你 最重要的那些选项。想了解更多信息,你随时可以 查看联机手册(man ls)。本节我要 介绍 ls 程序并讨论基本选项。在随后的 各节中,我们会讨论 ls 更高级的特性, 那时我会介绍一些更复杂的选项。

只考虑最重要的选项,ls 程序的语法是:

ls [-aCdFglrRs1] [name...]

其中 name 是目录或文件的名字。

在继续之前,先看看这些选项,注意 -l(小写字母 "l")和 -1(数字 "1")。这是两个不同的 选项,别弄混了。-l(字母 l)选项 用得很多;-1(数字 1)选项很少用。

ls 的默认行为是按字母顺序列出目录中 文件的名字。例如,要列出 /bin 目录中的 文件,使用:

ls /bin

如果你想查看多个目录的内容,可以指定多个 名字。例如,要列出 /bin/etc 目录中的文件,使用:

ls /bin /etc

如果你不指定目录,ls 默认会显示你 工作目录中的文件。因此,要看工作目录中的 文件,只需输入:

ls

这两个字母的词,是 Unix 世界上最常用的 命令。

正如我们先前讨论的,.(点)字符是 工作目录的缩写。因此,下面两条命令是 等价的:

ls
ls .

更有用的是 .. 缩写,它代表 父目录。因此,要列出工作目录的父目录中的 文件,你会使用:

ls ..

正如你所料,你可以多次使用 .., 在树上向上任意多层。例如,要列出 工作目录的父目录的父目录中的文件,使用:

ls ../..

ls 把输出发送到终端(通常是 这样)时,输出会被组织成若干列。列数会 自动选定,以便名字在屏幕或窗口上排列 得当。例如,下面是 /bin 目录列表 输出的前七行。(在这台具体的系统上,实际 输出是 20 行。)

awk         dmesg       kill        ping        stty
bash        echo        ksh         ps          su
cat         ed          ln          pwd         tcsh
chmod       egrep       login       rm          touch
cp          env         ls          rmdir       umount
cut         ex          mkdir       sed         uname
date        false       more        sort        vi
.           .           .           .           .
.           .           .           .           .
.           .           .           .           .

注意,文件名是按列以字母顺序排列的。 也就是说,你要竖着读,而不是横着读。正如我 在第 23 章解释的,/bin 目录中包含 许多标准 Unix 程序,所以这个目录里的名字 应该看着眼熟。

当你把 ls 的输出重定向到文件或管道时, ls 每行只写一个文件名。这使得用另一个 程序处理 ls 的输出变得很容易。(重定向 和管道在第 15 章讲解。)一个常见的例子是:

ls | wc -l

wc -l 命令统计它收到的输入行数。 因此,lswc 的这个组合会告诉你 工作目录中有多少个文件。

如果由于某种原因,你想强制 ls 把列 写入文件或管道,使用 -C(大写 "C") 选项,例如:

ls -C | less

如果你想强制 ls 在终端上每个文件名 写一行(而不是分列),使用 1 选项 (数字 "1"):

ls -1

默认情况下,ls 按字母顺序显示文件名。 (更准确地说,ls 使用你所处区域的 排序序列中字符的顺序。见本章后面的讨论。) 如果你想按相反顺序显示名字,使用 -r(小写 "r")选项:

ls -r

本节我们要讨论的最后一个 ls 选项是 -R,它代表 "recursive"(递归,一会儿就解释)。这个选项 告诉 ls 列出你所指定目录之内 — 直接或间接 — 所有子目录和 文件的信息。换句话说,ls -R 显示的是整棵目录树的信息。

例如,假设你想查看系统上各用户创建的 所有文件和子目录。只需显示 /home 目录的全部后代:

ls -R /home

类似地,要列出工作目录的所有后代,你会 使用:

ls -R

这类列表往往非常长,所以你可能想把输出 通过管道交给 less,一次显示一屏。 由于输出要进管道,如果你想按列显示,就 必须加上 -C 选项:

ls -CR /home | less
ls -CR | less

当你想使用 -R 时,记住还有一个 -r(reverse,反向)选项,所以输入时 一定要小心。

名称的由来

Recursive(递归)


在计算机科学中,递归(RECURSIVE)数据结构是 由同类型的更小的数据结构搭建起来的结构。 目录树是递归的,因为它们包含着其他更小的 树。

某些目录工具(比如 ls)有一个选项可以 处理整棵目录树,也就是从某个指定目录向下延伸 的所有子目录和文件。因为这样的树被认为是 递归的,处理它们的选项通常命名为 -r-R

排序序列、区域设置与 ls
(Collating Sequences, Locales and ls)

本章前面我提到,ls 的默认行为是按 字母顺序列出目录中文件的名字。这话听起来 很直白,其实不然。这是因为"字母顺序"的定义 并非在所有系统上都一样。这一切取决于你的 排序序列,而排序序列又由你的区域设置决定。

如我们在第 19 章所讨论的,区域设置 (locale)是一种技术规格,描述与来自特定 文化的用户交流时应使用的语言和惯例。例如, 你的区域设置可能是美式英语、英式英语、 荷兰语、西班牙语等等。就我们的目的而言, 区域设置最重要的一方面,是它定义了你的 排序序列,也就是字符排序的顺序 (第 19 章有解释)。

你系统的默认区域设置是在系统安装时确定的。 如果你使用美式英语,你的区域设置要么是基于 ASCII 码的 C(POSIX)区域设置, 要么是 en_US 区域设置,它属于一套更 新的国际体系。要检查你的区域设置,可以使用 locale 命令。它会显示各个环境变量 的值。你要看的那个是 LC_COLLATE,它 指定了你排序序列的名字,因为正是你的排序 序列决定了你系统上"字母顺序"的含义。

C 区域使用与 ASCII 码相同的排序序列。 具体说,所有大写字母归在一起,所有小写字母 归在一起,大写在前:ABCDEF...abcdef... 我们称之为 C 排序序列,因为它与 C 编程 语言一起使用。

然而,en_US 区域使用词典排序序列, 其中大写与小写混在一起:aAbBcCdDeEfF...

当你用 ls 列出文件时,它们显示的顺序 取决于你的排序序列。例如,假设你有 6 个 文件,分别名为 AaBbCc。如果你的区域 设置是 C,你用 ls 列出这些文件, 会看到:

A B C a b c

如果你的区域设置是 en_US,你会看到:

a A b B c C

这可能看起来是小事一桩,其实不是。如果你使用 C 区域,日子会好过得多。本章后面讨论 通配符时,你会看到一个重要的例子。所以, 我要你做下面这件事。

现在花点时间输入 locale 命令。它会 显示定义你区域设置的那些环境变量。如果 LC_COLLATE 变量设置为 CPOSIX,那就很好。如果它设置为 en_US,我要你把它永久改为 C。 (我在我的系统上就是这么做的。)你要做的只是 把合适的命令加到你的登录文件里。第一条命令 用于 Bourne Shell 家族(Bash、Korn Shell); 第二条命令用于 C-Shell 家族(C-Shell、Tcsh):

export LC_COLLATE=C
setenv LC_COLLATE C

关于区域设置和排序序列的详细信息,见 第 19 章;关于登录文件的讨论,见 第 14 章。

辨别文件类型(一):ls -F
(Checking File Types, Part I: ls -F)

你常常想知道一个目录里有哪些类型的文件。 这种情况下你有三种选择。你可以用 ls-F 选项;可以用 ls--color 选项(仅限 Linux);还可以使用 file 命令。在 接下来三节中,我们会依次讨论这三种技术。

当你使用带 -F(flag,标记)选项的 ls 时,它会在某些类型文件的名字后面 显示一个标记(FLAG)。这些标志概括在 Figure 24-4 中。最重要的是 / (斜杠),它表示目录;还有 *(星号), 它表示可执行文件(比如程序或脚本)。大多数 情况下不会带标记。这表示一个普通的、 不可执行的文件。

Figure 24-4: ls -F 命令显示的标记

当你使用带 -F 选项的 ls 时, 它会在某些类型的文件后面显示一个标记。 最重要的标志是 /(表示目录)和 *(表示可执行文件)。如果没有标记, 该文件就是普通的不可执行文件。

标记 含义
普通文件:不可执行
*普通文件:可执行
/目录
@符号链接(第 25 章讨论)
|命名管道/FIFO(第 23 章讨论)

例如,假设你的工作目录中包含一个名为 documents 的目录、名为 memoessay 的文本文件、 一个名为 spacewar 的程序(二进制文件), 以及一个命名管道 tunnel。要显示带 标记的文件名,你会使用:

ls -F

输出为:

documents/  essay  memo  spacewar*  tunnel|

辨别文件类型(二):ls --color
(Checking File Types, Part II: ls --color)

如果你使用 Linux,除了 -F 还有另一种 选择。你可以使用 --color 选项,用颜色来 表示各种文件类型。(*) (我们在第 10 章 讨论过以 -- 开头的选项。)语法如下:

ls --color[=always|=auto|=never] [name...]

其中 name 是目录或文件的名字。

* 脚注

在使用 FreeBSD 的系统上(包括 OS X (Macintosh)),你可以类似地使用 -G 选项。

--color 打开时,ls 会用颜色 来表示各种类型的文件。例如,下面这条命令 显示工作目录中各文件的名字:

ls --color

使用 --color 选项时,有三种变化形式。 第一种是 --color= always,这也是默认值。如果你愿意,也可以用 yesforce。因此,下面四条 命令是等价的。它们都告诉 ls 用颜色 来表示各种类型的文件。

ls --color
ls --color=always
ls --color=yes
ls --color=force

第二种变化形式是 --color=never。这告诉 ls 不要使用颜色。如果由于某种原因颜色 开着而你想把它关掉,你就会用到它。如果你 愿意,也可以用 nonone。因此, 下面三条命令都是告诉 ls 不要使用颜色:

ls --color=never
ls --color=no
ls --color=none

讲到这儿,你大概在想,为什么要为一个本质上 只是"是"或"否"的选择提供这么多写法?答案是, 那些给 ls 添加颜色支持的程序员认为, 用户应该既可以指定 neveralways,也可以指定 yesno。另外两个值 forcenone 则是为了与其他版本的 ls 保持兼容而加入的。(*)

* 脚注

在第 2 章,我解释过 Linux 是开放 源代码软件,这意味着任何人都可以查看(甚至 修改)源代码。恰好,--color 选项的 各种变化形式的文档写得很不充分。不过, 我通过阅读 ls 程序的源代码弄清了 其中的细微差别。

如果你曾经为了搞清一个程序如何工作而 一筹莫展,而文档又不足或令人困惑,请记住: 没有什么魔法。哪怕你只懂一点 C,也可以试试 阅读源代码。要抓住要旨并不那么难,而阅读 别人的代码是提高自己的编程水平的最好 途径之一。

通常情况下,产生颜色的特殊代码会混杂在输出 之中。当输出显示在屏幕上时这没什么问题,但当 你把输出送进管道或存进文件时,它看起来就 像一堆乱码。为了避免这一点,你可以使用 --color 选项的最后一种变化形式,把它 设为 auto。这告诉 ls 只在输出 送往终端时才使用颜色。如果你愿意,也可以用 ttyif-tty。因此,下面三条 命令是等价的:

ls --color=auto
ls --color=tty
ls --color=if-tty

要亲自看看这一点,试试下面两条命令。第一条 命令强制使用颜色,这会产生一些特殊代码, 在 less 中查看时就像乱码。第二条命令 检测到输出不是送往终端,因此不生成颜色代码, 避开了 less 的那个问题。

ls --color=yes /bin | less
ls --color=auto /bin | less

类似地,如果颜色是开着的,当你把输出保存 到文件时,你会想把它关掉:

ls --color=auto > filelist

很多人都喜欢每次用 ls 时显示颜色。 确实,这种做法太普遍了,以至于在某些系统上, 会自动为 ls 创建一个带 --color 选项打开的别名。如果你是 Linux 用户,而且每次 使用 ls 都能看到颜色,即使你没有指定 --color,那很可能是你在不知不觉中使用 了别名。

要检查是不是这样,你可以告诉 Shell 忽略任何 别名:在命令之前输入一个 \(反斜杠) (见第 13 章)。如果输出现在毫无颜色, 你就可以断定自己先前用的是别名。

\ls

如果你想永久关掉颜色,只需自己创建一个别名 作为替代,例如:

alias ls='ls --color=no'
alias ls 'ls --color=no'

第一个别名用于 Bourne Shell 家族:Bash、 Korn Shell;第二个用于 C-Shell 家族:Tcsh、 C-Shell。要让别名永久生效,把它放进你的 环境文件。(有关别名的帮助见第 13 章; 关于环境文件的介绍见第 14 章。)

如果你想永久打开颜色,那么在环境文件中改用 下面某个别名:

alias ls='ls --color=yes'
alias ls 'ls --color=yes'

就个人而言,我更喜欢标记而不是颜色,所以我 建议你把 -F--color 一起用:

alias ls='ls -F --color=yes'
alias ls 'ls -F --color=yes'

— 提示 —

当你使用带 --color 选项的 ls 时,会用若干种不同的颜色来表示 不同类型的文件。这些颜色由一个名为 LS_COLORS 的环境变量设定。你可以通过 修改这个变量来自定义颜色。如果这听起来挺有 意思,不妨先读一读 dircolors 程序的介绍:

man dircolors
info dircolors

思路是用 dircolors 生成一条命令, 把 LS_COLORS 设成你想要的样子。然后 你可以把这条命令放进你的环境文件。

辨别文件类型(三):file
(Checking File Types, Part III: file)

到目前为止,我们讨论了两种了解目录中文件 类型的方法。你可以用 ls-F 在文件名后显示标记,也可以用 ls--color 用颜色表示不同 的文件类型(或者两者都用)。一种精密得多的 检查文件类型的方法是使用 file 命令, 它了解数千种不同的文件类型。语法是:

file [name...]

其中 name 是文件或目录的名字。它有很 多选项,但你不一定用得上。(如果你好奇, 请看手册页。)

使用 file 很直接。只需指定一个或多个 文件或目录的名字,例如:

file /etc/passwd /bin / ~/elmo.c /bin/ls

下面是一些典型输出:

/etc/passwd:         ASCII text
/bin:                directory
/:                   directory
/home/harley/elmo.c: ASCII C program text
/bin/ls:             ELF 32-bit LSB executable, Intel 80386
                     version 1 (SYSV), for GNU/Linux 2.6.9,
                     dynamically linked (uses shared libs),
                     stripped

前四个文件的输出很容易理解。第一个文件 (口令文件)包含纯 ASCII 文本。第二个和第三个 文件是目录。第四个文件包含 C 源代码。最后 一个文件是可执行程序。因此,file 给出 了大量技术信息,这对程序员和系统管理员很 有用。如果你感兴趣,下面解释这些内容的 含义。

ELF:Executable and Linking Format(可执行与 链接格式),一种可执行文件的标准文件格式。

32-bit:字长。

LSB:按最低有效字节(Least Significant Byte)的字序编译,用于 x86 处理器。

executable:一个可执行文件。

Intel 80386:该文件编译时所针对的处理器 架构。

version 1 (SYSV):内部文件格式的版本。

GNU/Linux 2.6.9:编译该程序时所在的 操作系统和内核版本。

dynamically linked (uses shared libs):使用 共享库,不是静态链接。

stripped:该可执行文件已去掉符号表。这是由 strip 程序完成的,目的是减小可执行文件的 体积。

在我们的例子里,有两个目录,/bin/(根目录)。注意,file 给出的 是关于目录本身的信息。如果你想让 file 分析目录之内的文件,你就必须 指定它们的名字。要指定目录中的所有文件, 你要使用所谓的"通配符"。我们会在本章后面 讨论通配符。现在,先给你一个例子。下面这条 命令用 file 分析 /etc 目录中的 所有文件。由于输出相当长,我们把它通过管道 交给 less,一次显示一屏:

file /etc/* | less

跟踪你的磁盘空间使用情况:
ls -hs, du, df, quota
(Keeping Track of Your Disk Space Usage: ls -hs, du, df, quota)

有三个程序可以用来了解你的文件占用了多少 磁盘空间:ls -sduquota。我们逐个讨论。

第一个程序是带 -s(size,大小)选项的 ls。这告诉 ls 在每个文件名前面 加上以千字节为单位的大小。如果你指定一个目录 名,ls 还会显示整个目录的总量。这里 举个例子:

ls -s /bin

下面是这条命令输出的部分。(实际输出是 21 行。)

total 8176
  4 awk      12 dmesg    16 kill     40 ping     48 stty
712 bash     28 echo   1156 ksh      88 ps       32 su
 28 cat      60 ed       35 ln       28 pwd     352 tcsh
 44 chmod     4 egrep    32 login    48 rm       48 touch
 76 cp       24 env     100 ls       24 rmdir    72 umount
 40 cut       4 ex       36 mkdir    60 sed      24 uname
 54 date     24 false    36 more     64 sort    592 vi
    .           .           .           .           .
    .           .           .           .           .
    .           .           .           .           .

从第一行可以看到,目录中所有文件占用的总空间 是 8,176 千字节。其他各行显示各文件需要多少 空间。例如 cat 文件用了 28 千字节。 在 Linux 上,你可以用 -h(human-readable, 人性化)选项,在数字旁边显示单位。例如:

ls -sh /bin/cat

输出为:

28K /bin/cat

下一个可用来显示文件大小的程序是 du(disk usage,磁盘用量)。语法是:

du [-achs] [name...]

其中 name 是目录或文件的名字。

当你指定一个或多个文件的名字时,du 会显示这些文件使用的存储空间。这里是一个 显示口令文件大小的例子(口令文件见 第 11 章):

du /etc/passwd

在多数系统上,输出以 1K 为单位显示。例如, 下面的输出告诉你,口令文件占用了 8K 字节的 磁盘空间:

8       /etc/passwd

要在数字旁边显示单位,使用 -h(人性化)选项:

du -h /etc/passwd

这把输出改变为:

8.0K    /etc/passwd

你也许在想,口令文件——通常是个很小的 文件——为什么要用掉 8K 磁盘空间。毕竟, 8K 可以存放多达 8,192(8 x 1024)个字符, 远远超过口令文件里的数据量。情况是,就这个 特定的文件系统而言,磁盘上的存储空间是按 8K 一块来分配的。因此,尽管这个文件很小, 它在磁盘上仍占据 8K。(见本章后面关于 分配单元的讨论。)

如我所说,多数版本的 du 以 1K 为单位 显示输出。但有些系统使用 512 字节为单位。 (单位大小在 du 的手册页上有说明。) Solaris 就是如此。在这类系统上,通常会有 一个 -k 选项,强制 du 使用 1K 为单位。例如,在 Solaris 系统上,你可以用 下面两条命令中的任意一条,以 1K 为单位显示 口令文件占用的磁盘空间:

du -k /etc/passwd
du -hk /etc/passwd

到目前为止,我们用 du 显示特定文件 占用的磁盘空间。不过,du 最常见的 用法是查看一棵目录树中所有文件占用了多少 空间。如果你不指定名字,du 会假定你要 的是工作目录。例如,下面这条命令从你的 工作目录出发,显示每个子目录、子子目录、 子子子目录,依此类推的名字。在每个名字旁边, du 显示该目录中文件占用的磁盘空间总量。 最后是一个总计。(由于输出很长,我把通过 管道交给 less,一次显示一屏。)

du -h | less

要看看你的所有文件占用了多少磁盘空间, 指定你的工作目录:

du -h ~ | less

下面这条命令显示 /usr/bin 之下的 所有文件和 /etc 之下的所有文件 占用了多少磁盘空间:

du -h /usr/bin /etc | less

如果你使用 -s(sum,总计)选项, du 只会显示总量,这就去掉了大量 无关的输出。在我看来,这是 du 最有用的 用法。这里举两个例子。第一个例子显示你的 个人文件(从主目录开始)占用的磁盘空间总量:

du -hs ~

第二个例子对 /usr/bin/bin/etc 目录做同样的事。第一条命令用于 Bourne Shell 家族(Bash、Korn Shell)。第二条 命令用于 C-Shell 家族(Tcsh、C-Shell)。

du -hs /usr/bin /bin /etc 2> /dev/null
(du -hs /usr/bin /bin /etc > /dev/tty) >& /dev/null

这些命令有点复杂,所以花点时间讨论一下。 你会注意到,我把标准错误丢进了位桶 (见第 15 章)。我之所以这么做,是因为 du 在处理目录时,可能会遇到它无权 读取的子目录。每发生一次,du 就会显示 一条错误消息。丢弃标准错误可以防止这些 消息显示出来。

重定向标准错误的具体做法取决于你使用的 是哪个 Shell。因此,一条命令用于 Bourne Shell 家族,另一条用于 C-Shell 家族。(全部细节在 第 15 章讲解。)

接着说,-c(count,计数)选项会在输出 末尾显示一个总计。这个选项与 -s-h 组合起来最有用。这里举个例子。 同样,第一条命令用于 Bourne Shell 家族; 第二条用于 C-Shell 家族:

du -csh /usr/bin /bin /etc 2> /dev/null
(du -csh /usr/bin /bin /etc > /dev/tty) >& /dev/null

这组选项(-csh)特别好记,因为 碰巧它正是 C-Shell 程序的名字。

最后,如果你使用 -a(all,全部)选项, du 会显示它所处理的每个目录和文件的 大小。这会产生非常长的列表,但它能精确地 告诉你磁盘空间是如何被使用的。例如,要显示 关于你个人文件磁盘存储的全部信息,指定你的 主目录:

du -ah ~ | less

下一个磁盘存储程序是 df。(这个名字 代表 "disk free-space",磁盘剩余空间。)这个 程序显示每个文件系统用了多少磁盘空间、还有 多少可用。df 程序有各种选项,但一般 你不需要。我要提到的唯一选项是 -h,它 通过用千字节、兆字节和吉字节而不是块作为 存储单位,显示人性化的输出。在你的系统上 把这几条命令都试一遍,看看你更喜欢哪个:

df
df -h

下面是第一条命令在典型 Linux 系统上的输出。 在这个例子中,根文件系统(/)——它 几乎包含系统上的全部数据——只用了它被分配 空间的 9%。一个较小的文件系统 /boot 用了它空间的 16%。

Filesystem   1K-blocks     Used  Available  Use%
/dev/hda1     36947496   312446   31915940    9% /
/dev/hdd1        99043    14385      79544   16% /boot
tmpfs           192816        0     192816    0% /dev/shm

便于比较,这里是第二条命令的输出:

Filesystem        Size     Used  Available  Use%
/dev/hda1          36G     3.0G       31G     9% /
/dev/hdd1          97M      15M       78M    16% /boot
tmpfs             189M        0      189M     0% /dev/shm

最后一个磁盘存储程序是 quota。如果你 与人共用一台 Unix 或 Linux 系统,很可能你的 系统管理员规定了每个用户 ID 允许使用的 磁盘空间配额。如果你超出了配额,在你删除 一些文件之前,将不允许再使用任何磁盘文件 空间。

如果你的系统设了这样的配额,你可以用 quota 程序查看你的用量和限额:

quota

要显示额外信息,使用 -v(详细) 选项:

quota -v

注意:ls -sdudfquota 这三个程序 估算存储使用量的方式各不相同,所以如果数字 略有差异,不要惊讶。

— 提示 —

如果你使用的是共享系统,务必记住你是在 共享。不时用 du 看看自己占用了 多少磁盘空间。如果你有不需要的文件,特别是 大文件,请体谅他人,把它们删除。

别把这想成是被迫在配额内生活。把它想成 做一个好邻居。

文件有多大?块与分配单元:dumpe2fs
(How Big Is a File? Blocks and Allocation Units: dumpe2fs)

磁盘容量是用千字节、兆字节和吉字节来 衡量的。一个千字节(1K)是 1,024 (210)字节;一个兆字节是 1,048,576 (220)字节;一个吉字节是 1,073,741,824 (230)字节。在一个文本文件中,一个字节 存放一个字符。例如,100 个字符需要 100 字节的磁盘空间。

我们前面已经讨论过如何用 ls -sdu 来显示一个文件占用的磁盘 空间。在继续往下讲之前,有一个重要的道理 我希望你明白。一个文件占用的磁盘空间,并 不等于文件中的数据量。原因如下。

在文件系统内部,空间是按固定大小的整数 块来分配的,这些块称为块(BLOCK),依文件 系统不同,它们的大小可以是 512 字节、1K、 2K 或 4K。为一个文件能够分配的最小空间就 是一个块。我们来考虑一个使用 1K(1024 字节)块的文件系统。(这在 Linux 上很 典型。)一个只包含 1 字节数据的文件需要 一个完整的块。如果这个文件增大到比一个块 多出 1 字节,它就需要第二个块。因此,一个 最多包含 1024 字节数据的文件需要 1 个块。一个包含 1025 字节数据的文件需要 2 个块。

问:一个 1,000,000 字节的文件需要多少个 块?

答:假设块的大小是 1024 字节,1,000,000 除以 1024 略小于 976.6。因此,一个 1,000,000 字节的文件要占 977 个块。(这相当于 1,000,448 字节。)

到目前为止,我们谈的都是数据在文件系统 内部如何组织。可是,当文件被写入磁盘或其 他存储介质时又会怎样呢?出于效率的考虑, 磁盘空间也是按固定大小的整数块来分配的, 这些块称为分配单元(ALLOCATION UNIT)或 簇(CLUSTER)。分配单元的大小取决于文件系 统和存储设备。例如,在我的一台 Linux 系统上,块的大小是 1K。然而,磁盘的分配 单元却是 8K。因此,一个只需要 1 个字节的 文件实际上会占用 8K 的磁盘空间。

问:一个文件包含 8,500 字节的数据。它需要 多少个块?它会占用多少磁盘空间?

答:这个文件包含 8500/1024 = 8.3K 字节的数据。假设块的大小是 1K,这个文件 需要 9 个块。假设磁盘空间是按 8K 的分配 单元来分配的,这个文件会占用 2 个分配 单元,也就是 16K 字节的磁盘空间。

你如何确定自己系统上块和分配单元的大小 呢?我们先说分配单元,因为方法更简单。我 们的策略是:创建一个极小的文件,然后看看 它在磁盘上占用了多少空间。这个空间就是一个 分配单元的大小。

第一步是创建一个很小的文件。下面这几条 命令就可以做到:

cat > temp
X
^D

首先,输入 cat 命令 (第 16 章),从键盘读取输入并把它 重定向到一个名为 temp 的文件。 注意:如果 temp 不存在,Shell 会替你 创建它;如果 temp 已经存在,Shell 会把它替换掉。

接下来,键入一行只含一个字符的内容。这里 我输入了字母 "X" 并按下 <Return>。 这些数据将被写入文件 temp

最后,按 ^D(Ctrl-D)发送 eof 信号来表示数据结束(见 第 7 章)。现在我们就有了一个极小的 文本文件,它由两个字符组成:一个 "X", 后面跟一个换行符。

输入 ls -l 命令(本章后面会 讲到),显示文件中的数据量:

ls -l temp

输出是:

-rw-rw-r-- 1 harley staff 2 Aug 10 11:45 temp

文件大小显示在日期之前。你可以看到,这个 文件包含 2 字节的数据。现在用 du 程序(本章前面讲过)看看这个文件占用了 多少磁盘空间:

du -h temp

这是输出:

8.0K    temp

你可以看到,在我们的示例中,这个样本文件 在磁盘上占了 8K 的存储空间,尽管它只包含 2 字节的数据。因此我们可以断定,这个系统 的分配单元是 8K。

为了给我们的实验收尾,用 rm 程序 (第 25 章)删除这个临时文件:

rm temp

要找出发文件系统的块大小可就费点劲了。 虽然有些文件程序(比如 df)会显示 一个 "block size"(块大小),但那并不是文件 系统的正式块大小:它只是该程序使用的方便 单位而已。确切找出权威块大小的方法取决于你 使用的是哪个操作系统。在 Linux 上,你用 dumpe2fs 程序;在 Solaris 上,你用 fstyp -v;在 FreeBSD 上,你用 dumpfs。我拿 Linux 举个例子,给你 看看它是怎么工作的。(如果你需要这些程序的 任何细节,请查阅在线手册。)

正如我上面所解释的,文件系统内所有的数据 都组织成块。其中有一个块称为超级块 (SUPERBLOCK),它是一个特殊的数据区,保存 着关于文件系统本身的关键信息。在 Linux 上,你可以用 dumpe2fs 程序查看超级 块的内容。特别地,它可以显示文件系统使用的 块大小。做法如下。

1. 找出代表该文件系统的特殊文件的名字, 例如 /dev/hda1。为此你可以使用 df 命令(本章前面讲过)。

2. 要运行 dumpe2fs,你必须是超级 用户。用 su 命令切换成超级用户(见 第 6 章)。

3. 输入 dumpe2fs 命令,后面跟上 特殊文件的名字。这条命令会显示超级块中的 大量数据。你要找的那个数字在包含字符串 "Block size" 的那一行上。所以你要做的 只是运行 dumpe2fs 命令,再用 grep 从输出中筛出 "Block size"。这里举个例子:

dumpe2fs /dev/hda1 | grep "Block size"

注意:如果 Shell 找不到 dumpe2fs 程序,你就必须给出完整的路径名。该程序 位于 /sbin 中:

/sbin/dumpe2fs /dev/hda1 | grep "Block size"

下面是一些示例输出。在这个例子里,你可以 看到文件系统的块大小是 1K(1024 字节):

Block size: 1024

如果你想花点时间看看超级块里的所有信息, 把 dumpe2fs 的输出通过管道交给 less:

dumpe2fs /dev/hda1 | less

看完之后,用 exit 命令注销超级 用户(见第 6 章)。

用通配符做路径名展开
(Globbing With Wildcards)

每当你输入一条把文件名作为参数的命令时, 都可以使用某些称为通配符(WILDCARD)的元 字符来指定多个文件名。你可能还记得 第 13 章讲过的:元字符就是 Shell 解释为具有特殊含义的任何字符。当你把通配符 用在文件名里面时,它们就具有特殊的含义。 这里举个例子。

假设你想列出工作目录中所有以字母 "h" 开头的文件名。你可以用:

ls h*

在这个例子中,*(星号)是一个元 字符,它匹配零个或多个字符组成的任意序列。

乍看起来,通配符和我们在第 20 章讨论 过的正则表达式元字符很像。实际上,通配符 更简单。而且,它们只用于一个目的:在你输入 命令时匹配一组文件名。图 24-5 给出了 基本的通配符及其含义。在继续往下学之前, 先花点时间把这张表和第 20 章里总结 正则表达式的那几张表比较一下。

Figure 24-5: 用于指定文件名的通配符一览

每当你输入一条把文件名作为参数的命令时, 都可以用通配符来匹配多个文件名。这张表展示了 各种通配符以及它们的用法。注意:当你输入一个 路径名时,你无法匹配 / 字符;必须 显式地把它键入。

符号 含义
*匹配零个或多个字符组成的任意序列
?匹配任意单个字符
[list]匹配 list(列表)中的任意字符
[^list]匹配不在 list(列表)中的任意字符
[!list](POSIX)匹配不在 list(列表)中的任意字符
{string1,string2...}匹配所指定的字符串中的一个

当你使用通配符时,Shell 会在运行命令之前 解释这个模式,并把它替换成合适的文件名。 例如,假设你输入:

ls h*

Shell 会把 h* 替换成你工作目录中所有 以字母 h 开头的文件名。然后 Shell 运行 ls 命令。比方说,假设你的工作 目录中包含以下六个文件:

a  data-old  data-new  harley  h1  h2  z

如果你输入上面那条命令,Shell 会把它变成:

ls h1 h2 harley

你当然可以在同一条命令中使用多个模式:

ls h* data*

在我们的例子中,Shell 会把这条命令改成:

ls h1 h2 harley data-old data-new

用通配符指定文件这件事,正式的叫法有好几种, 取决于你用的是哪个 Shell。在 Bash 中,它叫 路径名展开(PATHNAME EXPANSION);在 Korn Shell 中,它叫文件名生成(FILENAME GENERATION);在 C-Shell 和 Tcsh 中,它叫 文件名替换(FILENAME SUBSTITUTION)。当 Shell 实际执行替换时,这个过程叫作通配 (GLOBBING)。有时 glob 这个词还被当作动词 使用,比如说:"除非你设置了 noglob 变量,否则 C-Shell 会自动做通配。"

正如我所说,当通配符被 Shell 通配展开时, 在参数传给程序之前,通配符就被换成了真正的 文件名。如果你用的模式没有匹配到任何文件, Shell 会显示一条相应的消息。例如,假设你的 工作目录中包含上面列出的那些文件,而你输入了 下面这条命令:

ls v*

这条命令列出所有以字母 "v" 开头的文件。 由于没有这样的文件,ls 显示了一条 错误消息:

ls: v*: No such file or directory

现在你已经理解了主要的概念,我们来逐个详细 讲讲这些通配符。最重要的通配符 * (星号)匹配零个或多个字符。* 通配符 可以匹配除 /(斜杠)以外的任何字符, 而你知道,斜杠在路径名中是用作分隔符的。 (如果你想指定一个 /,必须自己把它 键入。)例如,下面这些通配符写法所匹配的 模式如表所示:

Ha*以 "Ha" 开头的文件名
Ha*y以 "Ha" 开头并以 "y" 结尾的文件名
Ha*l*y以 "Ha" 开头、中间含有一个 "l" 并以 "y" 结尾的文件名

?(问号)通配符匹配除 / 以外的任意单个字符。例如:

d?以 "d" 开头的两个字符的文件名
??任意两个字符的文件名
?*y至少两个字符、以 "y" 结尾的文件名

你可以用 [](方括号) 把一个字符列表括起来,从而指定一个列表。 它表示所指定字符中任意一个的一次出现。 例如:

spacewar.[co]要么是 "spacewar.c",要么是 "spacewar.o"
[Hh]*以 "H" 或以 "h" 开头的文件名

要匹配不在列表中的任意字符,在列表的开头 放一个 ^(扬抑符)。(在 Tcsh 中, 你使用 ^ 作为替代。)例如,下面这条 命令显示你工作目录中所有不以字母 "H" 或 "h" 开头的文件名:

ls [^Hh]*

在 POSIX 中,你要用:

ls [!Hh]*

在方括号内,你可以用 -(连字符) 指定一个字符范围。例如,模式 [0-9] 匹配 0 到 9 中的任意一个数字。字母的范围 也是同样的道理,但你必须小心:字母的范围是 按照与你所在区域设置一起使用的排序序列来 展开的。(见本章前面的讨论。)考虑下面两个 例子:

[a-z]*以小写字母开头的文件名
[a-zA-Z]*[0-9]以大写或小写字母开头并以数字结尾的文件名

如果你使用的是 C 排序序列(C 区域设置),字母的顺序是 ABCDEF...abcdef...。因此, 上面的例子会像你期望的那样工作。然而,如果你 使用词典排序序列(en_US 区域设置), 这些例子就不能正常工作,因为字母的顺序是 aAbBcCdDeEfF...zZ

更具体地说,在 C 排序序列下,[a-z] 匹配任意一个小写字母,这正是你想要的。在词典 排序序列下,[a-z] 匹配除 "Z" 之外的任意大写或小写字母,完全不是你想要的。 类似地,在 C 排序序列下,[A-Z] 匹配任意一个大写字母。在词典排序序列下, [A-Z] 匹配除 "a" 之外的任意大写或 小写字母,这同样不是你想要的。

因此,我强烈建议你使用 C 排序序列,而不是 词典排序序列。要做到这一点,你必须确保 LC_COLLATE 环境变量设置为 C 而不是 en_US。(本章前面有操作说明。)

如果出于某种原因,你确实决定继续用词典排序 序列,那你就不能把 [a-z][A-Z] 当作通配符使用了。不过,有几 个预定义的字符类别可以用来替代它们。我把最 重要的几个列在图 24-6 中。关于预定义 字符类别的详细讨论,请看第 20 章。

Figure 24-6: 通配符:预定义的字符类别

通配符可以使用范围来匹配某个特定集合中的 任意字符。最常见的范围是 [a-z] (匹配一个小写字母)和 [A-Z](匹配 一个大写字母)。正如正文所解释的,这些范围 在 C 区域设置下有效,在 en_US 区域设置下却不行。作为一种 替代,你可以使用预定义的字符类别,其中最重要 的几个列在本表中。更多细节请看 第 20 章。

类别 含义 类似于……
[[:lower:]]小写字母[a-z]
[[:upper:]]大写字母[A-Z]
[[:digit:]]数字[0-9]
[[:alnum:]]大写和小写字母、数字[A-Za-z0-9]
[[:alpha:]]大写和小写字母[A-Za-z]

这里举个例子。你想显示最古老的那些 Unix 程序的名字。这些名字大多由两个小写字母 组成,比如 lsrm。寻找 这类程序最好的地方是 /bin/usr/bin(见第 23 章)。如果你 使用 C 区域设置,就可以用下面这条 命令。在你自己的系统上试试看,看看你能 找到什么。

ls /bin/[a-z][a-z] /usr/bin/[a-z][a-z]

如果你使用 en_US 区域设置,而恰好 有什么两个字符的名字包含一个大写字母, 你就会得到错误的结果。在这种情况下,正确 的命令应该是:

ls /bin/[[:lower:]][[:lower:]] /usr/bin/[[:lower:]][[:lower:]]

你可以看出我为什么总是推荐使用 C 排序序列。

到目前为止,我们讲的是文件名展开时使用的 三种通配符:* 匹配零个或多个字符, ? 匹配任意单个字符,[ ] 定义一个列表。我们还需要讨论的最后一种 通配符模式,允许你指定多个字符串,然后依次 匹配其中的每一个字符串。为此,你要用 {} (花括号)把一列以逗号 分隔的模式括起来。例如:

{harley,weedly}

重要:逗号的前后都不要加空格。

当你以这种方式使用花括号时,它告诉 Shell 依次用每个模式生成一个单独的文件名。我们 把这称为花括号展开(BRACE EXPANSION)。 花括号展开只在 Bash、Tcsh 和 C-Shell 中才有(Korn Shell 和 FreeBSD Shell 没有)。 处理一条命令时,花括号展开在文件名展开 之前进行。

这里举个例子。假设你想列出 /home/harley/home/weedly/home/tln 这三个目录中所有文件的 名字。你可以显式地指定全部三个目录名:

ls /home/harley /home/weedly /home/tln

用花括号展开,命令就简单了:

ls /home/{harley,weedly,tln}

再看第二个例子。你想把文件 olddata1olddata2olddata3newdata1newdata2newdata3 的内容合并起来,并把输出 保存到一个名为 master 的新文件中。 用下面任意一条命令都可以:

cat olddata1 olddata2 olddata3 newdata1 newdata2 newdata3 > master
cat {old,new}data{1,2,3} > master
cat {old,new}data[1-3] > master

(用于合并文件的 cat 程序在 第 16 章讨论。用于重定向标准输出的 > 字符在第 15 章讨论。)

花括号展开之所以重要,是因为它有两种用法。 第一,你已经看到了,它会匹配一组名字有共同 之处的文件。第二,当你创建新文件时,它还可以 用来描述那些并不存在的文件名。例如,假设你的 主目录中包含一个名为 work 的子目录。 下面两条 mkdir 命令都会在 work 目录中创建四个新的子目录。 注意,用花括号展开是多么方便:

mkdir ~/work/essays ~/work/photos ~/work/bin ~/work/music
mkdir ~/work/{essays,photos,bin,music}

最后一个例子。在第 25 章中,你将学会 如何用 touch 命令快速创建空文件。 假设你想创建下面这五个新文件:

dataold  datanew  databackup  datamaster  datafinal

使用花括号展开,命令是:

touch data{old,new,backup,master,final}

名称的由来

Wildcard(通配符), Globbing(通配展开)


"wildcard"(通配符)这个术语来自扑克牌以及 其他一些纸牌游戏,在这类游戏中,某些牌被指定 为"百搭"(wild)。在一场扑克牌局中,百搭牌 可以取各种各样的值。

"Globbing"(通配展开)指的是把带有通配符的 模式展开成一个文件名列表。"glob"这个术语 可以一直追溯到最早的那个 Unix Shell,甚至 早于 Bourne Shell(见第 11 章)。在那 时候,通配符展开是由一个独立的程序 (/etc/glob)完成的,由 Shell 调用它。 谁也不知道这个程序为什么要叫 glob, 所以你可以自己编一个理由。

在 Unix 圈子里,通配展开这个概念实在太常见 了,以至于极客们在日常交谈中也会用到它。例如, 假设一个极客给另一个极客发短信说:"《星际迷航》 你最喜欢哪一部,ST:TOS、ST:TNG 还是 ST:DS9?"第二个极客可能会回答:"ST:* 我一概不看。"

诸如此类,你有时会看到用 UN*X 这个名字来 代表任何一种 Unix 或 Linux。这可以追溯到 1970 年代,当时 AT&T 声称 UNIX 是一个注册 商标,未经他们许可谁也不能使用这个名字。例如, AT&T 的律师们说 UNIX 是形容词而不是名词, 因此绝不能提 "UNIX",只能说 "the UNIX operating system"(UNIX 操作系统)。面对 这种荒唐说法,许多 Unix 极客开始写 UN*X 来指代任何一种 Unix。

点文件(隐藏文件):ls -a
(Dot Files (Hidden Files): ls -a)

默认情况下,ls 程序不会显示任何以 .(点)字符开头的文件名。因此,如果你 用一个文件,而又不想每次使用 ls 的 时候都看见它,你只要给它起一个以点开头的 名字就行。正如我们在第 14 章讨论过的, 这类文件称为点文件(DOTFILE)或隐藏文件 (HIDDEN FILE)。大多数时候,点文件是程序 用来存放配置数据或初始化命令的。例如,所有 Shell 都使用点文件,vi/Vim 编辑器 也用(见图 24-7)。

Figure 24-7: 各 Shell 与 vi/Vim 使用的点文件

任何以 .(点)开头的文件都称为点文件 或隐藏文件。ls 程序不会显示点文件的 名字,除非你使用 -a 选项。

点文件最常见的作用是替程序存放配置数据或 初始化命令。这里作为例子,列出各种 Shell 以 及 vi/Vim 文本编辑器所使用的点文件。

文件名 用途
.bash_login登录文件:Bash
.bash_logout注销文件:Bash
.bash_profile登录文件:Bash
.bashrc环境文件:Bash
.cshrc环境文件:C-Shell、Tcsh
.exrc初始化文件:vi、Vim
.history历史文件:Bash、Korn Shell、C-Shell、Tcsh
.login登录文件:C-Shell、Tcsh
.logout注销文件:C-Shell、Tcsh
.profile登录文件:Bash、Korn Shell、Bourne Shell
.tcshrc环境文件:Tcsh
.vimrc初始化文件:Vim

要显示隐藏文件的名字,你要用 ls-a(all files,所有文件)选项。例如, 要查看你隐藏文件的名字,就切换到主目录, 然后用 ls -a:

cd
ls -a

很有可能,你还会看到一些以点开头的目录名。 这样的目录同样是隐藏的,除非你使用 -a 选项,否则不会用 ls 看到它们。

当你使用 -a 时,你会看到所有 的文件。遗憾的是,没有任何选项可以只显示 点文件。不过,借助通配符,你可以把显示的 文件名限制为只有点文件。例如,下面这条命令 显示工作目录中所有名字以 . 开头、 后面紧跟一个字母的文件:

ls .[a-zA-Z]*

下面这条命令有点复杂,但要有用得得多。它 同样显示点文件,但会把 ... 以及任何隐藏目录中的内容都省略 掉:

ls -d .??*

图 24-7 列出了我们已经讲过的标准点文件 的名字(Shell 部分见第 14 章,vi 和 Vim 部分见第 22 章)。这些文件在某一天 你可能想要改动。你很可能会在主目录里发现 许多其他的点文件。但是,除非你清楚自己在 做什么,否则别去碰它们。

— 提示 —

你主目录中的许多点文件是很重要的。在编辑 这类文件之前,先做个备份是个好主意。为此 使用 cp 程序(第 25 章),例如:

cp .bash_profile .bash_profile.bak

如果你不小心把这个文件弄坏了,还能把它恢复 回来。为此使用 mv 命令(同样在 第 25 章),例如:

mv .bash_profile.bak .bash_profile

长目录列表:ls -dhltu
(Long Directory Listings: ls -dhltu)

当你使用 ls 程序时,有几个选项可以在 显示名字的同时显示出各种信息。这些选项中最 有用的是 -l,它代表 "long listing" (长列表):

ls -l

如果列表长到从你的屏幕上滚走了,你可以把它 通过管道交给 less:

ls -l | less

这里是一些示例输出,我们接着就来分析它:

total 32
-rw-rw-r--  1 harley staff  2255 Apr  2 21:52 application
drwxrwxr-x  2 harley staff  4096 Oct  5 11:40 bin
drwxrwxr-x  2 harley staff  4096 Oct  5 11:41 music
-rw-rw-r--  1 harley staff   663 Sep 26 20:03 partylist

在最右边你看到的是文件的名字。在这个例子中, 有四个文件:applicationbinmusicpartylist。在最左边, 也就是每行的开头,有一个用一个字母表示的 指示符,告诉你文件的类型。我们一会儿再详细 谈这个。这里我先提一下:-(连字符) 表示普通文件;d 表示目录。因此, applicationpartylist 是普通 文件;binmusic 是目录。

在文件名的左边是一个时间和日期。这称为修改 时间(MODIFICATION TIME)。它显示文件最后一次 被改动的时间。在我们的例子中,application 文件最后一次被改动是在 4 月 2 日 21:52。 (记住,Unix 使用 24 小时制;见第 8 章 和附录 F。)

作为一种替代,你可以把 -u-l 一起使用,从而显示访问时间(ACCESS TIME)而不是修改时间。访问时间显示文件最后 一次被读取的时间。例如:

ls -lu application

输出在下面。正如你从上面的输出中所看到的, application 文件最后一次被改动是在 4 月 2 日 21:52;而正如你从下面的输出中所看到 的,这个文件最后一次被读取是在 4 月 11 日 15:45:

-rw-rw-r-- 1 harley staff 2255 Apr 11 15:45 application

如果你想按时间排序显示文件,使用 -t 选项:

ls -lt
ls -ltu

下面是第一条命令的输出,它按从新到旧(最近 改动的排到最早改动的)的顺序显示文件:

total 32
drwxrwxr-x  2 harley staff  4096 Oct  5 11:41 music
drwxrwxr-x  2 harley staff  4096 Oct  5 11:40 bin
-rw-rw-r--  1 harley staff   663 Sep 26 20:03 partylist
-rw-rw-r--  1 harley staff  2255 Apr  2 21:52 application

如果你把 -t-r(reverse, 反向)选项结合起来,ls 就会按从旧到新的 顺序显示文件:

ls -lrt
ls -lrtu

例如:

total 32
-rw-rw-r--  1 harley staff  2255 Apr  2 21:52 application
-rw-rw-r--  1 harley staff   663 Sep 26 20:03 partylist
drwxrwxr-x  2 harley staff  4096 Oct  5 11:40 bin
drwxrwxr-x  2 harley staff  4096 Oct  5 11:41 music

— 提示 —

假设你的工作目录里有很多很多文件,而你想 显示最近改动的那些文件的信息。最简单的办法 就是按时间的倒序显示文件:

ls -lrt

要显示最近访问的文件,用:

ls -lrtu

由于你是在一个大目录里干活,大部分文件名字 会从屏幕上滚走。不过这无关紧要,因为你关心 的只是最后几行。

回到我们的讨论。在列表的最上方,ls 显示了被列出的所有文件一共使用了多少个文件 系统块。在这个例子中,两个文件和两个目录一共 用了 32 个块。(关于文件系统块的解释,见本章 前面的讨论。)

在日期的左边,你看到的是每个文件以字节为单位 的大小。如果文件是文本文件,每个字节存放一个 字符的数据。例如,partylist 是一个 文本文件,包含 663 个字符,包括每一行文本末尾 的那个换行符。同样,application 包含 2,255 字节的数据,也包括换行符。这里要明白 一件重要的事:你在这里看到的数字表示文件实际 包含的数据量,而不是文件占用的存储空间大小。 如果你想知道一个文件占了多少空间,必须使用 duls -s 命令,本章 前面讲过。

默认情况下,ls 以字节为单位显示文件 大小,而当数字很大时,这容易让人犯迷糊。要 以千字节(K)或兆字节(M)为单位显示较大的 数字,使用 -h(human-readable,人类 可读)选项:

ls -hl

例如,下面的输出显示的还是上面那些文件:

total 32
-rw-rw-r--  1 harley staff  2.3K Apr  2 21:52 application
drwxrwxr-x  2 harley staff  4.0K Oct  5 11:40 bin
drwxrwxr-x  2 harley staff  4.0K Oct  5 11:41 music
-rw-rw-r--  1 harley staff   663 Sep 26 20:03 partylist

注意,两个目录各用 4,096 字节,正好是 4K。 这是因为这台特定的系统使用 4K 的分配单元, 而每个目录一开始都至少占用 1 个分配单元。 (分配单元在本章前面讨论过。)要紧的是要记住: 这个 4K 指的是目录本身的大小,不是目录内容的 大小。虽然我们常常说一个目录"包含"若干文件, 但那只是个比喻。目录只占用很少的存储空间, 因为它们存放的是关于文件的信息,而不是 文件本身。

在每一行的最左边,第一个字符显示文件的类型。 有好几种可能,图 24-8 作了小结。正如我所 说,最重要的字符是 -(连字符),它表示 普通文件;还有 d,它表示目录。- 字符虽然能标识出一个普通文件,却不告诉你这个 文件的任何具体情况。如果你想了解更多信息,可以 使用 file 命令(本章前面介绍过)。例如, 你在上面的列表中可以看到 partylist 是一 个普通文件。想要更多信息,就输入:

file partylist

输出是:

partylist: ASCII text

Figure 24-8: ls -l 使用的文件类型指示符

当你使用 ls 加上 -l(长列表) 选项时,关于每个文件的信息会各自占一行显示。 在每一行的最左边,ls 显示一个字符来 指明文件的类型。下面是最重要的几个指示符。

指示符 含义
-普通文件
d目录
l符号链接
b特殊文件(块设备)
c特殊文件(字符设备)
p命名管道/FIFO

回到文件指示符这个话题。不那么常见的字符有: l(小写字母 "l")表示符号链接 (第 25 章),p 表示命名管道 (第 23 章),bc 表示 特殊文件(第 23 章)。就特殊文件而言, Unix 区分两类设备。终端这类一次处理一个字节 数据的设备称为字符设备(CHARACTER DEVICE)。 磁盘这类一次处理固定数目字节的设备称为块设备 (BLOCK DEVICE)。字母 c 标识代表字符 设备的特殊文件;b 标识代表块设备的 特殊文件。

在文件大小的左边是两个名字:文件所有者的用户 ID 和组,也就是该用户 ID 所属的那个组。在我们 的例子中,所有文件都属于用户 ID harley, 它位于名为 staff 的组中。(有些 Unix 版本除非你使用 -g 选项,否则不显示组。) 在用户 ID 的左边是一个数字,表示这个文件有多 少个链接。最后,最左边那串九个字符(紧挨着 开头那个字符的右边)显示的是文件权限。文件 所有权、组、链接和权限这四个概念,我们要在 第 25 章讨论,到那时我们会更详细地看 ls -l 程序的输出。

当你指定一个目录的名字时,ls 列出的 是该目录中各个文件的信息。例如,要为 /bin 目录中的所有文件显示长列表,你 应该用:

ls -l /bin | less

如果你想要的是目录本身的信息,使用 -d (directory)选项。它告诉 ls 把目录当作 它自己的文件来看待。例如,要显示 /bin 目录本身(而不是 /bin 里面的内容)的 信息,你应该用:

ls -dl /bin

这里是一些示例输出:

drwxr-xr-x 2 root root 4096 Dec 21 2008 /bin

当你要列出许多文件、其中一些是目录,而 ls 又为每个目录的内容显示出一堆你 不想要的信息时,这是一个很值得记住的方便 选项。当你使用 -d 时,它告诉 ls 不要往任何目录里面去看。

-l 选项显示的信息可以有许多富有想象力 的用法,办法就是把输出通过管道交给一个过滤器 (见第 16-19 章)。这里举两个例子给你一些启发。 要列出所有最后一次修改是在九月的文件名字,你 可以用:

ls -l | grep Sep

要统计最后一次修改是在九月的文件有多少个,用:

ls -l | grep Sep | wc -l

使用 ls 的实用别名
(Useful Aliases for Using ls)

ls 程序的使用频率极高。确实, 正如我前面提到的,我认为 ls 是整个 Unix 工具箱里最有用的程序。因此,人们常常 定义一些别名,好让 ls 搭配最常用的 选项时用起来更省事。一旦你找到了自己喜欢的 别名,就可以把它们放进你的环境文件,让它们 永久生效。(关于别名的详细讨论见 第 13 章;关于环境文件的说明见 第 14 章。)

你可以对 ls 使用两类别名。第一类是 重新定义 ls 本身的别名。例如,假设你 每次用 ls 都想要 -F--color 这两个选项。用下面某个别名 就行。第一个是给 Bourne Shell 家族的;第二个 是给 C-Shell 家族的:

alias ls='ls -F --color=auto'
alias ls 'ls -F --color=auto'

第二类别名是为 ls 的某个特定用法编出 一个新名字。下面是 Bourne Shell 家族的别名:

alias ll='ls -l'
alias la='ls -a'
alias lla='ls -la'
alias ldot='ls -d .??*'

对于 C-Shell 家族:

alias ll  'ls -l'
alias la  'ls -a'
alias lla 'ls -la'
alias ldot 'ls -d .??*'

这些别名让你可以方便地显示一个长列表 (ll)、一个所有文件的列表(la)、 一个所有文件的长列表(lla)以及一个只 有点文件的列表(ldot)。例如,一旦你 定义了 ll 别名,你就可以用下面这条 命令显示 /bin 目录的长列表:

ll /bin

要显示你工作目录的长列表(包括点文件),用:

lla

要只显示你的点文件,用:

ldot

我的建议是,把这些别名放进你的环境文件,并且 花些时间用用它们。等你习惯了这些别名,你就 再也离不开它们了。

显示目录树:tree
(Displaying a Directory Tree: tree)

如果你使用 Linux,有一个叫 tree 的强大工具,它能替你把文件系统的任何一部分 画成一张图。语法是:

tree [-adfFilrst] [-L level] [directory...]

其中 level 是要向下深入这棵树的层数, directory 是一个目录的名字。

要想看看它是怎么工作的,就把整个文件系统的 树列出来。因为这棵树会非常庞大,你需要把它 通过管道交给 less。看累的时候就按 q 退出。

tree / | less

大多数时候,你会用 tree 来把自己的 文件可视化。要显示文件系统里属于你的那一部分, 用:

tree ~ | less

这里是一些典型的输出:

/home/harley
|-- bin
|   |-- funky
|   `-- spacewar
`-- essays
    |-- history
    |   |-- crusades
    |   `-- renaissance
    `-- literature
        |-- kafka
        `-- tolstoy

在这个例子中,主目录有两个子目录,binessaysbin 目录包含两个 文件。essays 目录自己又包含两个子 目录,historyliterature, 它们各包含两个文件。

tree 程序有很多选项。我来解释最重要的 那几个,好让你去实验。首先要说,有些选项和 我们用在 ls 上的一样。-a 选项 显示所有文件,包括点文件;-s 除了名字 还显示每个文件的大小;-F 显示一个标明 文件类型的标志;-r 按相反的顺序排列 输出;-t 按修改时间排列输出。

除此之外,tree 还有它自己的选项。最 有用的是 -d,它只显示目录,例如:

tree -d ~ | less

用上面那棵同样的树,输出是:

/home/harley
|-- bin
`-- essays
    |-- history
    `-- literature
4 directories

-f 选项显示完整的路径名,例如:

tree -df ~ | less

示例输出是:

/home/harley
|-- /home/harley/bin
`-- /home/harley/essays
    |-- /home/harley/history
    `-- /home/harley/literature
4 directories

-i 选项省略缩进线。当你想收集一组路径 名时,这很有用:

tree -dfi ~ | less

示例输出是:

/home/harley
/home/harley/bin
/home/harley/essays
/home/harley/history
/home/harley/literature
4 directories

要限制树的深度,你可以使用 -L(limit, 限制)选项,后面跟一个数字。它告诉 tree 只向下深入那么多层,例如:

tree -d -L 2 /home

最后,-l 选项告诉 tree 跟随 所有的符号链接(见第 25 章),把它们 当作真正的目录。

作为本节的小结,这里举个例子,说明如何用 tree 找出整个文件系统中所有名为 bin 的目录。思路是从根目录 (/)开始,把搜索限制为只看目录 (-d),显示完整的路径名(-f), 省略缩进线(-i),然后把输出送给 grep(第 19 章),只挑出那些以 /bin 结尾的行。命令是:

tree -dif / | grep '/bin$'

这里是一些示例输出:

/bin
/home/harley/bin
/usr/bin
/usr/local/bin
/usr/X11R6/bin

文件管理器
(File Managers)

在本章中,我们讨论了可以对目录做的那些基本 操作:创建、删除、移动和重命名。我们还讨论了 如何更改你的工作目录,以及如何用 ls 以各种方式显示一个目录的内容。在第 25 章中,我们会就普通文件讨论类似的话题。特别 是,我会告诉你如何创建、复制、重命名、移动和 删除文件,以及如何用 ls 显示关于文件 的信息。

在这两章里,我们用的都是文本方式的命令,它们 是在 Shell 提示符下输入的,也就是我们最早在 第 6 章讨论过的标准 Unix CLI(命令行 界面)。目录和文件命令虽然强大,却还有一种 替代方式我要让你知道。你不必敲命令,而是可以 使用文件管理器(FILE MANAGER),一种为帮助你 操作目录和文件而设计的程序。

文件管理器用整个屏幕或整个窗口来显示文件和 目录的列表。按下不同的键,你就能快速方便地 执行各种常用操作。每个文件管理器都有自己的 一套做法,所以我不去细说:那得靠你自己去学。 通常,精通一个文件管理器要花上一阵子,可一旦 你学会了,用它就成了一种本能。入门的第一步是 阅读它内置的帮助信息。

经典的文件管理器是 Norton Commander,它是一 款极其流行的工具,最早由程序员 John Socha 于 1986 年为老牌的 DOS 操作系统编写。多年来, Socha 开发的双面板设计被一次又一次地克隆和 扩展。如果你是 Windows 用户,你多半对另一种 设计有些经验,那就是 Windows 默认的文件管理器 Windows Explorer 所采用的设计。

大体上,我们可以把文件管理器分为两个家族: 基于 GUI 的和基于文本的。基于 GUI 的文件 管理器是为在图形桌面环境中使用而设计的,比如 Gnome 或 KDE(见第 5 章)。大多数桌面环境都 自带一个默认的文件管理器:Gnome 的是 Nautilus;KDE 的是 Konqueror(见图 24-9)。不过,如果你想要更多的选择,还有 各种各样免费提供的图形文件管理器。基于文本 的文件管理器则是用在文本环境中的,例如当你 使用虚拟控制台(第 6 章)时,或者当你 用终端仿真程序(第 3 章)访问一台远程 Unix 主机时。

Figure 24-9: 一个文件管理器的例子

文件管理器是一种帮助你操作文件和 目录的程序。使用文件管理器,是在 命令行上敲目录和文件命令之外的另 一种选择。这里是一张照片,显示的 是 Konqueror,也就是 KDE 桌面环境 的默认文件管理器。

 

作为参考,下面是一份精选的文件管理器清单, 你不妨试一试。

图形文件管理器:
• File Runner
• Gentoo
• Nautilus(随 Gnome 附带)
• Konqueror(随 KDE 附带)
• XFE [X File Explorer]

文本方式的文件管理器:
• FDclone(FD 的日语版克隆,FD 是一个 DOS 文件管理器)
• Midnight Commander(经典 Norton Commander 的克隆)
• Vifm(基于 vi 命令的文件管理器)

最后,还有一款工具我要提一提。你可以用 Vim 文本编辑器(第 22 章)来执行文件操作。 只要带上一个目录的名字启动 Vim,Vim 就会让 你对该目录中的文件做基本的操作。有空试一试。 注意:当你以兼容模式启动 Vim 时,也就是带上 -C 选项时(见第 22 章),这个 功能不起作用。

练习
(Exercises)

复习问题 #1:

什么是路径名?

绝对路径名与相对路径名有什么区别?

复习问题 #2:

什么是工作目录?它还有什么别的名字?

哪条命令显示你工作目录的名字?再给出 显示同样信息的另一种方法。

哪三条命令用来更改你的工作目录?

假设你想随时都看得到你工作目录的名字。 你会怎么安排?提示:想想你的 Shell 提示符。

复习问题 #3:

你用什么程序列出目录中各个文件的名字?

你用哪些选项来显示:

• 所有文件,包括点文件(隐藏文件)
• 名字按字母表的逆序排列
• 整棵树,包括所有子目录
• 每个名字后面的标志(/ = 目录)
• 每个文件的大小,以人类可读的单位
• 长列表(权限、所有者等等)
• 目录本身的信息,而不是它的内容

当你查看一个目录列表时,... 这两个条目是什么意思?

复习问题 #4:

什么是通配展开?

什么是通配符?

五种不同的通配符分别是什么?每一种各 匹配什么?

通配展开与使用正则表达式有什么不同?

复习问题 #5:

你用什么程序画出目录树的图?

对这个程序来说,最有用的选项是哪一个?

应用你的知识 #1:

从你的主目录开始,用尽可能的 命令,创建下面这些目录:

temp/
temp/books/
temp/books/unix/harley
temp/books/literature/
temp/books/literature/english/
temp/books/literature/english/shakespeare
temp/books/literature/english/shakespeare/hamlet
temp/books/literature/english/shakespeare/macbeth

显示一棵从 temp 开始的目录树图, 只显示目录。(注意:如果你用的不是 Linux, 你可能没有能做这件事的程序。)

应用你的知识 #2:

从上一题的那些目录出发,用尽可能 的命令,创建下面这些空文件。

(提示:用 touch 命令创建这些文件。 它在第 25 章有讲解,本章中也有一个例子。)

• 在 harley 目录中:创建 notesquestionsanswers
• 在 english 目录中:创建 essayexam-schedule
• 在 hamlet 目录中:创建 quotes

显示一棵从 temp 开始的目录树图, 显示所有文件和目录。

应用你的知识 #3:

清空目录栈。

现在把下面这两个目录压入栈中:

~/temp/books/unix/harley
~/temp/books/literature/english/

显示栈的内容,让每个元素各占一行并带行号。

利用这个栈,切换到 harley 目录。

应用你的知识 #4:

写出两条命令,用来显示你工作目录中所有文件 的访问时间,这些文件的名字由 "backup" 这几 个字符开头,后面跟一个字符,再后面跟两位 数字。第一条命令应当使用预定义的字符类别。 第二条命令应当使用范围。

进一步思考 #1:

你在教一门 Unix 课,现在到了讲解怎样创建 目录、怎样理解工作目录的时候了。你有两种 选择。

第一,你可以抽象地讲解这些概念:我们按需 创建目录,我们用 cd 命令来更改工作 目录的值。

或者,你可以引入树的比喻:目录就像树的 枝干,用 cd 就是我们从一根枝干移到 另一根枝干。

你认为哪种办法更好?为什么?

进一步思考 #2:

要显示文件类型,你可以用 ls -F,也 可以用 file。为什么非得有两条这样 的命令不可?