Linux工作原理2常用基本命令和目录层次结构

发布时间 2023-05-29 09:14:37作者: 磁石空杯

本章是对本书中你将遇到的Unix命令和工具的指南。

为什么是Unix命令?这不是一本关于Linux如何工作的书吗?当然是的,但Linux在本质上是一种Unix风格。在本章中,你会看到Unix这个词,而不是Linux,因为你可以把你学到的东西直接带到BSD和其他Unix风味的系统中去。我试图避免涉及太多Linux特有的用户界面扩展,这不仅是为了给你使用其他操作系统提供更好的背景,而且也是因为这些扩展往往是不稳定的。如果你知道核心命令,你就能更快地适应新的Linux版本。此外,了解这些命令可以促进你对内核的理解,因为许多命令直接对应于系统调用。

注意:如果想了解比这里更多的关于Unix初学者的细节,可以考虑阅读《The Linux Command Line》第二版(No Starch Press, 2019),《UNIX for the Impatient》第二版(Addison-Wesley Professional, 1995),以及《Learning the UNIX Operating System,》第五版(O'Reilly, 2001)。

2.1 Bourne Shell: /bin/sh

shell是Unix系统中最重要的部分之一。shell是一个运行命令的程序,就像用户在终端窗口中输入的命令。这些命令可以是其他程序或shell的内置功能。shell还可以作为小型的编程环境。Unix程序员经常将普通的任务分解成较小的组件,并使用shell来管理任务和拼凑事情。

系统的许多重要部分实际上是shell脚本--包含shell命令序列的文本文件。如果你以前使用过MS-DOS,你可以把shell脚本想象成非常强大的.BAT文件。因为它们很重要,第11章完全是关于shell脚本的。

随着你在本书中的学习和实践,你会增加你对使用shell操作命令的知识。shell最好的一点是,如果你犯了错误,你可以很容易地看到你输入的内容,找出错误的原因,然后再试一次。

有许多不同的Unix shell,但所有的功能都来自Bourne shell(/bin/sh),这是贝尔实验室为早期版本的Unix开发的标准外壳。每个Unix系统都需要一个版本的Bourne shell,以便正常运行,正如你在本书中看到的那样。

Linux使用Bourne shell的增强版本,称为bash或 "Bourne-again "shell。bash shell是大多数Linux发行版上的默认shell,在Linux系统中,/bin/sh通常是bash的链接。在运行本书中的例子时,你应该使用bash shell。

注意
如果你在一个你不是系统管理员的组织中使用本章作为Unix账户的指南,你可能没有把bash作为你的默认shell。你可以用chsh改变你的shell或者向你的系统管理员寻求帮助。

2.2 使用Shell

当你安装Linux时,你应该至少创建一个普通用户,作为你的个人账户。在本章中,你应该以普通用户的身份登录。

2.2.1 Shell窗口

登录后,打开shell窗口(通常被称为终端)。从Gnome或KDE这样的GUI中这样做的最简单方法是打开终端程序,在新窗口中启动shell。一旦你打开了shell,它应该在顶部显示提示,通常以美元符号($)结尾。在Ubuntu上,这个提示符应该是name@host:path$,在Fedora上是[name@host path]$,其中name是你的用户名,host是你机器的名字,path是你当前的工作目录(见2.4.1节)。如果你熟悉Windows,shell窗口看起来就像DOS的命令提示符;在macOS中,终端程序基本上与Linux的shell窗口相同。

本书包含许多你将在shell提示符下输入的命令。它们都以一个$开头,表示shell提示符。例如,输入这个命令(只输入粗体部分,不输入$),然后按ENTER键:

andrew@andrew-HP:~$ echo Hello there.
Hello there.

注意:本书中的许多shell命令以#开头。你应该以超级用户(root)的身份运行这些命令,所以需要特别小心。在运行这些命令时,最好的做法是使用sudo,以便提供一些保护,并有日志,你可以在以后查找可能的错误。你将在第2.20节看到如何做到这一点。

现在输入这个命令:

andrew@andrew-HP:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...

这个命令显示了/etc/passwd系统信息文件的内容,然后返回你的shell提示。现在不要关注这个文件的作用,你将在第7章中了解它的全部内容。

命令通常以要运行的程序开始,后面可能有参数,告诉程序要对什么进行操作以及如何操作。这里,程序是cat,有一个参数,即/etc/passwd。许多参数是修改程序默认行为的选项,通常以破折号(-)开头。你很快就会在讨论ls的时候看到这一点。然而,有一些例外情况并不遵循这种正常的命令结构,比如shell的内置参数和环境变量的临时使用。

2.2.2 cat

cat程序是Unix中最容易理解的程序之一;它简单地输出一个或多个文件的内容或另一个输入源。cat命令的一般语法如下:

$ cat file1 file2 ...

当你运行这个命令时,cat会打印file1、file2和任何其他你指定为参数的文件(在前面的例子中用...表示)的内容,然后退出。该程序被称为cat,因为它在打印多个文件的内容时执行串联操作。有很多方法可以运行cat;让我们用它来探索Unix的I/O。

2.2.3 标准输入和标准输出

Unix进程使用I/O流来读和写数据。进程从输入流中读取数据,并将数据写到输出流中。流是非常灵活的。例如,输入流的源头可以是一个文件、一个设备、一个终端窗口,甚至是另一个进程的输出流。

要看正在工作的输入流,请输入cat(没有参数)并按下ENTER。这一次,你不会得到任何直接的输出,你也不会得到你的shell提示符,因为cat仍然在运行。现在输入任何内容,并在每一行的末尾按回车键。像这样使用时,cat命令会重复你输入的任何一行。一旦你觉得足够无聊,就在一个空行上按CTRL-D来终止cat并返回shell提示符。

cat在这里采用交互式行为的原因与流有关。当你没有指定输入文件名时,cat从Linux内核提供的标准输入流而不是连接到文件的流中读取。在这种情况下,标准输入被连接到你运行cat的终端。

注意:在空行上按下CTRL-D会以EOF(文件结束)信息停止当前终端的标准输入条目(通常也会终止一个程序)。不要把它与CTRL-C混淆,后者通常会终止一个程序,而不管其输入或输出。

标准输出是类似的。内核给每个进程一个标准输出流,它可以在那里写出它的输出。cat命令总是把它的输出写到标准输出。当你在终端运行cat时,标准输出被连接到该终端,所以你在那里看到了输出。

标准输入和输出通常被缩写为stdin和stdout。许多命令的操作与cat一样;如果你不指定输入文件,命令就从stdin中读取。输出则有些不同。一些程序(如cat)只将输出发送到stdout,但其他程序可以选择将输出直接发送到文件。

还有第三个标准I/O流,叫做标准错误。你会在第2.14.1节看到它。

标准流的最好的特点是,你可以很容易地操纵它们来读写终端以外的地方,你将在第2.14节中学习。特别是,你将学习如何将流连接到文件和其他进程。

2.3 基本命令

现在让我们来看看更多的Unix命令。这是基本命令的简化列表,没涉及到细节。

2.3.1 ls

ls命令列出了目录的内容。默认情况下是当前目录,但你可以添加任何目录或文件作为参数,而且有许多有用的选项。例如,使用ls -l来获得详细的(长)列表,使用ls -F来显示文件类型信息。下面是一个长列表的例子;它包括文件的所有者(第3列),组(第4列),文件大小(第5列),以及修改日期/时间(在第5列和文件名之间):

andrew@andrew-HP:~$ ls -l
total 119228
-rwxrwxrwx  1 andrew andrew      108 11月 10  2022  a-cov.gcda
-rwxrwxrwx  1 andrew andrew      676 11月 10  2022  a-cov.gcno
-rwxrwxrwx  1 andrew andrew    27248 11月 10  2022  a.out
-rw-rw-r--  1 andrew andrew     1949  2月 15 14:31  base.html
drwxrwxr-x  5 andrew andrew     4096  5月 27 07:20 'Calibre 书库'
drwxrwxrwx 24 andrew andrew     4096  5月 11 15:04  code
...

你将在第2.17节中了解更多关于该输出的第1列。你可以暂时忽略第2列;它是指向文件的硬链接的数量,在第4.6节中有解释。

2.3.2 cp

在其最简单的形式中,cp复制文件。例如,要将file1复制到file2,请输入以下内容:

$ cp file1 file2

你也可以把一个文件复制到另一个目录,在该目录中保持相同的文件名:

$ cp file dir

要复制一个以上的文件到名为dir的目录(文件夹)中,请尝试像这样的例子,复制三个文件:

$ cp file1 file2 file3 dir

2.3.3 mv

mv(移动)命令的工作原理与cp很相似。在其最简单的形式中,它重命名文件。例如,要把file1重命名为file2,请输入以下内容:

$ mv file1 file2

2.3.4 touch

touch命令可以创建文件。如果目标文件已经存在,touch不会改变该文件,但会更新该文件的修改时间戳。例如,要创建空文件,可以这样输入:

$ touch file

然后在该文件上运行ls -l。你应该看到像下面这样的输出,其中的日期和时间表示你运行触摸的时间:

$ touch file
$ ls -l file
-rw-rw-r-- 1 andrew andrew 0  5月 27 11:20 file

要看到时间戳的更新,至少要等一分钟,然后再运行同样的touch命令。ls -l返回的时间戳会更新。

2.3.5 rm

rm命令删除(移除)文件。在你删除文件后,它通常会从你的系统中消失,除非你从备份中恢复它。

$ rm file

2.3.6 echo

echo命令将其参数打印到标准输出:

$ echo Hello again.
Hello again.

echo命令对于查找shell globs("通配符",如*)和变量(如$HOME)的扩展非常有用,你在本章后面会遇到这些情况。

2.4 浏览目录

Unix的目录层次结构从/开始,也称为根目录。目录的分隔符是斜线(/),而不是反斜线(\)。在根目录下有几个标准的子目录,如/usr,你将在第2.19节中学习。

当你引用文件或目录时,你指定路径或路径名。当路径以/开头时(如/usr/lib),它是完整或绝对的路径。

由两个点(...)标识的路径组件指定一个目录的父级。例如,如果你在/usr/lib中工作,路径..指的是/usr。同样地,../bin指的是/usr/bin。

点(.)指的是当前目录;例如,如果你在/usr/lib,路径.仍然是/usr/lib,而./X11是/usr/lib/X11。你不会经常使用.,因为如果路径不是以/开头,大多数命令都默认为当前目录(所以你可以在前面的例子中直接使用X11而不是./X11)。

不以/开头的路径被称为相对路径。大多数时候,你会使用相对路径名,因为你已经在你需要的目录中或附近。

现在你已经对基本的目录机制有了了解,下面是一些基本的目录命令。

cd

当前工作目录是一个进程(如shell)当前所处的目录。除了大多数Linux发行版中默认的shell提示外,你还可以用2.5.3节中描述的pwd命令查看你的当前目录。

每个进程都可以独立设置自己的当前工作目录。cd命令可以改变shell的当前工作目录:

$ cd dir

如果你省略了dir,shell将返回到你的主目录,即你第一次登录时开始的目录。有些程序会用~符号(tilde)来简写你的主目录。

注意: cd命令是shell的内置命令。它不能作为单独的程序工作,因为如果它作为子进程运行,它不能(通常)改变其父级的当前工作目录。

2.4.2 mkdir

mkdir命令创建新的目录:

$ mkdir dir

2.4.3 rmdir

rmdir命令可以删除目录dir:

$ rmdir dir

如果dir不是空的,这个命令就会失败。然而,如果你没有耐心,你可能不想先费力地删除dir里面的所有文件和子目录。你可以使用rm -r dir来删除目录和它的内容,但要小心!这是少数几个可以造成严重破坏的命令之一,特别是当你以超级用户身份运行它时。-r选项指定了递归删除,重复删除dir中的所有内容。不要将-r标志与星号(*)等globs一起使用。最重要的是,在运行你的命令之前一定要仔细检查。

2.4.4 Shell的globbing("通配符")

shell可以将简单的模式与文件和目录名相匹配,这个过程被称为 "通配"。这与其他系统中的通配符的概念相似。其中最简单的是glob字符*,它告诉shell去匹配任何数量的任意字符。例如,下面的命令可以打印出当前目录下的文件列表:

$ echo *

shell将包含globs的参数与文件名相匹配,将文件名替换为这些参数,然后运行修改后的命令行。这种替换被称为扩展,因为shell将所有匹配的文件名替换为一个简化的表达式。下面是一些使用*来扩展文件名的方法:

  • at*扩展到所有以at开头的文件名。
  • *at扩展到所有以at结尾的文件名。
  • at扩展到所有包含at的文件名。

另一个shell glob字符,问号(?),指示shell精确匹配一个任意字符。例如,b?at匹配boat和brat。
如果你不希望shell在命令中扩展一个glob,可以用单引号('')把这个glob括起来。例如,命令echo '*'可以打印出一颗星。你会发现这对下一节中描述的一些命令很方便,例如grep和find。(你将在第11.2节中学习更多关于引号的知识)。

注意:重要的是要记住,shell在运行命令前会进行扩展,而且只在这时进行。因此,如果一个 ""没有被扩展到命令中,shell不会对它做任何事情;而是由命令来决定它要做什么。
shell的模式匹配能力还有很多,但是
和? 是你现在需要知道的。第2.7节描述了那些以点开头的有趣文件的glob行为。

2.5 中级命令

这一节描述了最基本的Unix中级命令。

2.5.1 grep

grep命令打印文件或输入流中与某个表达式相匹配的行。例如,要打印/etc/passwd文件中包含文本root的行,请输入以下内容:

$ grep root /etc/passwd

grep命令在同时对多个文件进行操作时特别方便,因为它除了打印匹配的行外,还打印文件名。例如,如果你想检查/etc中包含root这个词的每个文件,你可以使用这个命令:

$ grep root /etc/*

两个最重要的grep选项是-i(用于不区分大小写的匹配)和-v(反转搜索,即打印所有不匹配的行)。还有一个更强大的变体,叫做egrep(它只是grep -E的一个同义词)。
grep能够理解正则表达式,这些模式以计算机科学理论为基础,在Unix工具中非常常见。正则表达式比通配符式的模式更强大,而且它们有不同的语法。关于正则表达式,有三件重要的事情需要记住:

  • .* 匹配任何数量的字符,包括没有字符(像globs和通配符中的*)。
  • .+ 匹配任何一个或多个字符。
  • .精确匹配一个任意字符。

注意: grep(1) 手册页包含了对正则表达式的详细描述,但它可能有些难以阅读。要了解更多信息,你可以阅读Jeffrey E. F. Friedl编写的《Mastering Regular Expressions》第三版(O'Reilly,2006年),或者参见Tom Christensen等人编写的《Programming Perl》第四版(O'Reilly,2012年)的正则表达式章节。如果你喜欢数学,并且对正则表达式的来源感兴趣,可以看看Jeffrey Ullman和John Hopcroft的《AutomataTheory, Languages, and Computation》第三版(Prentice Hall,2006)。

2.5.2 less

当文件非常大,或者命令的输出很长,并且滚动到屏幕上方时,less命令就会派上用场。
要翻阅像/usr/share/dict/words这样的大文件,你可以使用命令less /usr/share/dict/words。当运行 less 时,你会看到文件的内容,每次都是一屏。按空格键可以往前看文件,按b(小写)可以往后跳一个屏幕。要退出,按q。

注意:less命令是一个名为more的旧程序的增强版。Linux台式机和服务器有less,但它在许多嵌入式系统和其他Unix系统中不是标准配置。如果你遇到了不能使用less的情况,可以试试more。
你也可以在less里面搜索文本。例如,要向前搜索一个单词,你可以输入/word,要向后搜索,你可以使用?word。当你找到一个匹配词时,按n继续搜索。

正如你在第2.14节中所了解的,你可以将几乎所有程序的标准输出直接发送到另一个程序的标准输入。当你有命令有大量的输出需要筛选,而你又想用 less 这样的东西来查看输出时,这就特别有用。下面是将grep命令的输出发送到less的例子:

$ grep ie /usr/share/dict/words | less

2.5.3 pwd

pwd(打印工作目录)程序只是输出当前工作目录的名称。你可能想知道为什么你需要这个,因为大多数Linux发行版都在提示符中设置了用户账户的当前工作目录。有两个原因。
首先,不是所有的提示符都包括当前工作目录,特别是你可能想在自己的提示符中去掉它,因为它占用了很多空间。
第二,你将在第2.17.2节中了解到的符号链接有时会掩盖当前工作目录的真正完整路径。使用pwd -P来消除这种混淆。

2.5.4 diff

要查看两个文本文件之间的差异,可以使用diff:

$ diff file1 file2

有几个选项可以控制输出的格式,默认的输出格式往往是人类最容易理解的。然而,大多数程序员在需要将输出结果发送给别人时,更喜欢diff -u的输出,因为自动化工具更容易处理这种格式。

2.5.5 file

如果你看到一个文件,但不确定它的格式,可以尝试使用file命令,看看系统是否能猜到它:

$ file file

你可能会对这个看起来很单纯的命令的作用感到惊讶。

2.5.6 查找和定位

当你知道某个文件在某个目录树下,但你不知道在哪里时,这是很令人沮丧的。运行find来查找dir中的文件,方法如下:

$ find dir -name file -print

像本节中的大多数程序一样,find能做一些花哨的事情。然而,在你熟知这里显示的形式并理解为什么你需要-name和-print选项之前,不要尝试诸如-exec之类的选项。find命令接受特殊的模式匹配字符,如,但你必须用单引号('')将它们括起来,以保护这些特殊字符不被shell自己的globbing功能所影响。(回顾一下第2.4.4节,shell在运行命令之前会展开globs)。
大多数系统也有查找文件的locate命令。locate不是实时搜索文件,而是搜索系统定期建立的索引。用locate搜索要比find快得多,但是如果你要找的文件比索引要新,locate就找不到它。

2.5.7 head和tail

head和tail命令允许你快速查看文件或数据流的一部分。例如,head /etc/passwd显示密码文件的前10行,tail /etc/passwd显示最后10行。
要改变显示的行数,使用-n选项,其中n是你想看的行数(例如,head -5 /etc/passwd)。要打印从第n行开始的行,使用tail +n。

2.5.8 sort

sort命令可以快速将文本文件的行数按字母数字顺序排列。如果文件的行以数字开头,并且你想按数字顺序排序,使用-n选项。r选项可以反转排序的顺序。

2.6 改变你的密码和shell

使用passwd命令来改变你的密码。你会被要求提供你的旧密码,然后两次提示你的新密码。
最好的密码往往是容易记住的长 "废话 "句子。密码越长(就字符长度而言)越好;尝试16个字符或更多。(在以前,你可以使用的字符数是有限的,所以建议你增加一些奇怪的字符之类的。)
你可以用chsh命令来改变你的shell(比如zsh、ksh或tcsh),但请记住,本书假设你运行的是bash,所以如果你做了改变,一些例子可能就不能用了。

Dot文件(隐藏文件)

如果你还没有进入你的主目录,就换成ls来看看,然后运行ls -a。你看到输出的不同了吗?当你运行ls而不使用-a时,你不会看到被称为点文件的配置文件。这些文件和目录的名称都是以点(.)开头的。常见的点文件有.bashrc和.login,还有一些点目录,如.ssh。
点状文件或目录没有什么特别之处。一些程序默认不显示它们,这样你在列出你的主目录的内容时就不会看到一个完整的混乱。例如,除非你使用-a选项,否则ls不会列出点文件。此外,除非你明确使用模式,如.*,否则shell globs不匹配点状文件。

注意:你可能会遇到globs的问题,因为.匹配.和.(当前和父目录)。你可能希望使用诸如.[^.]或.??*的模式来获取除当前和父目录之外的所有点状文件。

2.8 环境和外壳变量

shell可以存储临时变量,称为shell变量,包含文本字符串的值。shell变量对于跟踪脚本中的数值非常有用,而且一些shell变量可以控制shell的行为方式。(例如,bash shell在显示提示符之前会读取PS1变量)。
要给shell变量赋值,可以使用等号(=)。下面是一个简单的例子:

$ STUFF=blah

前面的例子将名为STUFF的变量的值设置为blah。要访问这个变量,使用$STUFF(例如,尝试运行echo $STUFF)。你将在第11章中了解到shell变量的许多用途。
注意:在分配变量时,不要在=的周围加上任何空格。
环境变量就像shell变量一样,但它并不是专门针对shell的。Unix系统中的所有进程都有环境变量存储。环境变量和shell变量的主要区别是,操作系统会将shell的所有环境变量传递给shell运行的程序,而shell变量不能在你运行的命令中被访问。
你用shell的导出命令指定一个环境变量。例如,如果你想把$STUFF这个shell变量变成一个环境变量,可以用下面的方法:

$ STUFF=blah
$ export STUFF

由于子进程继承了父进程的环境变量,许多程序会读取它们的配置和选项。例如,你可以把你最喜欢的less命令行选项放在LESS环境变量中,当你运行less时就会使用这些选项。(许多手册中都有一个名为 "环境 "的章节来描述这些变量)。

2.9 命令路径

PATH是一个特殊的环境变量,它包含了命令路径(简称path),这是系统目录列表,shell在试图找到一个命令时,会搜索这个目录。例如,当你运行ls时,shell会在PATH中列出的目录中搜索ls程序。如果同名的程序出现在路径中的几个目录中,shell会运行第一个匹配的程序。
如果你运行echo $PATH,你会看到路径的组成部分是由冒号(:)分隔的。比如说

$ echo $PATH
/usr/local/bin:/usr/bin:/bin

要告诉shell在更多的地方寻找程序,可以改变PATH环境变量。例如,通过使用这个命令,你可以在路径的开头添加目录dir,这样shell就会在寻找其他PATH目录之前寻找dir:

$ PATH=dir:$PATH

或者你可以在PATH变量的末尾加上目录名,使Shell最后查找dir:

$ PATH=$PATH:dir

注意:如果你在修改路径时错误地输入了$PATH,你可能会意外地抹去整个路径。如果发生这种情况,不要惊慌! 这种损害不是永久性的;你可以启动一个新的shell。(为了达到持久的效果,你需要在编辑某个配置文件时输入错误,即使这样也不难纠正)。恢复正常的最简单方法是关闭当前的终端窗口并启动另一个。

特殊字符

当与他人讨论Linux时,你应该知道一些你会遇到的特殊字符的名称。如果你对这种事情感到有趣,请看 "Jargon File"(http://www.catb.org/jargon/html/)或其印刷品,《The New Hacker’s Dictionary》,第三版,作者Eric S. Raymond(MIT Press,1996)。

注意: 你经常会看到控制字符用圆点标记;例如,^C代表CTRL-C。

2.11 命令行编辑

当你使用shell的时候,注意到你可以使用左右方向键来编辑命令行,也可以使用上下箭头来翻阅以前的命令。这在大多数Linux系统中是标准的。

  • CTRL-B 将光标向左移动
  • CTRL-F 将光标向右移动
  • CTRL-P 查看上一条命令(或将光标上移)。
  • CTRL-N 查看下一个命令(或将光标向下移动)
  • CTRL-A 将光标移至行首
  • CTRL-E 将光标移至行尾
  • CTRL-W 擦除前面的单词
  • CTRL-U 从光标到行首的擦除
  • CTRL-K 从光标处擦除到行尾处
  • CTRL-Y 粘贴被擦除的文字(例如,从CTRL-U)。

2.12 文本编辑器

两个事实上的标准Unix文本编辑器,即vi和Emacs。大多数Unix向导对他们选择的编辑器很虔诚,但不要听他们的。只要自己选择就好。如果你选择一个与你的工作方式相匹配的,你会发现它更容易学习。基本上,选择归结于此:
如果你想要一个几乎可以做任何事情的编辑器,并且有广泛的在线帮助,而且你不介意做一些额外的输入来获得这些功能,那就试试Emacs。
如果速度就是一切,那就试试vi;它 "玩 "起来有点像电子游戏。
学习vi和Vim编辑器: 阿诺德-罗宾斯、埃尔伯特-汉纳和琳达-拉姆所写的《Unix Text Processing》第七版(O'Reilly,2008),可以告诉你关于vi的一切。对于Emacs,使用在线教程:启动Emacs,按CTRL-H,然后输入T。或者阅读《GNU Emacs Manual》第18版,作者是理查德-M-斯塔尔曼(自由软件基金会,2018)。

还有一些更友好的编辑器,比如nano、Pico等。

注意:编辑文本是你第一次开始看到终端和GUI之间的区别的地方。像vi这样的编辑器在终端窗口内运行,使用标准的终端I/O接口。GUI编辑器启动他们自己的窗口并展示他们自己的界面,独立于终端。Emacs默认在GUI中运行,但也会在终端窗口中运行。

2.13 获得在线帮助

Linux系统有大量的文档。对于基本的命令,手册页(或称man页)会告诉你你需要知道的东西。例如,要查看ls命令的手册页,请按以下方式运行man:

$ man ls

大多数手册页主要集中在参考信息上,也许有一些例子和交叉引用,但仅此而已。不要指望有什么教程,也不要指望有什么吸引人的文学风格。
当程序有许多选项时,手册页往往以某种系统的方式(例如,按字母顺序)列出选项,但它不会告诉你哪些是重要的选项。如果你有耐心,你通常可以在手册页中找到你需要知道的东西。如果你没有耐心,可以问朋友,或者花钱请人做你的朋友,这样你就可以问他或她。
要按关键词搜索手册页,使用-k选项:

$ man -k keyword

如果你不太清楚你想要的命令的名称,这很有帮助。例如,如果你正在寻找一条对某物进行排序的命令,可以运行:

$ man -k sort
--snip--
comm (1) - compare two sorted files line by line
qsort (3) - sorts an array
sort (1) - sort lines of text files
sortm (1) - sort messages
tsort (1) - perform topological sort
--snip--

输出包括手册页面名称、手册章节(见下文),以及对手册页面所含内容的快速描述。

注意:如果你对前面章节中描述的命令有任何疑问,你可以通过使用man命令找到答案。

手册页是通过编号的章节来引用的。当有人提到手册页面时,他们通常把章节编号放在名称旁边的括号里,比如ping(8)。表2-3列出了这些章节和它们的编号。
image

第1、5、7和8节应该是本书的良好补充。第4节可能用处不大,第6节如果再大一点就好了。如果你不是一个程序员,你可能无法使用第3节,但是一旦你在本书中阅读了更多关于系统调用的内容,你可能就能理解第2节中的一些材料。
一些常见的术语有许多匹配的手册页面,跨越几个章节。默认情况下,man会显示它找到的第一个页面。你可以按章节选择手册页面。例如,要阅读/etc/passwd文件描述(而不是passwd命令),你可以在页面名称前插入章节号,如:

$ man 5 passwd

手册页涵盖了基本内容,但还有很多方法可以获得在线帮助(除了在互联网上搜索之外)。如果你只是在寻找某个命令的某个选项,可以尝试在命令名称后面输入--help或-h(不同的命令有不同的选项)。你可能会得到大量的信息(如ls --help的情况),也可能找到你要找的东西。
前段时间,GNU项目认为它不太喜欢手册页,于是转而使用另一种叫做info(或texinfo)的格式。这种文档通常比典型的手册页更深入,但它可能更复杂。要访问info手册,请使用info的命令名:

$ info command

如果你不喜欢info阅读器,你可以把输出发送到less(只需添加 | less)。
有些软件包将它们的可用文档倾倒在/usr/share/doc中,而不考虑在线手册系统,如man或info。如果你发现自己在搜索文档,请查看你系统中的这个目录--当然,也可以在网上搜索。

2.14 Shell的输入和输出

现在你已经熟悉了基本的Unix命令、文件和目录,你准备学习如何重定向标准输入和输出。让我们从标准输出开始。
要把命令的输出发送到文件而不是终端,请使用>重定向字符:

$ command > file

如果文件不存在,shell就会创建它。如果文件存在,shell会先删除(clobbers)原始文件。(有些shell有参数可以防止clobber。例如,你可以输入set -C来避免bash中的clobbering)。
你可以用">>重定向 "的语法将输出附加到文件中而不是覆盖它:

$ command >> file

这是方便的方法,当执行相关的命令序列时,可以将输出收集在一个地方。
要把一个命令的标准输出发送到另一个命令的标准输入,可以使用管道字符(|)。要看这是如何工作的,请尝试这两条命令:

$ head /proc/cpuinfo
$ head /proc/cpuinfo | tr a-z A-Z

你可以通过你想要的管道命令发送输出,只需在每个额外的命令前添加一个管道。

2.14.1 标准错误

偶尔,你可能会重定向标准输出,但发现程序仍然会向终端打印一些东西。这被称为标准错误(stderr);它是额外的输出流,用于诊断和调试。例如,这个命令产生了错误:

$ ls /fffffffff > f

完成后,f应该是空的,但你仍然在终端上看到以下作为标准错误的错误信息:

ls: cannot access /fffffffff: No such file or directory

如果你愿意,你可以重定向标准错误。例如,要把标准输出发送到f,把标准错误发送到e,使用2>语法,像这样:

$ ls /fffffffff > f 2> e

数字2指定了shell所修改的流ID。流ID 1是标准输出(默认),而2是标准错误。
你也可以用>&符号将标准错误发送到与stdout相同的地方。例如,要把标准输出和标准错误都发送到名为f的文件中,请尝试这个命令:

$ ls /fffffffff > f 2>&1

2.14.2 标准输入重定向

要将文件引导到程序的标准输入,请使用<操作符:

$ head < /proc/cpuinfo

你偶尔会遇到需要这种重定向的程序,但由于大多数Unix命令都接受文件名作为参数,这种情况并不常见。例如,前面的命令可以写成head /proc/cpuinfo。

2.15 理解错误信息

当你在类似Unix的系统(如Linux)上遇到问题时,你必须阅读错误信息。与其他操作系统的信息不同,Unix的错误通常准确地告诉你出了什么问题。

2.15.1 Unix错误信息的剖析

大多数Unix程序产生和报告相同的基本错误信息,但在任何两个程序的输出之间可能有细微的差别。这里有一个例子,你肯定会以某种形式遇到:

$ ls /dsafsda
ls: cannot access /dsafsda: No such file or directory

这条信息有三个组成部分:

  • 程序名称,ls。有些程序省略了这一识别信息,当你编写shell脚本时,这可能会让人感到厌烦,但这其实并不是什么大问题。
  • 文件名,/dsafsda,这是一个更具体的信息。这个路径有一个问题。
  • 错误No such file or directory表明文件名有问题。

把它们放在一起,你会得到这样的信息:“ls tried to open /dsafsda but couldn’t because it doesn’t exist.” 。这可能看起来很明显,但当你运行包括不同名称的错误命令的shell脚本时,这些信息会变得有点混乱。
在排除错误时,总是先解决第一个错误。有些程序在报告一系列其他问题之前,会报告它们不能做任何事情。例如,假设你运行一个名为scumd的虚构程序,你看到了这样的错误信息:

scumd: cannot access /etc/scumd/config: No such file or directory

在这之后是一大串其他错误信息,看起来就像一场完全的灾难。不要让那些其他错误分散你的注意力。你可能只是需要创建/etc/scumd/config。

注意:不要把错误信息和警告信息混淆。警告通常看起来像错误,但它们包含警告这个词。警告通常意味着有什么问题,但程序还是会尝试继续运行。为了解决警告信息中指出的问题,你可能必须找到一个进程并在做其他事情之前杀死它。(你将在第2.16节中学习列出和杀死进程的知识)。

2.15.2 常见错误

你在Unix程序中遇到的许多错误是由文件和进程出错引起的。其中有很多错误直接来自于内核系统调用遇到的情况,因此你可以通过观察这些错误了解内核是如何将问题反馈给进程的。

No such file or directory

这是头号错误。你试图访问不存在的文件。因为Unix的文件I/O系统对文件和目录没有什么区别,这个错误信息涵盖了两种情况。当你试图读取不存在的文件时,当你试图改变到不存在的目录时,当你试图写到不存在的目录中的文件时,你都会得到它,等等。这种错误也被称为ENOENT,是 "Error NO ENTity "的简称。

注意:如果你对系统调用感兴趣,这通常是open()返回ENOENT的结果。关于它可能遇到的错误的更多信息,请参见open(2)手册页。

File exists

在这种情况下,你可能试图创建已经存在的文件。当你试图创建与文件同名的目录时,这很常见。

Not a directory, Is a directory

当你试图把文件作为目录,或把目录作为文件时,这些信息就会弹出来。例如:

$ touch a
$ touch a/b
touch: a/b: Not a directory

注意,这个错误信息只适用于a/b的a部分。当你遇到这个问题时,你可能需要稍微挖掘一下,找到被当作目录的路径组件。

No space left on device

你没有磁盘空间了。

Permission denied

当你试图读取或写入不允许访问的文件或目录时(你的权限不足),你会得到这个错误。当你试图执行没有设置执行位的文件时,也会显示这个错误(即使你可以读取该文件)。你将在第2.17节中阅读更多关于权限的内容。

Operation not permitted

这通常发生在你试图杀死不属于你的进程时。

Segmentation fault, Bus error

分段故障本质上意味着编写你刚刚运行的程序的人在某个地方搞砸了。该程序试图访问它不允许触及的内存的某个部分,而操作系统将其杀死。同样,总线错误意味着程序试图以一种不应该的方式访问一些内存。当你得到这些错误之一时,你可能是给了程序一些它不期望的输入。在极少数情况下,可能是内存硬件出现了故障。

2.16 列出和操纵进程

回顾第一章,进程是正在运行的程序。系统中的每个进程都有一个数字的进程ID(PID)。为了快速列出正在运行的进程,只需在命令行上运行ps。你应该得到一个类似这样的列表:

$ ps
  PID TTY STAT TIME COMMAND
  520 p0  S    0:00 -bash
  545  ?  S    3:59 /usr/X11R6/bin/ctwm -W
  548  ?  S    0:10 xclock -geometry -0-0
 2159 pd  SW   0:00 /usr/bin/vi lib/addresses
31956 p3  R    0:00 ps

这些字段如下:

  • PID 进程的ID。
  • TTY 进程所运行的终端设备。稍后会有更多关于这个的内容。
  • STAT 进程的状态--即进程正在做什么,它的内存在哪里。例如,S表示睡眠,R表示运行。(参见ps(1)手册中对所有符号的描述)。
  • TIME 进程到目前为止所使用的CPU时间,以分和秒为单位。换句话说,该进程在处理器上运行指令的总时间。请记住,由于进程不是持续运行的,这与进程开始后的时间(或 "壁钟时间")不同。
  • COMMAND 这个看起来很明显,是用来运行程序的命令,但是要注意,进程可以改变这个字段的原始值。此外,shell可以进行glob扩展,这个字段将反映扩展后的命令,而不是你在提示符下输入的命令。

注意:PID对于系统上运行的每个进程都是唯一的。然而,在进程终止后,内核最终可以为一个新的进程重新使用这个PID。

2.16.1 ps命令选项

ps命令有很多选项。为了使事情更加混乱,你可以用三种不同的方式指定选项--Unix、BSD和GNU。许多人认为BSD风格是最舒服的(也许是因为它涉及到较少的输入),所以这就是我们在本书中要使用的风格。下面是一些最有用的选项组合:

  • ps x 显示所有正在运行的进程。
  • ps ax 显示系统中的所有进程,而不仅仅是你自己的进程。
  • ps u 包括更详细的进程信息。
  • ps w 显示完整的命令名称,而不仅仅是适合一行的内容。
    与其他程序一样,你可以组合选项,如ps aux和ps auxw。
    要检查特定的进程,可以在ps命令的参数列表中添加其PID。例如,要检查当前的shell进程,你可以使用ps u $$($$是一个shell变量,评估为当前shell的PID)。你会在第8章中找到关于管理命令top和lsof的信息。这些命令对于定位进程是很有用的,即使你在做系统维护以外的事情时也是如此。

2.16.2 进程终止

要终止进程,你可以用kill命令向它发送信号--从内核向进程发送的信息。在大多数情况下,你所需要做的就是这样:

$ kill pid

有许多类型的信号。默认的(上面使用的)是TERM,即终止。你可以通过给kill增个额外的选项来发送不同的信号。例如,要冻结进程而不是终止它,可以使用STOP信号:

$ kill -STOP pid

停止的进程仍然在内存中,准备继续它的工作。使用CONT信号来继续运行该进程:

$ kill -CONT pid

注意:使用CTRL-C来终止正在当前终端运行的进程,与使用kill来结束进程的INT(中断)信号是一样的。

内核在接收到信号时给大多数进程一个机会来清理自己(通过信号处理机制)。然而,一些进程可能会选择非终结性的动作来响应信号,在试图处理信号的过程中陷入困境,或者干脆忽略它,所以你可能会发现进程在你试图终止它之后仍然运行。如果发生这种情况,而你又确实需要杀死进程,那么终止它的最粗暴的方法就是使用KILL信号。与其他信号不同,KILL不能被忽略;事实上,操作系统甚至不给进程一个机会。内核会直接终止该进程,并将其从内存中强行删除。使用这种方法只是作为最后的手段。

你可能会看到其他用户用kill输入数字而不是名字--例如,kill -9而不是kill -KILL。这是因为内核使用数字来表示不同的信号;如果你知道你想发送的信号的编号,你可以这样使用kill。运行kill -l来获得信号编号与名称的映射。

2.16.3 作业控制

Shell支持作业控制,这是一种通过使用各种按键和命令向程序发送TSTP(类似于STOP)和CONT信号的方法。这允许你在你使用的程序之间暂停和切换。例如,你可以用CTRL-Z发送一个TSTP信号,然后通过输入fg(移至前台)或bg(移至后台;见下一节)再次启动程序。但是,尽管它很有用,而且许多有经验的用户也有这样的习惯,作业控制并不是必须的,而且对初学者来说可能会感到困惑: 用户经常按CTRL-Z而不是CTRL-C,忘记了他们正在运行的东西,最终导致许多进程被中止。

注意:要查看你是否不小心暂停了当前终端上的任何进程,可以运行jobs命令。
如果你想运行多个程序,在单独的终端窗口中运行每个程序,把非交互式进程放在后台(如下一节所述),并学会使用screen和tmux工具。

2.16.4 背景进程

通常,当你从shell中运行Unix命令时,在程序执行完毕之前,你不会再得到shell提示。然而,你可以将进程从shell中分离出来,用安培号(&)把它放在 "后台";这样你就会得到提示符。例如,如果你有需要用 gunzip 解压的大文件(你会在第 2.18 节中看到这个),并且你想在它运行时做一些其他的事情,可以运行这样的命令:

$ gunzip file.gz &

shell应该通过打印新的后台进程的PID来响应,并且提示应该立即返回,这样你就可以继续工作了。如果该进程需要很长的时间,它甚至可以在你注销后继续运行,如果你必须运行进行大量数字计算的程序,这就特别方便了。如果进程在你注销或关闭终端窗口之前完成,shell通常会通知你,这取决于你的设置。

注意如果你正在远程访问一台机器,并希望在你注销时继续运行一个程序,你可能需要使用nohup命令;详情请见其手册页。
bash shell和大多数全屏交互程序都支持CTRL-L来重绘整个屏幕。如果一个程序是从标准输入中读取的,CTRL-R通常会重绘当前行,但在错误的时间按错误的顺序会让你处于比以前更糟糕的情况。例如,在bash提示符下输入CTRL-R会使你进入反向的isearch模式(按ESC退出)。

2.17 文件模式和权限

每个Unix文件都有一组权限,决定你是否可以读取、写入或运行该文件。运行ls -l可以显示这些权限。下面是这样显示的例子:

-rw-r-r-1 1 juser somegroup 7041 Mar 26 19:34 endnotes.html

文件的模式1表示该文件的权限和一些额外的信息。该模式有四个部分,如图2-1所示。

图2-1: 文件模式的各个
模式的第一个字符是文件类型。在这个位置上的破折号(-),如图所示,表示一个普通的文件,意味着这个文件没有什么特别之处;它只是二进制或文本数据。这是迄今为止最常见的文件类型。目录也很常见,在文件类型槽中用d表示。(第3.1节列出了其余的文件类型)。
文件模式的其余部分包含权限,分为三组:用户、组和其他,按顺序排列。例如,例子中的rw-字符是用户权限,后面的r--字符是组的权限,最后的r--字符是其他权限。
每个权限集可以包含四种基本表示:

  • r表示文件是可读的。
  • w表示该文件是可写的。
  • x表示该文件是可执行的(你可以把它作为一个程序运行)。
  • 表示 "无"(更确切地说,这组中的那个权限没有被授予)。
    用户权限(第一组)与拥有该文件的用户有关。在前面的例子中,那就是juser。第二组,组权限,是针对文件的组(本例中的somegroup)。该组中的任何用户都可以利用这些权限。(使用groups命令查看你所在的组,更多信息见7.3.5节)。
    系统中的每个人都可以根据第三组权限,即其他权限进行访问,这些权限有时被称为世界权限。

注意:每个读、写和执行的权限槽有时被称为一个权限位,因为操作系统中的底层表示是一系列的位。因此,你可能会听到人们把部分权限称为 "读位"。
一些可执行文件在用户权限列表中有一个s,而不是x,这表明该可执行文件是setuid,意味着当你执行该程序时,它的运行就好像文件所有者是用户而不是你。许多程序利用这个setuid位,以root身份运行,以获得他们需要的权限来改变系统文件。比如passwd程序,它需要改变/etc/passwd文件。

2.17.1 修改权限

要改变一个文件或目录的权限,可以使用chmod命令。首先,挑选你想改变的权限集,然后挑选要改变的位。例如,要给文件增加组(g)和世界(o,代表 "其他")阅读(r)权限,你可以运行这两条命令:

$ chmod g+r file
$ chmod o+r file

或者你可以一次完成:

$ chmod go+r file

要删除这些权限,使用go-r而不是go+r。

注意:显然,你不应该让文件成为世界可写文件,因为这样做会使你系统中的任何人都可以改变它们。但是,这是否也允许任何连接到互联网的人改变它们呢?可能不会,除非你的系统有一个网络安全漏洞。在这种情况下,文件权限反正也帮不了你。

你有时可能会看到有人用数字来改变权限,例如:

$ chmod 644 file

这被称为绝对改变,因为它一次性设置了所有权限位。要理解这一点,你需要知道如何以八进制形式表示权限位(每个数字代表以8为基数的数字,0到7,并对应权限集)。参见chmod(1)手册页或info手册了解更多。
如果你喜欢使用绝对模式,你其实不需要知道如何构建这些模式;只要记住你最常使用的模式即可。表2-4列出了最常见的几种模式。
表2-4:绝对权限模式

目录也有权限。如果目录是可读的,你可以列出该目录的内容,但只有当该目录是可执行的,你才能访问该目录中的文件。在大多数情况下,你都需要这两种权限;人们在设置目录的权限时常犯的错误是,在使用绝对模式时不小心删除了执行权限。
最后,你可以用umask shell命令指定一组默认的权限,它对你创建的任何新文件应用一组预定义的权限。一般来说,如果你希望每个人都能看到你创建的所有文件和目录,就使用umask 022,如果你不希望,就使用umask 077。如果你想使你所希望的权限掩码适用于新的窗口和以后的会话,你需要把带有所需模式的umask命令放在你的某个启动文件中,如第13章中所讨论的。

2.17.2 使用符号链接

符号链接是指向另一个文件或目录的文件,有效地创建了别名(像Windows中的快捷方式)。符号链接提供了对模糊的目录路径的快速访问。
在一个长的目录列表中,符号链接看起来像这样(注意文件模式中的l是文件类型):

lrwxrwxrwx 1 ruser users  11 Feb 27 13:52  somedir -> /home/origdir

如果你试图访问这个目录中的somedir,系统会给你/home/origdir代替。符号链接只是指向其他名字的文件名。它们的名字和它们所指向的路径不需要有任何意义。在前面的例子中,/home/origdir不需要存在。
事实上,如果/home/origdir不存在,任何访问somedir的程序都会报告somedir不存在(除了ls somedir,这个命令愚蠢地告诉你somedir就是somedir)。这可能是令人困惑的,因为你可以看到名为somedir的东西就在你眼前。
这并不是符号链接可能令人困惑的唯一方式。另一个问题是,你不能仅仅通过查看链接的名称来确定链接目标的特征;你必须跟踪链接,看它是否指向一个文件或目录。你的系统也可能有指向其他链接的链接,这被称为链式符号链接,当你试图追踪它们时,可能是个麻烦。
要创建一个从target到linkname的符号链接,使用ln -s,如下所示:

$ ln -s target linkname

linkname参数是符号链接的名称,target参数是链接指向的文件或目录的路径,而-s标志则指定了符号链接(见后面的警告)。
当制作符号链接时,在运行命令前要检查两次,因为有几种情况可能出错。例如,如果你不小心颠倒了参数的顺序(ln -s linkname target),如果linkname是已经存在的目录,你就会遇到一些麻烦。如果是这种情况(经常如此),ln会在linkname内创建一个名为target的链接,而且这个链接会指向自己,除非linkname是完整的路径。如果你在创建一个目录的符号链接时出了问题,请检查该目录是否有错误的符号链接并删除它们。

当你不知道它们的存在时,符号链接也会引起头疼。例如,你可以很容易地编辑你认为是文件的副本,但实际上是原文件的一个符号链接。

警告:创建符号链接时不要忘记-s选项。没有它,ln会创建硬链接,给文件增加真实的文件名。新的文件名具有旧文件名的状态;它直接指向(链接)文件数据,而不是像符号链接那样指向另一个文件名。硬链接可能比符号链接更令人困惑。除非你了解第4.6节的内容,否则要避免使用它们。

有了所有这些关于符号链接的警告,你可能想知道为什么有人会想使用它们。事实证明,它们的缺陷大大超过了它们为组织文件所提供的力量,以及它们轻松修补小问题的能力。一个常见的使用情况是,当一个程序期望找到一个特定的文件或目录,而这个文件或目录已经存在于你系统的其他地方。你不想做一个拷贝,如果你不能改变程序,你可以直接从它那里创建一个符号链接到实际的文件或目录位置。

2.18 归档和压缩文件

现在你已经了解了文件、权限和可能的错误,你需要掌握gzip和tar,这两个常用的工具用于压缩和捆绑文件和目录。

2.18.1 gzip

gzip(GNU Zip)这个程序是目前标准的 Unix 压缩程序之一。以.gz结尾的文件是GNU Zip压缩文件。使用 gunzip file.gz 来解压缩 .gz 并移除后缀;要再次压缩该文件,使用 gzip file。

2.18.2 tar

与其他操作系统的 ZIP 程序不同,gzip 不创建文件的档案;也就是说,它不把多个文件和目录打包成文件。要创建归档文件,用tar代替:

$ tar cvf archive.tar file1 file2 ...

用tar创建的归档文件通常有.tar后缀(这是惯例,不是必须的)。例如,在前面的命令中,file1、file2等是你希望归档在.tar中的文件和目录的名称。 c标志激活了创建模式。v和f标志有更具体的作用。
v 标志激活了粗略的诊断输出,使 tar 在遇到文件和目录时打印它们的名字。添加另一个v会使tar打印细节,如文件大小和权限。如果你不想让tar告诉你它在做什么,省略v标志。
f标志表示文件选项。命令行中f标志后的下一个参数必须是供tar创建的归档文件(在前面的例子中,它是.tar)。你必须在任何时候都使用这个选项,后面跟一个文件名,除非是磁带机。要使用标准输入或输出,将文件名设为破折号(-)。

要用tar解压.tar文件,使用x标志:

$ tar xvf archive.tar

在这个命令中,x标志使tar进入提取(解包)模式。你可以通过在命令行末尾输入各个部分的名称来提取档案的各个部分,但你必须知道它们的确切名称。(要确定这一点,请看接下来描述的内容表模式)。

注意:当使用提取模式时,记住tar在提取内容后不会删除存档的.tar文件。

在解压之前,用内容表模式检查 .tar 文件的内容通常是个好主意,使用 t 标志而不是 x 标志。这种模式会验证归档文件的基本完整性,并打印出里面所有文件的名称。如果你不在解压前测试归档文件,你可能最终会把一大堆乱七八糟的文件倒入当前目录,这可能真的很难清理。
当你用t模式检查归档文件时,要确认所有的文件都在合理的目录结构中;也就是说,归档文件中的所有文件路径名都应该以同一个目录开始。如果你不确定,可以创建一个临时目录,换到这个目录,然后再解压。(如果归档文件没有造成混乱,你总是可以使用mv * ...)。
当解压时,考虑使用p选项来保留权限。在解压缩模式下使用这个选项,可以覆盖你的umask,得到存档中指定的确切权限。当你以超级用户身份工作时,p选项是默认的。如果你在以超级用户身份解压归档文件时遇到权限和所有权方面的问题,请确保你一直等到命令终止并得到shell提示。尽管你可能只想解压缩文件的一小部分,但tar必须运行整个文件,你不能打断这个过程,因为它只有在检查了整个压缩文件后才设置权限。
把本节中所有的tar选项和模式都记在脑子里。如果你有困难,做一些闪存卡。这可能听起来像小学生,但避免这个命令的粗心错误是非常重要的。

2.18.3 压缩档案(.tar.gz)

许多初学者发现档案通常是压缩的,文件名以.tar.gz结尾,这让他们感到困惑。要解开压缩档案,要从右边到左边;先摆脱.gz,然后再担心.tar。 例如,这两个命令解压和解开.tar.gz:

$ gunzip file.tar.gz
$ tar xvf file.tar

刚开始的时候,一次做一个步骤就可以了,先运行gunzip来解压,然后运行tar来验证和解包。要创建一个压缩档案,做相反的事情:先运行tar,然后再运行gzip。经常这样做,你很快就会记住归档和压缩过程是如何进行的。但是,即使你不经常这样做,你也可以看到所有的输入会变得多么令人厌烦,你会开始寻找捷径。现在让我们来看看这些。

2.18.4 zcat

刚才的方法并不是在压缩档案中调用tar的最快或最有效的方法,它浪费了磁盘空间和内核I/O时间。更好的方法是将归档和压缩功能与一个流水线结合起来。例如,这个命令流水线解压.tar.gz:

$ zcat file.tar.gz | tar xvf -

zcat命令与gunzip -dc相同。-d选项解压,-c选项将结果发送到标准输出(在本例中,发送到tar命令)。
因为使用zcat非常普遍,Linux中的tar版本有一个快捷方式。你可以用z作为选项,在归档文件上自动调用gzip;这对提取归档文件(用tar的x或t模式)和创建归档文件(用c)都有效。例如,用下面的方法来验证压缩的存档:

$ tar ztvf file.tar.gz
$ tar zxvf file.tar.gz # 解压

然而,试着记住,在使用这个快捷方式时,你实际上是在执行两个步骤。

注意:.tgz文件和.tar.gz文件是一样的。这个后缀是为了适应FAT(基于MS-DOS)文件系统。

2.18.5 其他压缩工具

还有两个压缩程序是xz和bzip2,其压缩文件分别以.xz和.bz2结尾。虽然比gzip稍慢,但这些程序通常会将文本文件压缩得更多一些。要使用的解压程序是unxz和bunzip2,这两个程序的选项与它们的gzip对应程序足够接近,你不需要学习任何新东西。
大多数Linux发行版都带有与Windows系统上的ZIP档案兼容的zip和unzip程序。它们适用于通常的.zip文件,以及以.exe结尾的自解压档案。但是,如果你遇到一个以.Z结尾的文件,你就发现了一个由压缩程序创建的遗迹,它曾经是Unix的标准。gunzip程序可以解压这些文件,但gzip不会创建它们。

2.19 Linux目录层次结构要点

现在你知道了如何检查文件、改变目录和阅读手册页,你准备开始探索你的系统文件和目录。Linux目录结构的细节在文件系统层次标准或FHS(https://refspecs.linuxfoundation.org/fhs.shtml)中作了概述,但现在简要介绍一下就足够了。
图2-2提供了层次结构的简化概览,显示了/、/usr和/var下的一些目录。请注意,/usr下的目录结构包含一些与/相同的目录名称。

图2-2:Linux目录层次结构
下面是根目录中最重要的子目录:

  • /bin 包含随时可以运行的程序(也称为可执行程序),包括大多数基本的Unix命令,如ls和cp。/bin中的大多数程序都是二进制格式,由C语言编译器创建,但有些是现代系统中的shell脚本。
  • /dev 包含设备文件。你将在第3章中进一步了解这些文件。
  • /etc 这个核心系统配置目录(发音为EHT-see)包含用户密码、启动、设备、网络和其他设置文件。
  • /home 存放普通用户的家庭(个人)目录。大多数Unix安装都符合这个标准。
  • /lib 是library的缩写,这个目录存放包含可执行文件可以使用的代码的库文件。有两种类型的库:静态和共享。/lib目录应该只包含共享库,但其他lib目录,如/usr/lib,包含这两种类型以及其他辅助文件。(我们将在第15章详细讨论共享库)。
  • /proc 通过一个可浏览的目录和文件界面提供系统统计数据。Linux上的/proc子目录结构大部分是独特的,但许多其他Unix变体也有类似的功能。/proc目录包含了关于当前运行进程的信息以及一些内核参数。
  • /run 包含系统特有的运行数据,包括某些进程ID、套接字文件、状态记录,以及在许多情况下,系统日志。这是最近才添加到根目录中的;在旧系统中,你可以在/var/run中找到它。在较新的系统中,/var/run是指向/run的符号链接。
  • /sys 这个目录与/proc类似,它提供了设备和系统接口。你将在第3章中阅读更多关于/sys的内容。
  • /sbin 系统可执行程序的地方。/sbin目录中的程序与系统管理有关,所以普通用户通常不会在他们的命令路径中设置/sbin组件。如果不以root身份运行,这里的许多实用程序就无法工作。
  • /tmp 是存放较小的、你不太关心的临时文件的区域。任何用户都可以读取和写入/tmp,但该用户可能没有权限访问其他用户的文件。许多程序使用这个目录作为工作区。如果某些东西非常重要,不要把它放在/tmp里,因为大多数发行版在机器启动时都会清除/tmp,有些甚至会定期删除其旧文件。另外,不要让/tmp被垃圾填满,因为它的空间通常与一些重要的东西共享(例如,/的其他部分)。
  • /usr 虽然读作 "用户",但这个子目录没有用户文件。相反,它包含大的目录层次,包括Linux系统的大部分内容。/usr中的许多目录名称与根目录中的目录名称相同(如/usr/bin和/usr/lib),而且它们保存着相同类型的文件。(根目录不包含完整的系统的原因主要是历史原因--在过去,这是为了保持根目录的低空间要求)。
  • /var 变量子目录,程序在这里记录可能随时间变化的信息。系统日志、用户跟踪、缓存和其他系统程序创建和管理的文件都在这里。(你会注意到这里有一个/var/tmp目录,但系统在启动时并不擦除它)。

2.19.1 其他根子目录

在根目录下还有一些其他有趣的子目录:

  • /boot 包含内核启动加载器文件。这些文件只与Linux启动程序的第一阶段有关,所以你不会在这个目录中找到关于Linux如何启动其服务的信息。关于这一点,请看第5章。
  • /media 许多发行版中都有可移动媒体(如闪存驱动器)的基本连接点。
  • /opt 这可能包含额外的第三方软件。许多系统不使用/opt。

2.19.2 /usr目录

乍一看,/usr目录可能看起来比较干净,但快速浏览一下/usr/bin和/usr/lib就会发现这里有很多东西;/usr是大多数用户空间程序和数据所在的地方。除了/usr/bin、/usr/sbin和/usr/lib之外,/usr还包括以下内容:

  • /include 存放C语言编译器使用的头文件。
  • /local 是管理员可以安装自己的软件的地方。它的结构应该与/和/usr的结构相似。
  • /man 包含手册页面。
  • /share 包含的文件应该可以在其他类型的Unix机器上使用而不损失功能。这些通常是程序和库在必要时读取的辅助数据文件。在过去,机器网络会从文件服务器上共享这个目录,但今天以这种方式使用的共享目录已经很少了,因为在当代系统上对这些类型的文件没有实际的空间限制。相反,在Linux发行版上,你会发现/man、/info和其他许多子目录在这里,因为这是容易理解的惯例。

2.19.3 内核位置

在Linux系统中,内核通常是二进制文件/vmlinuz或者/boot/vmlinuz。引导加载器将这个文件加载到内存中,并在系统启动时将其启动。(你会在第5章中找到关于引导装载器的细节)。
一旦 Boot Loader 启动了内核,主内核文件就不再被运行中的系统使用。然而,你会发现在正常的系统运行过程中,内核会根据需要加载和卸载许多模块。它们被称为可加载的内核模块,位于/lib/modules下。

2.20 以超级用户身份运行命令

在进一步研究之前,你应该学习如何以超级用户身份运行命令。你可能很想启动root shell,但是这样做有很多缺点:

  • 没有改变系统的命令的记录。
  • 没有执行改变系统的命令的用户的记录。
  • 不能访问你的正常shell环境。
  • 必须输入root密码(如果你有的话)。

2.20.1 sudo

大多数发行版使用一个叫做sudo的软件包,允许管理员在以自己身份登录时以root身份运行命令。例如,在第7章中,你将学习如何使用vipw来编辑/etc/passwd文件。你可以这样做:

$ sudo vipw

当你运行这个命令时,sudo会在local2设施下的syslog服务中记录这个动作。你还将在第7章中学习更多关于系统日志的知识。

2.20.2 /etc/sudoers

当然,系统不会让任何用户作为超级用户运行命令;你必须在/etc/sudoers文件中配置特权用户。sudo软件包有许多选项(你可能永远不会用到),这使得/etc/sudoers中的语法有些复杂。例如,这个文件赋予用户1和用户2以root身份运行任何命令的权力,而不需要输入密码:

User_Alias ADMINS = user1, user2

ADMINS ALL = NOPASSWD: ALL

root ALL=(ALL) ALL

第一行定义了有两个用户的ADMINS用户别名,第二行授予权限。ALL = NOPASSWD: ALL部分意味着ADMINS别名中的用户可以使用sudo来作为root执行命令。第二个ALL表示 "任何命令"。第一个ALL的意思是 "任何主机"。(如果你有不止一台机器,你可以为每台机器或每组机器设置不同的访问权限,但我们不会介绍这个功能)。
root ALL=(ALL) ALL仅仅意味着超级用户也可以使用sudo来运行任何主机上的任何命令。额外的(ALL)意味着超级用户也可以作为任何其他用户运行命令。你可以通过在第二行/etc/sudoers中加入(ALL),将这个权限扩展到ADMINS用户,如图所示:

ADMINS ALL = (ALL) NOPASSWD: ALL

注意:使用visudo命令来编辑/etc/sudoers。该命令在你保存文件后检查文件的语法错误。

2.20.3 sudo日志

尽管我们将在本书后面更详细地讨论日志,但你可以用这个命令在大多数系统上找到sudo日志:

$ journalctl SYSLOG_IDENTIFIER=sudo

在旧系统上,你需要在/var/log中寻找日志文件,例如/var/log/auth.log。
这就是目前sudo的情况。如果你需要使用它更多的高级功能,请参阅sudoers(5)和sudo(8)手册。(用户切换的实际机制将在第7章中介绍)。

2.21 展望未来

你现在应该知道如何在命令行上做以下事情:运行程序、重定向输出、与文件和目录交互、查看进程列表、查看手册页面,以及通常在Linux系统的用户空间中进行操作。你还应该能够以超级用户的身份运行命令。你可能还不太了解用户空间组件的内部细节或内核中发生的事情,但随着文件和进程的基础知识的掌握,你已经在路上了。在接下来的几章中,你将使用刚刚学到的命令行工具来处理内核和用户空间的系统组件。