学习Shell
8、高级键盘技巧
8.1、编辑命令行
Bash使用了一个名为Readline的库(可供不同程序使用的共享例程集合)来实现命令行编辑。我们先前已经介绍过部分相关特性。例如,通过方向键可以移动光标,除此之外,还有更多的特性尚未提及。可以将其视为能够在工作中使用的附加工具,不是说必须全都学会,但是其中不少特性还是颇为实用的。挑选你需要的学习就行了。
8.1.1、光标移动
下表列举了光标移动的组合键。

8.1.2、修改文本
在输入命令的时候可能出现拼写错误,需要一种有效的纠正方法。下表列举了用于修改命令行文本的组合键。

8.1.3、剪切和粘贴文本
Readline文档使用术语killing和yanking指代我们常说的剪切和粘贴,下表列举了剪切和粘贴文本的组合键。被剪切的文本保存在名为kill-ring的缓冲区(内存中的一块临时存储区域)内。

8.2、补全功能
Shell还可以通过补全机制帮助你提高效率。在输入命令时,按Tab键就能够触发补全功能。让我们来看一看它是如何工作的。假设有如下主目录:
bash
[me@linuxbox ~]$ ls
Desktop ls-output.txt Pictures Templates Videos
Documents Music Public
输入下例内容,但不要按Enter键:
bash
[me@linuxbox ~]$ ls l
现在按Tab键:
bash
[me@linuxbox ~]$ ls ls-output.txt
观察Shell是如何补全该行的。再看另一个例子,同样不要按Enter键:
bash
[me@linuxbox ~]$ ls D
按Tab键:
bash
[me@linuxbox ~]$ ls D
没有补全,什么都没发生。这是因为D能够匹配目录中的不止一个条目。要想顺利补全,给出的"线索"必须没有歧义。如果我们继续输入:
bash
[me@linuxbox ~]$ ls Do
再按Tab键:
bash
[me@linuxbox ~]$ ls Documents
搞定!
虽然这个例子展示的是路径名补全(这也是常见的用法),但补全功能同样适用于变量(如果单词以$开头)、用户名(如果单词以~开头)、命令(如果单词是行中的首个单词)及主机名(如果单词以@开头)。
下表列举了与补全功能关联的组合键。

除此之外,还有不少晦涩难懂的组合键。完整的列表可以在Bash手册页的READLINE一节中找到。
8.3、命令历史记录
8.3.1、搜索历史记录
你可以使用下列命令随时查看命令历史记录的内容:
bash
[me@linuxbox ~]$ history | less
Bash默认会保存最近输入的500个命令,不过现在大多数发行版会将这个值设置为1000。如果我们想找出用来列出/usr/bin目录内容的命令,可以这样做:
bash
[me@linuxbox ~]$ history | grep /usr/bin
假设在输出结果中有这么一行:
bash
88 ls -l /usr/bin > ls-output.txt
88是历史记录中该命令的编号。我们可以使用历史扩展来立即重用这个命令:
bash
[me@linuxbox ~]$ !88
Bash会将!88扩展为历史记录中第88行的内容。历史扩展还有其他形式,在后面我们会介绍。
Bash也能够以增量方式搜索历史记录。也就是说,在输入命令的时候可以告诉Bash搜索历史记录,随着输入内容的增多,逐步细化搜索结果。按Ctrl-R组合键,接着输入你要搜索的内容,可以开始增量搜索。如果找到了匹配项,可以按Enter键,或者按Ctrl-J组合键将该匹配项从历史记录中复制到当前命令行。要想查找下一个匹配项(向"上"搜索历史记录),再次按Ctrl-R组合键。若要退出搜索,按Ctrl-G或Ctrl-C组合键即可。来看下面的例子:
bash
[me@linuxbox ~]$
按Ctrl-R组合键:
bash
(reverse-i-search)'':
提示符发生了变化,提示我们正在执行反向增量搜索。之所以称为"反向"(reverse),是因为查找的是过去的内容。接下来,输入要搜索的文本。在这个例子中,我们要搜索/usr/bin:
bash
(reverse-i-search)' / usr/bin': ls -l /usr/bin > ls-output.txt
搜索结果立刻就出现了。你可以按Enter键直接执行该命令,或是按Ctrl-J组合键将命令复制到当前命令行做进一步编辑。我们选择复制命令:
bash
[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt
返回Shell提示符,准备执行复制好的命令!
下表列举了部分可用于操作命令历史记录的组合键。

8.3.2、历史扩展
Shell通过!提供了一种专门针对历史记录的扩展类型。我们先前已经看到在!之后加上数字可以插入某条历史记录。下表列举了一些其他的历史扩展命令。

除非你十分确定历史记录的内容,否则在使用!string和!?string时务必要小心。
历史扩展机制的内容可远不止这些,但这个主题过于晦涩难懂,如果再继续下去,你估计就要"疯"了,所以就此打住。你可以在Bash手册页的HISTORY EXPANSION一节中找到所有与此相关的细枝末节。
script
除了Bash的命令历史记录特性,大多数Linux发行版还包含一个名为script的程序,它可用于记录整个Shell会话并将其保存在文件中。script的基本语法如下:
bash
script file
其中,file是会话记录文件。如果没有指定,则使用文件typescript。完整的选项和特性参见script的手册页。
9、权限
9.1、属主、属组以及其他用户
我们在前面,检查/etc/shadow文件时碰到过一个问题:
bash
[me@linuxbox ~]$ file /etc/shadow
/etc/shadow: regular file, no read permission
[me@linuxbox ~]$ less /etc/shadow
/etc/shadow: Permission denied
错误原因在于,作为普通用户,没有读取该文件的权限。
在UNIX安全模型中,用户可以拥有文件和目录。如果文件或目录属于某个用户,该用户(属主)拥有访问权。用户反过来可以属于由一个或多个用户组成的组(属组),属组用户被文件和目录的属主授予访问权。除了为属组授权,属主还可以为所有用户(用UNIX术语来说,这叫作世界,即world)授权。要想查看用户身份信息,可以使用id命令:
java
[me@linuxbox ~]$ id
uid=500(me) gid=500(me) groups=500(me)
来看一下输出结果。在创建用户时,会为用户分配一个称为用户ID(uid)的数字,为了便于使用,uid又被映射为用户名。用户还会得到一个属组ID(gid),还可以归属于其他属组。先前的例子取自Fedora系统。在其他系统中(如Ubuntu),输出结果可能会略有不同:
bash
[me@linuxbox ~]$ id
uid=1000(me) gid=1000(me) groups=4(adm),20(dialout),24(cdrom),25(floppy),
29(audio),30(dip),44(video),46(plugdev),108(lpadmin),114(admin),1000(me)
我们可以发现,在Ubuntu中,用户的uid和gid是不同的。原因很简单,在Fedora系统中,普通用户的编号从500开始,而在Ubuntu系统中则从1000开始编号。同时我们也发现,Ubuntu用户归属于更多属组。这与Ubuntu的系统设备和服务的权限管理方式有关。
这些信息从何而来?和Linux中的很多东西一样,它来自几个文本文件。属主定义在/etc/passwd文件中,属组定义在/etc/group文件中。在创建用户和属组时,这些文件随/etc/shadow文件一并改动,后者保存了用户的密码信息。对于每一个用户,/etc/passwd文件定义了用户(登录)名、uid、gid、用户的真实姓名、主目录以及登录Shell。如果检查/etc/passwd文件和/etc/group文件的内容,我们会发现除普通用户之外,还有超级用户(uid为0)和各类系统用户的信息。
尽管很多类UNIX系统将普通用户分配到一个共同属组(例如users组),但现代Linux的做法是为每个用户创建一个与用户同名的单成员属组。这使某些类型的授权更加容易。
9.2、读取、写入和执行
文件和目录的访问权限是按照读取、写入、执行来定义的。如果观察ls命令的输出结果,就可以看出一些"门道":
bash
[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-r-- 1 me me 0 2018-03-06 14:52 foo.txt
列表项的前10个字符是文件属性。其中,第1个字符是文件类型。下表描述了常见的文件类型。

文件属性中剩下的9个字符是文件模式,分别代表文件属主、文件属组、其他用户的读取、写入、执行权限。
下表描述了在文件和目录上设置r、w、x权限属性后的效果。

下表给出了一些文件属性示例。

9.2.1、chmod---修改文件模式
chmod命令可以修改文件或目录的模式(权限)。注意,只有文件属主或超级用户才能做到。chmod命令支持两种截然不同的模式表示方式。
- 八进制表示法。
- 符号表示法。
我们先来讲八进制数描述。在八进制表示法中,我们使用八进制数设置需要的权限模式。每个八进制数位可以描述3个二进制数位,这正好和文件模式的记录方式对应。下表展示了这种关系。

通过使用3个八进制数位,我们可以为属主、属组及其他用户设置文件模式:
bash
[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-r-- 1 me me 0 2018-03-06 14:52 foo.txt
[me@linuxbox ~]$ chmod 600 foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw------- 1 me me 0 2018-03-06 14:52 foo.txt
使用600作为参数,我们将属主的权限设置为可读取和写入,同时去除属组和其他用户的所有权限。尽管要记忆八进制和二进制之间的映射关系可能显得不太方便,但其实常用的也就那几个而已:7(rwx)、6(rw-)、5(r-x)、4(r--)、0(---)。
chmod命令也支持文件模式的符号表示法。符号表示法分为3个部分。
- 改动会影响到谁。
- 执行什么操作。
- 设置什么权限。
可以通过字符u、g、o、a的组合来指定要影响的对象,如下表所示。

如果没有指定字符,则使用all。操作符+表示添加权限,−表示去除权限,=表示只赋予指定权限,同时去除其他所有的权限。
使用字符r、w、x来指定权限。表9-6给出了一些符号表示法示例。

有些人偏好八进制表示法,有些人钟爱符号表示法。符号表示法的优势在于能够在不干扰其他用户权限的情况下,设置某类用户的权限。
更多细节和选项参见chmod命令的手册页。要小心--recursive选项:它对文件和目录都起作用,该选项并不如想象中的那么有用,因为我们很少会给文件和目录设置相同的权限。
9.2.2、使用GUI设置文件模式
现在,我们已经知道了如何设置文件和目录的权限,这样就可以更好地理解GUI中的权限设置了。在File(GNOME)和Dolphin(KDE)中,右击文件或者目录图标都将会弹出一个属性(Properties)窗口。下图取自GNOME,我们可以从中看到属主(Owner)、属组(Group)、其他用户(Others)的相关设置。

9.2.3、umask---设置默认权限
umask命令设置文件创建时的默认权限。该命令使用八进制表示法描述了从文件模式属性中去除权限的位掩码。来看下面的例子:
bash
[me@linuxbox ~]$ rm -f foo.txt
[me@linuxbox ~]$ umask
0002
[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-r-- 1 me me 0 2018-03-06 14:53 foo.txt
先删除foo.txt文件先前的所有副本,恢复到初始状态。接着,执行不包含任何参数的umask命令,查看当前的掩码值。输出结果为0002(0022是另一个常用的默认值),它是掩码的八进制表示形式。接着创建foo.txt文件的一个新实例,查看该文件的权限。
可以发现,文件属主和属组都获得了读取/写入权限,而其他用户仅获得读取权限。其他用户没有写入权限的原因在于掩码值。重复这个示例,这次我们自己设置掩码值:
bash
[me@linuxbox ~]$ rm foo.txt
[me@linuxbox ~]$ umask 0000
[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-rw- 1 me me 0 2018-03-06 14:58 foo.txt
将掩码设置为0000时(实际上是关闭掩码),可以看到其他用户也拥有了文件的写入权限。为了理解它是如何实现的,再来看一看八进制数。如果把该掩码展开成二进制形式,再与属性进行对比,就能明白是怎么回事了。
先忽略最前面的那几个0(很快就会讲到),我们发现,掩码中出现1的地方对应位置上的权限(在本例中,是其他用户的写入权限)都被去除了。这正是掩码的作用。掩码的二进制值中的1表示去除对应的权限。如果观察掩码值0022,就可以看出其作用。
同样,二进制值中出现1的地方对应位置上的权限都被去除了。可以试一试其他掩码值(例如一些带数字7的值),学习掩码的工作方式。练习完成后,记得清理现场,恢复到默认值:
bash
[me@linuxbox ~]$ rm foo.txt; umask 0002
大多数情况下,我们无须修改掩码值,Linux发行版提供的默认值就很好了。但在一些高安全级别的系统下,则需要控制掩码值。
9.2.4、一些特殊的权限
虽然通常看到的八进制权限掩码都表示为3位数字,但从技术上而言,它是用4位数字来表示的。原因在于,除读取、写入、执行权限之外,还有其他一些较少用到的权限设置。
第一个是setuid位(八进制表示为4000)。如果将其应用于可执行文件,会将有效用户ID(effective user ID)从真实用户(实际执行程序的用户)ID更改为程序属主的有效用户ID。大多数情况下,少数超级用户的程序才会做此设置。当普通用户执行setuid root程序时,该程序将以超级用户的权限来执行,可以访问普通用户通常被禁止访问的文件和目录。这显然会带来安全隐患,因此允许设置setuid位的程序数量必须控制在最小范围内。
第二个是setgid位(八进制表示为2000)。类似于setuid位,它会将有效组ID(effective group ID)从真实用户的真实组ID(real group ID)更改为文件属主的有效组ID。如果对目录设置setgid位,那么在该目录下新创建的文件将由该目录的属组所有,而非文件创建者的属组所有。这在共享目录中非常有用,当一个共同属组的成员需要访问目录中的所有文件时,不用管文件属主的属组是什么。
第三个是粘滞(sticky)位(八进制表示为1000)。这是来自"远古"UNIX的遗留产物,用于将可执行文件标记为"不可交换"。Linux会忽略文件上设置的粘滞位,如果对目录设置了粘滞位,则能够阻止用户删除或者重命名其中的文件,除非用户是该目录的属主,或者是文件的属主,又或者是超级用户。粘滞位常用来控制对共享目录(如/tmp)的访问。
下面是几个chmod命令示例,其中使用了符号表示法设置这些特殊权限。首先,为程序设置setuid位:
bash
chmod u+s program
然后,为目录设置setgid位:
bash
chmod g+s dir
最后,为目录设置粘滞位:
bash
chmod +t dir
查看ls命令的输出结果,确定这些特殊权限。首先是设置过setuid位的程序:
bash
-rwsr-xr-x
然后是设置过setgid位的目录:
bash
drwxrwsr-x
最后是设置过粘滞位的目录:
bash
drwxrwxrwt
9.3、改变用户身份
在很多时候,我们会发现有必要采用其他用户的身份。为了执行某些管理任务,我们经常需要获取超级用户权限,不过也可以"变成"另一个普通用户来完成账户测试之类的事情。改变身份的方法有3种。
- 注销,然后以其他用户身份重新登录。
- 使用su命令。
- 使用sudo命令。
第一种方法我们就跳过不讲了,大家都知道怎么做,而且它也不如其他两种方法来得方便。在Shell会话中,su命令允许你"扮演"其他用户,以该用户的ID启动新的Shell会话或是以该用户的身份执行单个命令。sudo命令允许超级用户设置名为/etc/sudoer的配置文件,定义允许特定用户在假定的身份下执行特定的命令。选择使用的命令在很大程度上取决于你所用的Linux发行版。你的Linux发行版可能包括这两个命令,但是其配置可能偏向于其中一个。我们先来介绍su命令。
9.3.1、su---以其他用户身份启动Shell
su命令能够以其他用户的身份启动Shell。其用法如下:
bash
su [-[l]] [user]
如果加入了-l选项,则启动的Shell会话将作为指定用户的登录Shell。这意味着会加载用户环境并将工作目录更改为该用户的主目录。这通常也是我们想要的结果。如果没有指定用户,那么假定其为超级用户。注意,-l可以缩写为-,而且这还是常用的写法。我们可以通过以下的操作来为超级用户启动一个Shell:
bash
[me@linuxbox ~]$ su -
Password:
[root@linuxbox ~]#
在输入su命令后,系统会提示输入超级用户密码。如果输入正确,会出现新的Shell提示符,表明该Shell拥有超级用户权限(提示符的末尾字符是#,而不是$),当前工作目录同时也更改为超级用户的主目录(通常为/root)。一旦进入新Shell,我们就能够以超级用户的身份执行命令了。Shell使用结束时,输入exit,返回之前的Shell环境:
bash
[root@linuxbox ~]# exit
[me@linuxbox ~]$
我们也可以使用su命令执行单个命令,而不用启动一个全新的交互式Shell:
bash
su -c 'command'
采用这种形式,该命令行被传入新Shell执行。一定记得将命令放进引号,这一点很重要,因为我们不希望扩展发生在当前Shell中,而是要留到新Shell中扩展:
bash
[me@linuxbox ~]$ su -c 'ls -l /root /*'
Password:
-rw------- 1 root root 754 2007-08-11 03:19 /root/anaconda-ks.cfg
/root/Mail:
total 0
[me@linuxbox ~]$
9.3.2、sudo---以其他用户身份执行命令
sudo命令在很多方面类似于su命令,但是它还有另外一些重要功能。超级用户可以配置sudo命令,允许普通用户在可控的方式下以其他用户的身份(通常是超级用户)执行命令。在特定情况下,用户可能被限制为只能执行一条或者几条特定的命令,其他命令一概被禁止。另一个重要的区别在于,sudo命令无须输入超级用户密码。使用sudo命令时,用户只需输入自己的密码来进行认证即可。例如,已配置好的sudo命令允许我们执行一个假想的备份程序backup_script,该程序需要超级用户权限。可以这样使用sudo命令来完成:
bash
[me@linuxbox ~]$ sudo backup_script
Password:
System Backup Starting...
在输入命令后,系统会提示用户输入自己的密码(而不是超级用户的密码),只要通过认证,就执行指定的命令。su命令和sudo命令之间的一个重要区别在于,sudo命令不需要启动新Shell,也不用加载其他用户的环境。这意味着,使用sudo命令的时候无须引用命令。注意,可以通过指定不同的选项来改变这种行为。还要注意的是,sudo命令的-i选项可用于启动一个交互式的超级用户Shell会话(和su -差不多)。更多详情可参见sudo命令的手册页。
要想知道sudo命令授予了哪些权限,可以使用-l选项来查看:
bash
[me@linuxbox ~]$ sudo -l
User me may run the following commands on this host:
(ALL) ALL
9.3.3、chown---更改文件属主和属组
chown命令可用于更改文件属主和属组。该命令需要超级用户权限。其用法如下:
bash
chown [owner][:[group]] file...
chown命令能够根据第一个参数更改文件属组和文件属组。下表给出了几个示例。

假设有两个用户janet和tony,前者拥有超级用户权限,后者没有。janet想把自己主目录中的一个文件复制到tony的主目录中。因为janet希望tony能编辑该文件,所以将文件的属主由janet更改为tony:
bash
[j anet@linuxbox ~]$ sudo cp myfile.txt ~tony
Password:
[j anet@linuxbox ~]$ sudo ls -l ~tony/myfile.txt
-rw-r--r-- 1 root root root 2018-03-20 14:30 /home/tony/myfile.txt
[j anet@linuxbox ~]$ sudo chown tony: ~tony/myfile.txt
[j anet@linuxbox ~]$ sudo ls -l ~tony/myfile.txt
-rw-r--r-- 1 tony tony tony 2018-03-20 14:30 /home/tony/myfile.txt
可以看到,janet把文件从自己的主目录复制到了tony的主目录。接着,janet将文件属主由root(使用sudo命令的结果)改为tony。在第一个参数结尾使用冒号,janet还将文件的属组更改为tony的登录组,也就是tony组。
注意,第一次使用sudo命令之后,janet并未被提示输入密码。这是因为在大多数配置中,sudo命令在超时之前,会"信任"你几分钟。
9.3.4、chgrp---更改文件属组
在较旧的UNIX版本中,chown命令只能更改文件属主,但不能更改文件属组。为此,得使用单独的chgrp命令。其用法和chown命令大同小异,只是限制更多。
9.4、行使权限
既然我们已经知道了权限是如何工作的,那么是该学以致用了。我们来演示一个常见问题的解决方案:设置共享目录。假设有两个用户Bill和Karen,二人都收集了各种音乐,想设置一个共享目录,在其中以Ogg Vorbis或MP3格式存放各自的音乐。Bill通过sudo命令获得超级用户权限。
第一件需要做的事情就是创建一个以Bill和Karen为成员的组。Bill使用图形化用户管理工具创建了一个名为music的组,将Bill和Karen添加到该组中,如图所示。

接着,Bill要为音乐文件创建目录:
java
[bill@linuxbox ~]$ sudo mkdir /usr/local/share/Music
Password:
因为Bill在自己的主目录之外操作文件,所以需要超级用户权限。创建好的目录具有下列所有权和权限:
bash
[bill@linuxbox ~]$ ls -ld /usr/local/share/Music
drwxr-xr-x 2 root root 4096 2018-03-21 18:05 /usr/local/share/Music
可以看出,该目录由root所有,权限模式为755。要想共享该目录,bill需要修改属组和相应权限,以允许写入:
bash
[bill@linuxbox ~]$ sudo chown :music /usr/local/share/Music
[bill@linuxbox ~]$ sudo chmod 775 /usr/local/share/Music
[bill@linuxbox ~]$ ls -ld /usr/local/share/Music
drwxrwxr-x 2 root music 4096 2018-03-21 18:05 /usr/local/share/Music
这都是什么意思?这意味着现在有了一个/usr/local/share/Music目录,属主是root,music组拥有该目录的读取和写入权限。Bill和Karen是music组的成员,两者能够在/usr/local/share/Music目录中创建文件。其他用户可以列出目录内容,但是无法在其中创建文件。
但还有一个问题。按照当前的权限设置,在Music目录中创建的文件和子目录拥有的是Bill和Karen的权限:
bash
[bill@linuxbox ~]$ > /usr/local/share/Music/test_file
[bill@linuxbox ~]$ ls -l /usr/local/share/Music
-rw-r--r-- 1 bill bill 0 2018-03-24 20:03 test_file
其实是两个问题。首先,系统默认的掩码是0022,这使属组成员无法对属组内其他成员的文件执行写入操作。如果共享目录中只包含文件,这倒不是问题,但是因为该目录下存放的是音乐,而音乐一般都是按照歌手和专辑来进行层次化组织分类的,属组成员需要能够在由同属组其他成员创建的目录中创建文件和子目录,所以把Bill和Karen使用的掩码更改成0002。
其次,由成员创建的每个文件和目录的属组都被设置为该用户的主组(primary group),而不是music组。这可以通过设置目录的setgid位来纠正:
bash
[bill@linuxbox ~]$ sudo chmod g+s /usr/local/share/Music
[bill@linuxbox ~]$ ls -ld /usr/local/share/Music
drwxrwsr-x 2 root music 4096 2018-03-24 20:03 /usr/local/share/Music
现在来测试一下,看一看新权限是否有效。Bill将其掩码设置为0002,删除之前的测试文件,又重新创建新的测试文件和目录:
bash
[bill@linuxbox ~]$ umask 0002
[bill@linuxbox ~]$ rm /usr/local/share/Music/test_file
[bill@linuxbox ~]$ > /usr/local/share/Music/test_file
[bill@linuxbox ~]$ mkdir /usr/local/share/Music/test_dir
[bill@linuxbox ~]$ ls -l /usr/local/share/Music
drwxrwsr-x 2 bill music 4096 2018-03-24 20:24 test_dir
-rw-rw-r-- 1 bill music 0 2018-03-24 20:22 test_file
[bill@linuxbox ~]$
这次创建的文件和目录都具有正确的权限,允许music组的所有成员在Music目录中创建文件和子目录。
9.5、修改密码
最后一个主题就是如何为自己设置密码(如果拥有超级用户权限,也可以为其他用户设置密码)。这可以通过passwd命令实现。该命令的用法如下:
bash
passwd [user]
要想修改你的密码,只需要输入passwd命令即可。接下来会提示你输入旧密码,再输入新密码:
bash
[me@linuxbox ~]$ passwd
(current) UNIX password:
New UNIX password:
passwd命令会尝试强制使用"强"密码。也就是说,太短的密码、与旧密码过于相似的密码、字典中的单词,以及太容易被猜到的密码都会被拒绝使用:
bash
[me@linuxbox ~]$ passwd
(current) UNIX password:
New UNIX password:
BAD PASSWORD: is too similar to the old one
New UNIX password:
BAD PASSWORD: it is WAY too short
New UNIX password:
BAD PASSWORD: it is based on a dictionary word
如果你拥有超级用户权限,则可以指定用户名作为passwd的参数,为其他用户设置密码。超级用户还可以使用其他选项锁定账户、使密码过期等,详见passwd命令的手册页。
10、进程
10.1、进程的工作方式
系统启动时,内核会以进程为形式展开一些自身的操作活动并运行一个名为init的程序。init再依次运行一系列初始化脚本(位于/etc),由这些脚本启动所有的系统服务。很多服务是作为守护程序(daemon program)实现的,此类程序在后台做自己的工作,没有任何用户界面。因此,就算我们没有登录,系统也在忙于运行一些例行工作。
一个程序能够启动其他程序,用进程的话来说,就是父进程生成子进程。
内核维护着每个进程的相关信息,以确保组织有序。例如,每个进程都被分配了一个叫作进程ID(Process ID,PID)的数字。PID是按照递增顺序分配的,init的PID始终为1。内核还记录分配给每个进程的内存和用来恢复进程运行的就绪信息。和文件类似,进程有属主、用户ID、有效用户ID等。
10.2、查看进程
常用的进程查看命令(不止一个)是ps命令。该命令选项众多,最简单的形式如下:
bash
[me@linuxbox ~]$ ps
PID TTY TIME CMD
5198 pts/1 00:00:00 bash
10129 pts/1 00:00:00 ps
这个例子的输出结果列出了两个进程:进程5198和进程10129,分别对应于bash和ps。如我们所见,在默认情况下,ps命令显示的信息并不多,只输出与当前终端会话关联的进程信息。为了获得更多信息,我们需要添加一些选项,但是在此之前,先来看一下ps命令输出的其他字段。TTY(电传打字机,teletype的缩写),指的是进程的控制终端。此处也反映了UNIX的悠久历史。TIME字段是进程消耗的CPU时间总和。可以看出,这两个进程都没有加重计算机的负载。
添加一个选项后,我们就能够更全面地了解系统当前的工作状态:
bash
[me@linuxbox ~]$ ps x
PID TTY STAT TIME COMMAND
2799 ? Ssl 0:00 /usr/libexec/bonobo-activation-server --ac
2820 ? Sl 0:01 /usr/libexec/evolution-data-server-1.10 --
15647 ? Ss 0:00 /bin/sh /usr/bin/startkde
15751 ? Ss 0:00 /usr/bin/ssh-agent /usr/bin/dbus-launch --
15754 ? S 0:00 /usr/bin/dbus-launch --exit-with-session
15755 ? Ss 0:01 /bin/dbus-daemon --fork --print-pid 4 --pr
15774 ? Ss 0:02 /usr/bin/gpg-agent -s --daemon
15793 ? S 0:00 start_kdeinit --new-startup +kcminit_start
15794 ? Ss 0:00 kdeinit Running...
15797 ? S 0:00 dcopserver --nosid
--snip--
x选项(注意,这里没有前置的连字符)会使ps命令显示所有的进程,不管这些进程是由哪个终端(如果有的话)控制的。TTY列中出现的?表示没有控制终端。通过该选项,我们可以查看当前用户的所有进程信息。
因为系统中运行着大量进程,所以ps命令产生的进程列表会特别长。将ps命令的输出结果通过管道传给less程序可以便于我们查看。有些选项的组合也会产生不短的输出行,将终端仿真器的窗口最大化也个好主意。
上面的输出结果中出现了一个名为STAT的新列。STAT(状态,state的缩写),该列描述了进程状态,如表所示。

进程状态之后还可能跟随其他字符,表明各种奇特的进程特征,详见ps命令的手册页。
另一组常用选项是aux(没有前置的连字符),可以输出更多进程信息:
bash
[me@linuxbox ~]$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2136 644 ? Ss Mar05 0:31 init
root 2 0.0 0.0 0 0 ? S< Mar05 0:00 [kt]
root 3 0.0 0.0 0 0 ? S< Mar05 0:00 [mi]
root 4 0.0 0.0 0 0 ? S< Mar05 0:00 [ks] root 5 0.0 0.0 0 0 ? S< Mar05 0:06 [wa]
root 6 0.0 0.0 0 0 ? S< Mar05 0:36 [ev] root 7 0.0 0.0 0 0 ? S< Mar05 0:00 [kh]
--snip--
输出结果中包含所有用户的进程。不加前置连字符的选项使ps命令按照"BSD风格"运行。Linux的ps命令能够模拟多种不同UNIX实现ps命令的行为方式。通过上述选项,我们得到了另外几列信息,如表所示。

使用top命令动态查看进程
虽然ps命令能够大量揭示出服务器的当前操作,但它提供的只是在运行ps命令那一刻的服务器状态信息。要想动态查看服务器的活动,需要使用top命令:
bash
[me@linuxbox ~]$ top
top命令会依据进程活动顺序显示持续更新(默认每3s更新一次)的系统进程列表。top这个名称源自该命令可用于查看系统中"位居前列"(top)的那些进程。top命令的输出结果分为两部分---上部分显示系统的总体状态信息,下部分显示按CPU活动排序的进程列表:
bash
top - 14:59:20 up 6:30, 2 users, load average: 0.07, 0.02, 0.00
Tasks: 109 total, 1 running, 106 sleeping, 0 stopped, 2 zombie
Cpu(s): 0.7%us, 1.0%sy, 0.0%ni, 98.3%id, 0.0%wa, 0.0%hi, 0.0%si
Mem: 319496k total, 314860k used, 4636k free, 19392k buff
Swap: 875500k total, 149128k used, 726372k free, 114676k cach
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6244 me 39 19 31752 3124 2188 S 6.3 1.0 16:24.42 trackerd
11071 me 20 0 2304 1092 840 R 1.3 0.3 0:00.14 top
6180 me 20 0 2700 1100 772 S 0.7 0.3 0:03.66 dbus-dae
6321 me 20 0 20944 7248 6560 S 0.7 2.3 2:51.38 multiloa
4955 root 20 0 104m 9668 5776 S 0.3 3.0 2:19.39 Xorg
1 root 20 0 2976 528 476 S 0.0 0.2 0:03.14 init
2 root 15 --5 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root RT --5 0 0 0 S 0.0 0.0 0:00.00 migratio
4 root 15 -5 0 0 0 S 0.0 0.0 0:00.72 ksoftirq
5 root RT -5 0 0 0 S 0.0 0.0 0:00.04 watchdog
6 root 15 -5 0 0 0 S 0.0 0.0 0:00.42 events/0
7 root 15 -5 0 0 0 S 0.0 0.0 0:00.06 khelper
41 root 15 -5 0 0 0 S 0.0 0.0 0:01.08 kblockd/
67 root 15 -5 0 0 0 S 0.0 0.0 0:00.00 kseriod
114 root 20 0 0 0 0 S 0.0 0.0 0:01.62 pdflush
116 root 15 -5 0 0 0 S 0.0 0.0 0:02.44 kswapd0
系统总体状态信息包含大量内容。下表提供了top命令信息字段。

top命令可以接收许多键盘命令,其中常用的有两个:一个是h,另一个是q。前者会显示程序的帮助界面,后者用来退出top命令。
主流的桌面环境都提供了能够显示类似于top命令输出的图形化程序(和Windows的任务管理器差不多),但是top命令要比这些图形化程序更好,这不仅因为其运行速度更快,而且在资源消耗方面也要少得多。毕竟,系统监控程序不应该拖慢监控目标的速度。
10.3、进程控制
现在我们已经知道如何查看和监控进程了,接下来就看一看如何控制进程。我们要使用一个叫作xlogo的程序作为实验对象。xlogo程序是由X Window系统(图形化程序的底层引擎)提供的一个示例程序,它只简单地显示一个包含X标识的可缩放窗口。先认识一下实验对象:
bash
[me@linuxbox ~]$ xlogo
输入命令之后,屏幕上应该会出现一个包含标识的小窗口。在某些系统中,xlogo程序可能会输出警告消息,不过可以放心忽略。
我们可以通过改变窗口的大小来验证xlogo程序是否处于运行状态。如果标识在调整后的窗口中被重新绘制了,则表明该程序正在运行。
注意,为什么Shell提示符没有返回呢?这是因为Shell正在等待程序结束,就像以前用过的其他程序一样。如果关闭xlogo窗口(见下图),提示符就又出现了。

10.3.1、中断进程
再次观察运行xlogo命令的时候会发生什么。输入xlogo命令,确保程序处于运行状态。接着返回Shell,按Ctrl-C组合键:
bash
[me@linuxbox ~]$ xlogo
[me@linuxbox ~]$
在Shell中,按Ctrl-C组合键可以中断程序。这意味着我们委婉地请求程序终止。按Ctrl-C组合键之后,xlogo窗口关闭,Shell提示符返回。
很多(但不是全部)命令行程序都可以使用这种方法中断。
10.3.2、将进程置于后台
假设我们想在不终止xlogo程序的情况下返回Shell提示符。这可以通过将程序置于后台来实现。把终端想象成拥有一个前台(表面可见的东西,例如Shell提示符)和一个后台(隐藏在表面背后的东西)。要想在运行程序时立即将其置于后台,可以在命令后面加上一个&:
bash
[me@linuxbox ~]$ xlogo &
[1] 28236
[me@linuxbox ~]$
输入命令之后,xlogo窗口出现,Shell提示符返回,但同时也输出了几个有趣的数字。这是Shell特性的一部分,称为作业控制。通过这条消息,Shell告诉我们已经启动了一个编号为1(1)的作业,其PID为28236。如果运行ps命令,就可以看到该进程:
bash
[me@linuxbox ~]$ ps
PID TTY TIME CMD
10603 pts/1 00:00:00 bash
28236 pts/1 00:00:00 xlogo
28239 pts/1 00:00:00 ps
Shell的作业控制功能也提供了相关的方法,可以查看从Shell启动的作业列表。输入jobs命令:
bash
[me@linuxbox ~]$ jobs
[1]+ Running xlogo &
结果显示,我们有一个编号为1的作业,目前处于运行状态,对应的命令是xlogo &。
10.3.3、使进程返回前台
前台的键盘输入(包括Ctrl-C组合键)对后台进程无效。要想使进程返回前台,可以像下面这样使用fg命令:
bash
[me@linuxbox ~]$ jobs
[1]+ Running xlogo &
[me@linuxbox ~]$ fg %1
xlogo
fg命令后面加上百分号和作业号(称为作业参数)。如果只有一个后台作业,作业参数可选。按Ctrl-C组合键就可以终止xlogo程序。
10.3.4、停止进程
我们有时候只是想要停止(暂停)进程,而不是将其终止。这样做通常是为了将前台进程转移到后台。为此,可以按Ctrl-Z组合键。让我们来试一下。在命令提示符后输入xlogo,先按Enter键,再按Ctrl-Z组合键:
bash
[me@linuxbox ~]$ xlogo
[1]+ Stopped xlogo
[me@linuxbox ~]$
我们可以通过调整xlogo窗口大小来验证程序是否已经被停止。可以看出,窗口此刻毫无反应。我们要么使用fg命令让程序在前台继续运行,要么使用bg命令让程序在后台恢复运行:
bash
[me@linuxbox ~]$ bg %1
[1]+ xlogo &
[me@linuxbox ~]$
和fg命令一样,如果只有一个作业,bg命令可以不指定作业参数。
如果通过命令行启动图形化程序的时候忘记在结尾添加&,那么将其从前台移入后台就显得非常方便了。
为什么我们会想要从命令行启动图形化程序?有两个原因。
- 你想要运行的程序在窗口管理器菜单中找不到,如xlogo程序。
- 通过命令行启动程序,有可能看到以图形化方式启动时所看不到的错误消息。有时候无法从图形化菜单中启动程序,但改用命令行方式启动的话,就会发现错误提示,找出问题所在。另外,有些图形化程序也包含了一些值得注意的实用命令行选项。
10.4、信号
kill命令可以"杀死"(kill)进程。这使我们能够终止需要被"杀死"的程序。来看一个例子:
bash
[me@linuxbox ~]$ xlogo &
[1] 28401
[me@linuxbox ~]$ kill 28401
[1]+ Terminated xlogo
我们先在后台执行xlogo。Shell输出了后台进程的作业参数和PID。接着,我们使用kill命令并指定了想要终止的进程PID,也可以使用作业参数(例如%1)代替PID来指定进程。
整个过程非常简单直观,事实可不止如此。确切来说,kill命令并不是"杀死"进程,而是向其发送信号。信号是系统与程序之间的通信途径之一。在使用Ctrl-C组合键和Ctrl-Z组合键的时候,我们已经见识过信号的实际效果了。当终端接收到其中某个组合键时,会向前台程序发送信号。对于Ctrl-C组合键,发送INT信号;对于Ctrl-Z组合键,发送TSTP信号。程序则"监听"(listen)信号并根据接收到的信号做出相应的操作。这种行为使程序能够在接收到终止信号时保存正在进行的工作。
10.4.1、使用kill命令向进程发送信号
kill命令可用于向进程发送信号。其语法如下:
bash
kill -signal PID...
如果未在命令行上指定信号,则默认使用TERM信号。kill命令的常用信号如表所示。

让我们来动手试一试kill命令:
bash
[me@linuxbox ~]$ xlogo &
[1] 13546
[me@linuxbox ~]$ kill -1 13546
[1]+ Hangup xlogo
在这个例子中,我们在后台启动了xlogo程序并使用kill命令向其发送HUP信号。xlogo程序被终止,Shell的输出信息表明该后台进程已接收到HUP信号。我们可能需要多按几次Enter键才能看到这条消息。注意,可以通过信号编号或者名称(包含带有SIG的名称)来指定信号:
bash
[me@linuxbox ~]$ xlogo &
[1] 13601
[me@linuxbox ~]$ kill -INT 13601
[1]+ Interrupt xlogo
[me@linuxbox ~]$ xlogo &
[1] 13608
[me@linuxbox ~]$ kill -SIGINT 13608
[1]+ Interrupt xlogo
重复先前的例子并尝试其他信号。记住,我们也可以使用作业参数代替PID。
和文件一样,进程也有属主,你必须是进程的属主(或者作为超级用户)才能使用kill命令向其发送信号。
除kill命令的那些常用信号之外,还有其他一些系统的常用信号,如表所示。

你可以使用下列命令显示所有的信号,满足一下好奇心:
bash
[me@linuxbox ~]$ kill -l
10.4.2、使用killall命令向多个进程发送信号
可以使用killall命令向匹配指定名称或用户名的多个进程发送信号。其用法如下:
bash
killall [-u user] [-signal] name...
作为演示,我们启动xlogo程序的两个实例,然后将其终止:
bash
[me@linuxbox ~]$ xlogo &
[1] 18801
[me@linuxbox ~]$ xlogo &
[2] 18802
[me@linuxbox ~]$ killall xlogo
[1]- Terminated xlogo
[2]+ Terminated xlogo
记住,和kill命令一样,你必须拥有超级用户权限才能向不属于你的进程发送信号。
10.5、关闭系统
关闭系统涉及有序地终止所有进程,还要在断电之前执行重要的内务整理工作(如同步所有已挂载的文件系统)。有4个命令可以执行该功能。
- halt。
- poweroff。
- reboot。
- shutdown。
顾名思义,前3个命令通常在使用的时候不加任何参数。来看一个例子:
bash
[me@linuxbox ~]$ sudo reboot
shutdown命令值得留意。通过该命令,我们能够指定执行哪种操作(挂起、关机或重启),提供关机延时。常见的用法是挂起系统:
bash
[me@linuxbox ~]$ sudo shutdown -h now
或者重启系统:
bash
[me@linuxbox ~]$ sudo shutdown -r now
指定延时的方式有多种,详见shutdown命令的手册页。
一旦执行了shutdown命令,就会向所有登录用户"广播"一条消息,警告他们即将发生的事件。
10.7、更多与进程相关的命令
因为监控进程是一项重要的系统管理任务,所以相关的命令数量颇多。下表列出了其中一些。
