Linux_base

知乎一匿名用户推荐The Linux Command Line,说这本书简单、基础,并且快速上手terminal。 并这本书有中文翻译。另知乎用户阿尔法汪推荐学会linux的man文档,这相当于linux的字典,权威。

但目前我毫无基础,暂时先根据The Linux Command Line学习。自然,以下提纲根据此书提纲。

引言 (Introduction)

第一章 引言

(略)

一些暂不知道属于下面哪个章节的知识就先放在这里:

linux的复制快捷键为Ctrl + Insert,粘贴快捷键为Shift + Insert

linux中切换输入法的shift

相对路径:./bin/..../etc/...等包含./的路径,均为相对路径,以/usr/local/hadoop为当前目录。例如在/usr/local/hadoop目录中执行./bin/hadoop version等同于执行/usr/local/hadoop/bin/hadoop version。可以将相对路径改成绝对路径来执行。

Shell中管道命令操作符为|,实现仅当前面一个命令传出正确的输出信息时才传递给下一个命令。可用来给含很多输出的命令筛选关键信息等。

学习 shell

第二章 : 什么是 shell

形如如下者就是shell界面:

[me@linuxbox ~]$

格式为[用户名@主机名 ~]$,如果最后一字符是#,而不是$,那代表对应用户名具有超级权限。

输入错误命令将报错:未找到命令(command not found),如下所示:

[chenfy@entrobus32 ~]$ anssffsf
-bash: anssffsf: 未找到命令
[chenfy@entrobus32 ~]$

命令历史:按下上箭头命令,将在提示符中重现之前输入的anssffsf,这就叫历史命令

移动光标:移动左右箭头按键,将改变输入位置。

关于鼠标和光标:linux也可使用鼠标。点击鼠标左键并拖动鼠标,将自动拷贝对应高亮文本,按下鼠标中键,将在光标处插入刚才的复制内容。(注:windows下的复制黏贴快捷键在linux下不起作用。)

聚焦跟随鼠标(focus follows mouse):作者认为这样更方便,可在窗口管理器中找到这个设置。

运行一些简单命令: date,显示系统当前时间和日期:

[chenfy@entrobus32 ~]$ date
2019年 09月 14日 星期六 17:30:20 CST
[chenfy@entrobus32 ~]$

cal,默认显示当前月份的日历:

[chenfy@entrobus32 ~]$ cal
      九月 2019     
日 一 二 三 四 五 六
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30

[chenfy@entrobus32 ~]$ 

df,查看磁盘剩余空间:

[chenfy@entrobus32 ~]$ df
文件系统            1K-块       已用      可用 已用% 挂载点
/dev/sda3      1921184032 1188612500 634957504   66% /
devtmpfs         74073608          0  74073608    0% /dev
tmpfs            74084364          0  74084364    0% /dev/shm
tmpfs            74084364       2500  74081864    1% /run
tmpfs            74084364          0  74084364    0% /sys/fs/cgroup
/dev/sda1          487634     154046    303892   34% /boot
cm_processes     74084364      93748  73990616    1% /run/cloudera-scm-agent/process
tmpfs            14816876         40  14816836    1% /run/user/1013
tmpfs            14816876          0  14816876    0% /run/user/0
tmpfs            14816876          0  14816876    0% /run/user/1002
tmpfs            14816876          4  14816872    1% /run/user/1007
tmpfs            14816876          0  14816876    0% /run/user/1004
tmpfs            14816876          0  14816876    0% /run/user/1015
tmpfs            14816876          0  14816876    0% /run/user/1021
[chenfy@entrobus32 ~]$ 

free,显示空闲内存数量:

[chenfy@entrobus32 ~]$ free
              total        used        free      shared  buff/cache   available
Mem:      148168732    30739976    42608804       96316    74819952   116460920
Swap:      49999996           0    49999996
[chenfy@entrobus32 ~]$ 

exit,结束终端会话:

[chenfy@entrobus32 ~]$ exit
登出
Connection closing...Socket close.

Connection closed by foreign host.

Disconnected from remote host(32号机) at 17:36:32.

Type `help' to learn how to use Xshell prompt.
[C:\~]$

幕后控制台:即使终端没有运行,在后台任然有几个终端会话运行着。他们叫做虚拟终端或者是虚拟控制台。在大多数Linux发行版中,这些终端会话可以通过按下Ctrl-Alt-F1Ctrl-Alt-F6访问。当一个会话被访问的时候,它会显示登陆提示框,我们需要输入用户名和明码,要从一个虚拟控制台换到另一个,按下 Alt 和 F1-F6(中的一个)。要返回图形桌面,按下 Alt-F7。

第三章:文件系统中跳转

文件系统树:类似windows,一个“类 Unix”操作系统,比如Linux,以分层目录结构来组织所有文件。这意味着所有文件组成了一棵树型目录(有时候在其它系统中叫做文件夹), 这个目录树可能包含文件和其它的目录。文件系统中的第一级目录称为根目录。 根目录包含文件和子目录,子目录包含更多的文件和子目录,依此类推。

但Linux,总是只有一个单一的文件系统树,不管有多少个磁盘或者存储设备连接到计算机上。 根据负责维护系统安全的系统管理员的兴致,存储设备连接到(或着更精确些,是挂载到)目录树的各个节点上。

当前工作目录:我们所在的目录则称为 当前工作目录。我们使用 **pwd**(print working directory的缩写)命令,来显示当前工作目录。

[chenfy@entrobus32 ~]$ pwd
/home/chenfy
[chenfy@entrobus32 ~]$ 

登录系统的默认工作目录为根目录下用户名文件夹,这个文件夹也称家目录,是允许该用户写入文件的地方。

列出目录内容:使用ls命令,列出一个内包含的文件及其子目录:

[chenfy@entrobus32 ~]$ ls
replaceContent.py  test.txt  text  tmp  try
[chenfy@entrobus32 ~]$ 

更改当前工作目录:可通过cd 路径名实现,路径名分绝对路径和相对路径。

绝对路径:开始于根目录,紧跟着目录树的一个个分支,一直到达所期望的目录或文件。例如,你的系统中有一个目录,大多数系统程序都安装在这个目录下。这个目录的路径名是/usr/bin。它意味着从根目录(用开头的/表示),有个叫usr的目录包含了目录bin

[chenfy@entrobus32 ~]$ cd /usr/bin
[chenfy@entrobus32 bin]$ pwd
/usr/bin
[chenfy@entrobus32 bin]$ ls
...Listing of many, many files ...
[chenfy@entrobus32 bin]$  

相对路径:相对路径开始于工作目录。为了使用相对路径,我们在文件系统树中用一对特殊符号(...)来表示相对位置。.指工作目录,..指工作目录的父目录。

[chenfy@entrobus32 bin]$ pwd
/usr/bin
[chenfy@entrobus32 bin]$ cd ..
[chenfy@entrobus32 usr]$ pwd
/usr
[chenfy@entrobus32 usr]$ 
[chenfy@entrobus32 usr]$ pwd
/usr
[chenfy@entrobus32 usr]$ cd ./bin
[chenfy@entrobus32 bin]$ pwd
/usr/bin
[chenfy@entrobus32 bin]$ 

几乎在任何情况,./都可省略:

[chenfy@entrobus32 usr]$ pwd
/usr
[chenfy@entrobus32 usr]$ cd bin
[chenfy@entrobus32 bin]$ pwd
/usr/bin
[chenfy@entrobus32 bin]$

表3-1 cd快捷键

快捷键 运行结果
cd 更改工作目录到你的家目录
cd - 更改工作目录到之前的工作目录
cd ~user_name 更改工作目录到用户家目录。例如cd ~bob会更改工作目录到用户bob的家目录

关于文件名的重要规则:

第四章:探究操作系统

开始之前,先学习一些对研究Linux系统有帮助的命令:

ls用来显示目录的内容,以及文件和目录的属性。除了当前工作目录外,也可以指定别的目录。

[chenfy@entrobus32 ~]$ ls
:     filesort  patitioner       people_new_text  rdd                resources      streaming           test.txt  tmp      try       writeback
file  ml        people_new_json  people_new.txt   replaceContent.py  secondarysort  TestPartitioner.py  text      TopN.py  word.txt
[chenfy@entrobus32 ~]$ ls /usr
bin  etc  games  include  lib  lib64  libexec  local  sbin  share  src  tmp
[chenfy@entrobus32 ~]$ ls /
bin  boot  dev  dfs  dmp  etc  home  impala  lib  lib64  lost+found  media  mnt  nfs  openshift  opt  proc  root  run  sbin  srv  swapfile  sys  tmp  usr  var  yarn
[chenfy@entrobus32 ~]$ 

甚至可以列出多个指定目录的内容。如下列出用户家目录(用字符~代表)和/usr目录的内容:

[chenfy@entrobus32 ~]$ ls ~ /usr
/home/chenfy:
:     filesort  patitioner       people_new_text  rdd                resources      streaming           test.txt  tmp      try       writeback
file  ml        people_new_json  people_new.txt   replaceContent.py  secondarysort  TestPartitioner.py  text      TopN.py  word.txt

/usr:
bin  etc  games  include  lib  lib64  libexec  local  sbin  share  src  tmp
[chenfy@entrobus32 ~]$

也可改变输出格式,加上-l,则结果以长格式暑促。

[chenfy@entrobus32 ~]$ ls -l
总用量 76
-rw-rw-r-- 1 chenfy chenfy    0 9月  16 19:27 :
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 17:18 file
drwxrwxr-x 3 chenfy chenfy 4096 9月  16 17:56 filesort
drwxrwxr-x 2 chenfy chenfy 4096 9月  18 20:23 ml
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 13:42 patitioner
drwxrwxr-x 2 chenfy chenfy 4096 9月  17 10:05 people_new_json
drwxrwxr-x 2 chenfy chenfy 4096 9月  17 10:06 people_new_text
drwxrwxr-x 2 chenfy chenfy 4096 9月  17 09:44 people_new.txt
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 10:14 rdd
-rw-rw-r-- 1 chenfy chenfy    0 8月   9 20:33 replaceContent.py
drwxrwxr-x 3 chenfy chenfy 4096 9月  17 10:43 resources
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 21:22 secondarysort
drwxrwxr-x 6 chenfy chenfy 4096 9月  18 14:18 streaming
-rw-rw-r-- 1 chenfy chenfy  859 9月  16 13:41 TestPartitioner.py
-rw-rw-r-- 1 chenfy chenfy   26 8月   9 20:16 test.txt
-rw-rw-r-- 1 chenfy chenfy   27 8月   9 20:54 text
drwxrwxr-x 2 chenfy chenfy 4096 8月  12 18:23 tmp
-rw-rw-r-- 1 chenfy chenfy  552 9月  16 17:12 TopN.py
drwxrwxr-x 2 chenfy chenfy 4096 8月   9 20:26 try
-rw-rw-r-- 1 chenfy chenfy   45 9月  16 10:52 word.txt
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 15:41 writeback
[chenfy@entrobus32 ~]$ 

选项和参数

命令经常会含有一个或多个修改命令表现的选项。而且,选项后面会带一个或多个参数,这些参数是命令的作用对象。大多数命令形如:

command -options arguments

大多数命令使用的选项(options),是由一个中划线(-)和单字符组成,例如ls命令的-l选项。但许多命令,包括来自GNU项目的命令,也支持长选项(似乎所有长选项均有其对应的选项,如表4-1所示),长选项由两个中划线和一个组成。当然,许多命令也允许把多个选项串在一起使用。下面例子ls命令有两个选项,-l选项规定长格式输出,-t选项按文件修改时间的先后来排序。

[chenfy@entrobus32 ~]$ ls -lt
总用量 76
drwxrwxr-x 2 chenfy chenfy 4096 9月  18 20:23 ml
drwxrwxr-x 6 chenfy chenfy 4096 9月  18 14:18 streaming
drwxrwxr-x 3 chenfy chenfy 4096 9月  17 10:43 resources
drwxrwxr-x 2 chenfy chenfy 4096 9月  17 10:06 people_new_text
drwxrwxr-x 2 chenfy chenfy 4096 9月  17 10:05 people_new_json
drwxrwxr-x 2 chenfy chenfy 4096 9月  17 09:44 people_new.txt
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 21:22 secondarysort
-rw-rw-r-- 1 chenfy chenfy    0 9月  16 19:27 :
drwxrwxr-x 3 chenfy chenfy 4096 9月  16 17:56 filesort
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 17:18 file
-rw-rw-r-- 1 chenfy chenfy  552 9月  16 17:12 TopN.py
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 15:41 writeback
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 13:42 patitioner
-rw-rw-r-- 1 chenfy chenfy  859 9月  16 13:41 TestPartitioner.py
-rw-rw-r-- 1 chenfy chenfy   45 9月  16 10:52 word.txt
drwxrwxr-x 2 chenfy chenfy 4096 9月  16 10:14 rdd
drwxrwxr-x 2 chenfy chenfy 4096 8月  12 18:23 tmp
-rw-rw-r-- 1 chenfy chenfy   27 8月   9 20:54 text
-rw-rw-r-- 1 chenfy chenfy    0 8月   9 20:33 replaceContent.py
drwxrwxr-x 2 chenfy chenfy 4096 8月   9 20:26 try
-rw-rw-r-- 1 chenfy chenfy   26 8月   9 20:16 test.txt
[chenfy@entrobus32 ~]$ 

ls命令有大量的选项,表4-1列出了最常用的选项:

表4-1:ls命令选项

选项 长选项 描述
-a --all 列出所有文件,包括隐藏文件(文件名.开头者)
-d --directory 通常,如果制定了目录名,ls命令会列出这个目录中的内容(即目标目录的下一级),而不是目录本身。把这个选项与-l选项结合使用,可以看到指定目录的详细信息,而不是目录中的内容
-F --classify 这个选项会在每个所列出的名字后面加上一个指示符。例如,如果名字是目录名,则会加上一个/字符,文件则否。这样可区分路径和文件。
-h --human-readable 当以长格式列出时,以人类可读的格式,而不是以字节数来显示文件的大小。
-l 以长格式显示结果
-r --reverse 以相反的顺序来显示结果。通常,ls命令的输出按照字母升序排列。
-S 按文件大小来排序
-t 按照修改时间来排序

长格式输出的个字段含义:

表4-2:ls长格式列表的字段

字段 含义
-rw-r-r-- 对于文件的访问权限。第一个字符指明文件类型。开头-说明是普通文件,若是d表明是目录。其后三个字符是文件所有者访问权限,再其后三个字符是所属组中成员的访问权限,最后三个字符是其他所有人的访问权限。此字段的完整含义见于第十章
1 文件的硬链接数目。更多信息参考关于链接的内容。
root 文件所有者的用户名
root 文件所属用户组的名字
32059 以字节数表示的文件大小
2007-04-03 11:05 上次修改时间
oo-cd-cover.odf 文件名

确定文件类型: 如前叙述,在Linux系统中,并不要求文件名来反映文件的内容。而且,在Unix系统比如Linux中,“一切皆文件”,所以光看看文件名不能对文件类型有把握,可采用file命令来确定文件类型:

[chenfy@entrobus32 ~]$ ls
:         patitioner       rdd                streaming           tmp       writeback
file      people_new_json  replaceContent.py  TestPartitioner.py  TopN.py
filesort  people_new_text  resources          test.txt            try
ml        people_new.txt   secondarysort      text                word.txt
[chenfy@entrobus32 ~]$ file word.txt
word.txt: ASCII text
[chenfy@entrobus32 ~]$ file text
text: ASCII text
[chenfy@entrobus32 ~]$ file TopN.py
TopN.py: Python script, ASCII text executable
[chenfy@entrobus32 ~]$ 

less浏览文件内容:

看less命令,仅能读不能写。那我何不用vim?!故略。

或者其可用来判断文本(这里不仅包括.txt文本,也包括程序脚本等如.py文件),因为或者是不是非文本文件无法用less打开呢。

Linux文件系统组成:

表4-4:Linux系统中的目录

目录 解释
/ 根目录,万物起源
/bin 包含系统启动和运行所必须的二进制程序。
/boot 包含Linux内核、初始化RAM磁盘映像(用于启动所需的驱动)和启动加载程序。
有趣的文件:
1. /boot/grub/grub.conf or menu.lst,用来配置启动加载程序。
2. /boot/vmlinuz,Linux内核。
/dev 这是一个包含所有设备节点的特殊目录。“万物皆文件”,也适用于设备。在这个目录里,内核维护着所有设备的列表。
/etc 这个目录包含所有系统层面的配置文件。它也包含一系列的shell脚本,在系统启动时,这些脚本会开启每个系统服务。这个目录中的任何文件应该是可读的文本文件。
有趣的文件:
/etc/crontab,定义自动运行的任务。
/etc/fstab,包含存储设备的列表,以及与他们相关的挂载点。
/etc/passwd,包含用户账号列表。
/home 在通常的配置环境下,系统会在/home下,给每个用户分配一个目录。普通用户只能在自己的目录下写文件。这个现在保护系统免受错误的用户活动破坏。
/lib 包含核心系统程序所使用的共享文件库。这些文件与Windows中的动态链接库相似。
/lost+found 每个使用Linux文件系统的格式化分区或设备,例如ext3文件系统,都会在这个目录。当部分恢复一个损坏的文件系统时,会用到这个目录。这个目录应该是空的,除非文件系统真的孙环了。
/media 在现在Linux系统中,/media目录会包含可移动介质的挂载点,例如USB驱动器、CD-ROM等等。这些介质连接到计算机后,会自动挂载到这个目录节点下下。
/mnt 在早些的Linux系统中,/mnt目录包含可移动介质的挂载点。
/opt 这个/opt目录被用来安装“可选的”软件。这个主要用来存储可能安装在系统中的商业软件产品。
/proc 这个/proc目录很特殊。从存储在硬盘上的文件的意义来说,它不是真正的文件系统。相反,它是一个由Linux内核维护的虚拟文件系统。它所包含的文件是内核的窥视孔。这些文件是可读的,它们告诉你内核是怎样监管计算机的。
/root root账户的家目录。
/sbin 这个目录包含“系统”二进制文件。他们是完成重大系统任务的程序,通常为超级用户保留。
/tmp 这个/tmp目录,是用来存储由各种程序创建的临时文件的地方。一些配置导致系统每次重新启动时,都会清空这个目录。
/usr 在Linux系统中,/usr目录可能是最大的一个。它包含普通用户所有程序和文件。
/usr/bin /usr/bin目录包含系统安装的可执行程序。通常,这个目录会包含许多程序。
/usr/lib 包含由/usr/bin目录中的程序所用的共享库。
/usr/local 这个/usr/local目录,是非系统发行版所自带程序的安装目录,通常,由源码编译的程序会安装在/usr/local目录下。新安装的Linux系统会存在这个目录,并且在管理员安装程序之前,这个目录是空的。
/usr/sbin 包含许多系统管理程序。
/usr/share /usr/share目录包含许多由/usr/bin目录中的程序使用的共享数据。其中包括像默认的配置文件、图标、桌面背景、音频文件等。
/usr/share/doc 大多数安装在系统中的软件包会包含一些文档。在/usr/share/doc目录下,我们可以找到按照软件包分类的文档。
/var 除了/tmp/home目录外,相对来说,目前我们看到的目录是静态的,这是说,他们的内容不会变。/var目录存放的是动态文件。各种数据库,假脱机文件、用户邮件等等,都位于这里。
/var/log 这个/var/log目录包含日志文件、各种系统活动的记录。这些文件非常重要,并且应该实时监控它们。其中最重要的一个文件是/var/log/messages。注意,为了系统安全,在一些系统中,你必须是超级用户才能查看这些日志文件。

符号链接:

我们四处查看系统中的文件,可能会看到一个目录,列出像这样的一条信息:

lrwxrwxrwx 1 root root 11 2007-08-11 07:34 libc.so.6 -> libc-2.6.so

注意看,为何这条信息第一个字符是l,并且有两个文件名呢?这是一个特殊文件,叫做符号链接(也叫软连接或symlink)。在大多数“类Unix”系统中,有可能一个文件被多个文件名所指向。虽然这种特征的意义并不明显,但它真的很有用。

描绘以下这样的场景:一个程序要求使用某个包含在名为foo文件中的共享资源,但是foo经常改变版本号。这样,在文件名中包含版本号,会是一个好主意,因为管理员或其他相关方,会知道安装了哪个foo版本。这会导致一个问题。如果我们更改了共享资源的名字,那么必须跟踪每个可能使用了这个共享资源的程序。当每次这个资源的新版本被安装后,都要让使用了它的程序去寻找新的资源名。这听起来很没趣。

这就是符号链接存在至今的原因。比方说,我们安装了文件foo的2.6版本,它的文件名是foo-2.6,然后创建了叫做foo的符号链接,这个符号链接指向foo-2.6。这意味着,当一个程序打开文件foo时,它实际上时打开文件foo-2.6。现在,每个人都很高兴。以来于文件foo的程序能找到这个文件,并且我们能知道安装了哪个版本。当升级到foo-2.7版本时,仅添加这个文件到文件系统中,删除符号链接foo,创建一个指向新版本的符号链接。这样不仅解决了版本升级的问题,而且运行在系统中保存两个不同的文件版本。假设foo-2.7有错误,那我们得退回到原版本。一样的操作,只需删除指向新版本的符号链接,创建指向旧版本的符号链接。

在上面列出的目录(来自Fedora的/lib目录)展示了一个叫做libc.so.6的符号链接,这个链接指向一个叫做libc-2.6.so的共享库文件。这意味着,寻找文件libc.so.6的程序,实际得到的是文件libc-2.6.so。在下一章节,将学习如何创建符号链接。

硬链接:

讨论到链接问题,需要提一下,还有一种连接类型,叫做硬链接。硬链接同样允许文件有多个名字,但是硬链接以不同的方法来创建多个文件名,在下一章节,会谈到符号链接与硬链接的更多差异。

第五章:操作文件和目录

操作文件和目录

这一章节将介绍如下命令:

这5个命令属于最常用的linux命令之列,用它们来操作文件和目录。

现在,用图形文件管理器来完成一些由这些命令会更容易一些。使用图形文件管理器,我们可以把文件从一个目录拖放到另一个目录、剪贴和黏贴文件、删除文件。那么为何还使用早期的命令行程序呢?

答案是命令行程序,功能强大灵活。虽然图形文件管理器能轻松实现简单的文件操作,但是对于复杂的文件操作任务,则使用命令行程序比较容易完成。例如,怎样拷贝一个目录中所有的HTML文件(这些文件在目录中不存在或者版本比目标目录的文件更新)到目标目录呢?要完成这个任务,使用图形文件管理器相当难,使用命令行相当容易:

cp -u *.html destination

通配符:

在开始使用命令之前,需要介绍一个使得=命令行如此强大的shell特性。因为shell频繁地使用文件名,shell提供了特殊字符来帮助你快速指定一组文件名。这些特殊字符叫做通配符。使用通配符(也以文件名代换著称)允许你根据字符的组合模式来选择文件名。下面列出这些通配符以及他们所选择的对象:

表5-1:通配符

通配符 意义
* 匹配任意字符
? 匹配单个字符
[characters] 将字符与字符集的成员字符匹配
![characters] 将字符与字符集的成员外的意义字符匹配
[ [:class:] ] 将字符与特定字符类(class)的成员进行匹配。(注:中间空格需去掉)

表5-2:最常用的字符类

字符类(character class) 意义
[:alnum:] 匹配任意字母或数字
[]:alpha:] 匹配任意字母
[:digit:] 匹配任意数字
[:lower:] 匹配任意小写字母
[:upper:] 匹配任意大写字母

表5-3:通配符范例

模式 匹配对象
* 所有文件
g* 任意以“g”开头的文件
g*.txt 以“b”开头,中间可有其他任意字符,并以“.txt”结尾的文件
Data??? 以“Data”开头,其后紧接着3个字符的文件
[abc]* 以“a”、“b”、“c”开头的文件
BACKUP.[0-9][0-9][0-9] 以"BACKUP."开头,并紧接着3个数字的文件
[ [:upper:] ]* 以大写字母开头的文件(注:中间空格需去掉)
[![:digit:]]* 不以数字开头的文件
*[[:lower:]123] 文件名以小写字母结尾,或以“1”、“2”、“3”结尾

字符范围:
传统的Unix字符范围表示法如[A-Z][a-z]等,虽然现在起作用,但必须小心甚至避免使用,因为可能不会产生如你预期的输出,应转而采用字符类。
通配符在GUI中也有效,如在Nautilus (GNOME 文件管理器)、Dolphin 和 Konqueror(KDE 文件管理器)中。

mkdir-创建目录

mkdir命令是用来创建目录的。如:

mkdir directory...

注意表示法:在描述一个命令时,如上所示,当有三个圆点跟在一个命令的参数后面,意味着这个参数可重复,例如如下命令将在工作目录创建dir1目录:

mkdir dir1

而如下命令将在工作目录创建dir1、dir2、dir3共3个目录:

mkdir dir1 dir2 dir3

cp-复制文件和目录

cp命令,复制文件或目录。它有两种使用方法:

copy item1 item2

复制单个文件或路径item1到文件或路径item2

cp item... directory

复制多个文件或目录到一个目录下。

表5-4:cp选项

选项 意义
-a
--all
复制文件和目录,以及它们的属性,包括所有权和权限。通常,副本具有用户所操作文件的默认属性。
-i
--interactive
在重写已存在文件之前,提示用户确认。如果这个选项不指定,cp会默认重写文件。
-r
--recursive
递归地复制目录及目录中的内容时,需要这个选项(或者也可选择-a)。
-u
--update
当把文件从一个目录复制到另一个目录时,仅复制目标目录中不存在的文件,或者是文件内容新于目标目录中已经存在的文件。
-v
--verbose
显示详实的命令操作信息。

表5-5:cp实例

命令 运行结果
cp file1 file2 复制文件file1内容到文件file2。如果file2已经存在,file2的内容会被file1的内容重写。如果file2不存在,则会创建file2
cp -i file1 file2 这条命令和上面的命令一样,除了如果文件file2存在的话,在文件file2被重写之前,会提示用户确认信息。
cp file1 file2 dir1 复制文件file1和文件file2到目录dir1。目录dir1必须存在。
cp dir1/* dir2 使用一个通配符,在目录dir1中的所有文件都被复制到目录dir2中。dir2必须已经存在。
cp -r dir1 dir2 复制目录dir1中的内容到目录dir2。如果目录dir2不存在, 创建目录dir2,操作完成后,目录dir2中的内容和dir1中的一样。 如果目录dir2存在,则目录dir1(和目录中的内容)将会被复制到dir2中。

mv-移动和重命名文件

mv命令可以执行文件移动和文件命名任务,这依赖于你怎样使用它。任何一种情况下,完成操作后,原来的文件名不再存在。mv使用方法与cp很相像,如,把文件或目录 “item1” 移动或重命名为 “item2”:

mv item1 item2

或者,把一个或多个条目从一个目录移动到另一个目录中:

mv item... directory

表5-6:mv选项

选项 意义
-i
--interactive
在重写一个已经存在的文件之前,提示用户确认信息。 如果不指定这个选项,mv命令会默认重写文件内容。
-u
--update
当把文件从一个目录移动另一个目录时,只是移动不存在的文件, 或者文件内容新于目标目录相对应文件的内容。
-v
--verbose
当操作mv命令时,显示翔实的操作信息。

表5-7:mv实例

命令 运行结果
mv file1 file2 移动file1file2。如果file2存在,它的内容会被file1的内容重写。如果file2不存在,则创建file2。 这两种情况下,file1都不再存在。
mv -i file1 file2 除了如果file2存在的话,在file2被重写之前,用户会得到 提示信息外,这个和上面的选项一样。
mv file1 file2 dir1 移动file1file2到目录dir1中。dir1必须已经存在。
mv -r dir1 dir2 如果目录dir2不存在,创建目录dir2,并且移动目录dir1的内容到目录dir2中,同时删除目录dir1。如果目录dir2存在,移动目录dir1(及它的内容)到目录dir2

rm-删除文件和目录

rm命令用来移除(删除)文件和目录:

rm item...

item...代表一个或多个文件或目录。

下面是一些二普遍使用的rm选项:

表5-8:rm选项

选项 意义
-i
--interactive
在删除已存在的文件前,提示用户确认信息。 如果不指定这个选项,rm会默默地删除文件
-r
--recursive
递归地删除文件,这意味着,如果要删除一个目录,而此目录 又包含子目录,那么子目录也会被删除。要删除一个目录,必须指定这个选项。
-f
--force
忽视不存在的文件,不显示提示信息。这选项覆盖了--interactive”`选项。
-v
--verbose
在执行rm命令时,显示翔实的操作信息。

表5-9: rm实例

命令 运行结果
rm file1 默默地删除文件
rm -i file1 除了在删除文件之前,提示用户确认信息之外,和上面的命令作用一样。
rm -r file1 dir1 删除文件file1, 目录dir1,及dir1中的内容。
rm -rf file1 dir1 同上,除了如果文件file1,或目录dir1不存在的话,rm仍会继续执行。

类Unix的操作系统,比如说Linux,没有复原命令。一旦你用rm删除了一些东西,它就消失了。Linux假定你很聪明,你知道你在做什么。
尤其要小心通配符。思考一下这个经典的例子。假如说,你只想删除一个目录中的HTML文件。输入:rm *.html,这是正确的,如果你不小心在 *.html 之间多输入了一个空格,就像这样:rm * .html。这个rm命令会删除目录中的所有文件,还会抱怨没有文件叫做.html
小贴士。当你使用带有通配符的rm命令时(除了仔细检查输入的内容外),先用ls命令来测试通配符。这会让你看到将要被删除的文件是什么。然后按下上箭头按键,重新调用刚刚执行的命令,用rm替换ls

ln — 创建链接 ln 命令既可创建硬链接,也可以创建符号链接。可以用其中一种方法来使用它:

ln file link

创建硬链接,和

ln -s item link

创建符号链接,”item” 可以是一个文件或是一个目录。

硬链接

与更加现代的符号链接相比,硬链接是最初Unix创建链接的方式。每个文件默认会有一个硬链接,这个硬链接给予文件名字。我们每创建一个硬链接,就为一个文件创建了一个额外的目录项。硬链接有两个重要局限性:

一个硬链接不能关联一个目录。

一个硬链接和文件本身没有什么区别。不像符号链接,当你列出一个包含硬链接的目录 内容时,你会看到没有特殊的链接指示说明。当一个硬链接被删除时,这个链接 被删除,但是文件本身的内容仍然存在(这是说,它所占的磁盘空间不会被重新分配), 直到所有关联这个文件的链接都删除掉。知道硬链接很重要,因为你可能有时 会遇到它们,但现在实际中更喜欢使用符号链接,下一步我们会讨论符号链接。

符号链接

创建符号链接是为了克服硬链接的局限性。符号链接生效,是通过创建一个 特殊类型的文件,这个文件包含一个关联文件或目录的文本指针。在这一方面, 它们和 Windows 的快捷方式差不多,当然,符号链接早于 Windows 的快捷方式 很多年;-)

一个符号链接指向一个文件,而且这个符号链接本身与其它的符号链接几乎没有区别。 例如,如果你往一个符号链接里面写入东西,那么相关联的文件也被写入。然而, 当你删除一个符号链接时,只有这个链接被删除,而不是文件自身。如果先于符号链接 删除文件,这个链接仍然存在,但是不指向任何东西。在这种情况下,这个链接被称为 坏链接。在许多实现中,ls 命令会以不同的颜色展示坏链接,比如说红色,来显示它们 的存在。

关于链接的概念,看起来很迷惑,但不要胆怯。我们将要试着练习这些命令,希望,它变得清晰起来。

创建游戏场(实战演习)

下面我们将要做些真正的文件操作,让我们先建立一个安全地带, 来玩一下文件操作命令。首先,我们需要一个工作目录。在我们的 家目录下创建一个叫做“playground”的目录。

创建目录

mkdir 命令被用来创建目录。首先确定我们在我们的家目录下,然后创建 playground 目录:

为了让我们的游戏场更加有趣,在 playground 目录下创建一对目录 ,分别叫做 “dir1” 和 “dir2”。更改我们的当前工作目录到 playground,然后 执行 mkdir 命令:

注意到 mkdir 命令可以接受多个参数,它允许我们用一个命令来创建这两个目录。

复制文件

下一步,让我们输入一些数据到我们的游戏场中。我们可以通过复制一个文件来实现目的。 我们使用 cp 命令从 /etc 目录复制 passwd 文件到当前工作目录下:

请注意,我们使用命令末尾的一个圆点来简化当前工作目录的写法。如果我们执行 ls 命令, 可以看到我们的文件:

现在,仅仅是为了高兴,重复操作复制命令,使用”-v”选项(详细),看看它做了些什么:

cp 命令再一次执行了复制操作,但是这次显示了一条简洁的信息,指明它 进行了什么操作。注意,cp 没有警告,就重写了第一次复制的文件。这是一个案例, cp 会假设你知道自己在做什么。如果希望得到警告的话,需要加入“-i”(互动)选项:

在提示信息后输入”y”,文件就会被重写,输入其它的字符(例如,”n”) cp 命令会保留原文件。

移动和重命名文件

现在,”passwd” 这个名字,看起来不怎么有趣,这是个游戏场,所以我们给它改个名字:

让我们来传送 fun 文件,通过移动重命名的文件到各个子目录, 然后再把它移回到当前目录:

首先,把 fun 文件移动目录 dir1 中,然后:

再把 fun 文件从 dir1 移到目录 dir2, 然后:

最后,再把 fun 文件带回到当前工作目录。接下来,来看看移动目录的效果。 首先,我们先移动我们的数据文件到 dir1 目录:

然后移动 dir1到 dir2目录,用 ls 来确认执行结果:

注意:因为目录 dir2 已经存在,mv 命令会把 dir1 移动到 dir2 目录中。如果 dir2 不存在, mv 会把dir1重命名为 dir2。最后,让我们把所有的东西放回原处:

创建硬链接

现在,我们试着创建链接。首先是硬链接。我们创建一些关联我们 数据文件的链接:

所以现在,我们有四个文件”fun”的实例。看一下目录 playground 中的内容:

注意到一件事,列表中,文件 fun 和 fun-hard 的第二个字段是”4”,这个数字 是文件”fun”的硬链接数目。你要记得一个文件至少有一个硬链接,因为文件 名就是由链接创建的。那么,我们怎样知道实际上 fun 和 fun-hard 是同一个文件呢? 在这个例子里,ls 不是很有用。虽然我们能够看到 fun 和 fun-hard 文件大小一样 (第五字段),但我们的列表没有提供可靠的信息来确定(这两个文件一样)。 为了解决这个问题,我们更深入的研究一下。

当考虑到硬链接的时候,我们可以假设文件由两部分组成:包含文件内容的数据部分和持有文件名的名字部分 ,这将有助于我们理解这个概念。当我们创建文件硬链接的时候,实际上是为文件创建了额外的名字部分, 并且这些名字都关联到相同的数据部分。这时系统会分配一连串的磁盘块给所谓的索引节点,然后索引节点与文 件名字部分相关联。因此每一个硬链接都关系到一个具体的包含文件内容的索引节点。

ls 命令有一种方法,来展示(文件索引节点)的信息。在命令中加上”-i”选项:

在这个版本的列表中,第一字段表示文件索引节点号,正如我们所见到的, fun 和 fun-hard 共享一样的索引节点号,这就证实这两个文件是同一个文件。

创建符号链接

建立符号链接的目的是为了克服硬链接的两个缺点:硬链接不能跨越物理设备, 硬链接不能关联目录,只能是文件。符号链接是文件的特殊类型,它包含一个指向 目标文件或目录的文本指针。

符号链接的建立过程相似于创建硬链接:

第一个例子相当直接,在 ln 命令中,简单地加上”-s”选项就可以创建一个符号链接, 而不是一个硬链接。下面两个例子又是怎样呢? 记住,当我们创建一个符号链接 的时候,会建立一个目标文件在哪里和符号链接有关联的文本描述。如果我们看看 ls 命令的输出结果,比较容易理解。

目录 dir1 中,fun-sym 的列表说明了它是一个符号链接,通过在第一字段中的首字符”l” 可知,并且它还指向”../fun”,也是正确的。相对于 fun-sym 的存储位置,fun 在它的 上一个目录。同时注意,符号链接文件的长度是6,这是字符串”../fun”所包含的字符数, 而不是符号链接所指向的文件长度。

当建立符号链接时,你既可以使用绝对路径名:

也可用相对路径名,正如前面例题所展示的。使用相对路径名更令人满意, 因为它允许一个包含符号链接的目录重命名或移动,而不会破坏链接。

除了普通文件,符号链接也能关联目录:

移动文件和目录

正如我们之前讨论的,rm 命令被用来删除文件和目录。我们将要使用它 来清理一下我们的游戏场。首先,删除一个硬链接:

结果不出所料。文件 fun-hard 消失了,文件 fun 的链接数从4减到3,正如 目录列表第二字段所示。下一步,我们会删除文件 fun,仅为了娱乐,我们会加入”-i” 选项,看一看它的作用:

在提示符下输入”y”,删除文件。让我们看一下 ls 的输出结果。注意,fun-sym 发生了 什么事? 因为它是一个符号链接,指向已经不存在的文件,链接已经坏了:

大多数 Linux 的发行版本配置 ls 显示损坏的链接。在 Fedora 系统中,坏的链接以闪烁的 红色文本显示!损坏链接的出现,并不危险,但是相当混乱。如果我们试着使用 损坏的链接,会看到以下情况:

稍微清理一下现场。删除符号链接:

对于符号链接,有一点值得记住,执行的大多数文件操作是针对链接的对象,而不是链接本身。 而 rm 命令是个特例。当你删除链接的时候,删除链接本身,而不是链接的对象。

最后,我们将删除我们的游戏场。为了完成这个工作,我们将返回到 我们的家目录,然后用 rm 命令加上选项(-r),来删除目录 playground, 和目录下的所有内容,包括子目录:

用 GUI 来创建符号链接
文件管理器 GNOME 和 KDE 都提供了一个简单而且自动化的方法来创建符号链接。 在 GNOME 里面,当拖动文件时,同时按下 Ctrl+Shift 按键会创建一个链接,而不是 复制(或移动)文件。在 KDE 中,无论什么时候放下一个文件,会弹出一个小菜单, 这个菜单会提供复制,移动,或创建链接文件选项。

总结

在这一章中,我们已经研究了许多基础知识。我们得花费一些时间来全面地理解。 反复练习 playground 例题,直到你觉得它有意义。能够良好地理解基本文件操作 命令和通配符,非常重要。随意通过添加文件和目录来拓展 playground 练习, 使用通配符来为各种各样的操作命令指定文件。关于链接的概念,在刚开始接触 时会觉得有点迷惑,花些时间来学习它们是怎样工作的。它们能成为真正的救星。

第六章:使用命令

在这之前,我们已经知道了一系列神秘的命令,每个命令都有自己奇妙的 选项和参数。在这一章中,我们将试图去掉一些神秘性,甚至创建我们自己 的命令。这一章将介绍以下命令:

到底什么是命令?

命令可以是下面四种形式之一:

  1. 是一个可执行程序,就像我们所看到的位于目录/usr/bin 中的文件一样。 这一类程序可以是用诸如 C 和 C++语言写成的程序编译的二进制文件, 也可以是由诸如shell,perl,python,ruby等等脚本语言写成的程序 。
  2. 是一个内建于 shell 自身的命令。bash 支持若干命令,内部叫做 shell 内部命令 (builtins)。例如,cd 命令,就是一个 shell 内部命令。
  3. 是一个 shell 函数。这些是小规模的 shell 脚本,它们混合到环境变量中。 在后续的章节里,我们将讨论配置环境变量以及书写 shell 函数。但是现在, 仅仅意识到它们的存在就可以了。
  4. 是一个命令别名。我们可以定义自己的命令,建立在其它命令之上。

识别命令

这经常很有用,能确切地知道正在使用四类命令中的哪一类。Linux 提供了一对方法来 弄明白命令类型。

type - 显示命令的类型

type 命令是 shell 内部命令,它会显示命令的类别,给出一个特定的命令名(做为参数)。 它像这样工作:

type command

“command”是你要检测的命令名。这里有些例子:

[chenfy@entrobus32 ~]$ type type
type 是 shell 内嵌
[chenfy@entrobus32 ~]$ type ls
ls 是 `ls --color=auto' 的别名
[chenfy@entrobus32 ~]$ type cp
cp 是 /usr/bin/cp
[chenfy@entrobus32 ~]$ ^C
[chenfy@entrobus32 ~]$ 

我们看到这三个不同命令的检测结果。注意,ls 命令(在 Fedora 系统中)的检查结果,ls 命令实际上 是 ls 命令加上选项”--color=tty”的别名。现在我们知道为什么 ls 的输出结果是有颜色的!

which - 显示一个可执行程序的位置

有时候在一个操作系统中,不只安装了可执行程序的一个版本。虽然在桌面系统中这并不普遍, 但在大型服务器中却很平常。为了确定所给定的执行程序的准确位置,使用 which 命令:

[chenfy@entrobus32 ~]$ which ls
alias ls='ls --color=auto'
	/usr/bin/ls
[chenfy@entrobus32 ~]$ 

这个命令只对可执行程序有效,不包括内建命令和命令别名,别名是真正的可执行程序的替代物。 当我们试着使用 shell 内建命令时,例如,cd 命令,我们或者得不到回应,或者是个错误信息:

[chenfy@entrobus32 ~]$ which cd
/usr/bin/cd
[chenfy@entrobus32 ~]$ 

注:

  1. 由于版本原因,如上所示,已能对cd采用which命令了。

得到命令文档

知道了什么是命令,现在我们来寻找每一类命令的可得到的文档。

help - 得到 shell 内建命令的帮助文档

bash 有一个内建的帮助工具,可供每一个 shell 内建命令使用。输入“help”,接着是 shell 内部命令名。例如:

[chenfy@entrobus32 ~]$ help cd
cd: cd [-L|[-P [-e]]] [dir]
    Change ...

注意表示法:出现在命令语法说明中的方括号,表示可选的项目。一个竖杠字符 表示互斥选项。在上面 cd 命令的例子中:

cd: cd [-L|[-P [-e]]] [dir]

这种表示法说明,cd 命令可能有一个“-L”选项或者“-P”选项,进一步,可能有参数“dir”。

虽然 cd 命令的帮助文档很简洁准确,但它决不是教程。正如我们所看到的,它似乎提到了许多 我们还没有谈论到的东西!不要担心,我们会学到的。

--help - 显示用法信息

许多可执行程序支持一个 --help 选项,这个选项是显示命令所支持的语法和选项说明。例如:

[chenfy@entrobus32 ~]$ mkdir --help
用法:mkdir [选项]... 目录...
Create the DIRECTORY(ies), if they do not already exist.
...

一些程序不支持 --help 选项,但不管怎样试一下。这经常会导致输出错误信息,但同时能 揭示一样的命令用法信息。

man - 显示程序手册页

许多希望被命令行使用的可执行程序,提供了一个正式的文档,叫做手册或手册页(man page)。一个特殊的叫做 man 的分页程序,可用来浏览他们。它是这样使用的:

man program

“program”是要浏览的命令名。

手册文档的格式有点不同,一般地包含一个标题、命令语法的纲要、命令用途的说明、 以及每个命令选项的列表和说明。然而,手册文档通常并不包含实例,它打算 作为一本参考手册,而不是教程。作为一个例子,浏览一下 ls 命令的手册文档:

[chenfy@entrobus32 ~]$ man ls

在大多数 Linux 系统中,man 使用 less 工具来显示参考手册,所以当浏览文档时,你所熟悉的 less 命令都能有效。

man 所显示的参考手册,被分成几个章节,它们不仅仅包括用户命令,也包括系统管理员 命令、程序接口、文件格式等等。下表描绘了手册的布局:

表6-1: 手册页的组织形式

章节 内容
1 用户命令
2 程序接口内核系统调用
3 C 库函数程序接口
4 特殊文件,比如说设备结点和驱动程序
5 文件格式
6 游戏娱乐,如屏幕保护程序
7 其他方面
8 系统管理员命令

有时候,我们需要查看参考手册的特定章节,从而找到我们需要的信息。 如果我们要查找一种文件格式,而同时它也是一个命令名时,这种情况尤其正确。 没有指定章节号,我们总是得到第一个匹配项,可能在第一章节。我们这样使用 man 命令, 来指定章节号:

man section search_term

例如:

[chenfy@entrobus32 ~]$ man 5 passwd
在第 5 节中没有关于 passwd 的手册页条目。
[chenfy@entrobus32 ~]$

上所示结果异于教材,可能因为权限不够,毕竟公司集群上的linux系统。

apropos - 显示适当的命令

也有可能搜索参考手册列表,基于某个关键字的匹配项。虽然很粗糙但有时很有用。 下面是一个以”floppy”为关键词来搜索参考手册的例子:

[chenfy@entrobus32 ~]$ apropos floppy
fdformat (8)         - low-level format a floppy disk
[chenfy@entrobus32 ~]$ 

输出结果每行的第一个字段是手册页的名字,第二个字段展示章节。注意,man 命令加上”-k”选项, 和 apropos 完成一样的功能。

[chenfy@entrobus32 ~]$ man -k floppy
fdformat (8)         - low-level format a floppy disk
[chenfy@entrobus32 ~]$

whatis - 显示非常简洁的命令说明

whatis 程序显示匹配特定关键字的手册页的名字和一行命令说明:

The Most Brutal Man Page Of Them All

最晦涩难懂的手册页

As we have seen, the manual pages supplied with Linux and other Unix-like systems are intended as reference documentation and not as tutorials. Many man pages are hard to read, but I think that the grand prize for difficulty has got to go to the man page for bash. As I was doing my research for this book, I gave it careful review to ensure that I was covering most of its topics. When printed, it’s over eighty pages long and extremely dense, and its structure makes absolutely no sense to a new user.

正如我们所看到的,Linux 和类 Unix 的系统提供的手册页,只是打算作为参考手册使用, 而不是教程。许多手册页都很难阅读,但是我认为由于阅读难度而能拿到特等奖的手册页应该是 bash 手册页。因为我正在为这本书做我的研究,所以我很仔细地浏览了整个 bash 手册,为的是确保我讲述了 大部分的 bash 主题。当把 bash 参考手册整个打印出来,其篇幅有八十多页且内容极其紧密, 但对于初学者来说,其结构安排毫无意义。

On the other hand, it is very accurate and concise, as well as being extremely complete. So check it out if you dare and look forward to the day when you can read it and it all makes sense.

另一方面,bash 参考手册的内容非常简明精确,同时也非常完善。所以,如果你有胆量就查看一下, 并且期望有一天你能读懂它。

info - 显示程序 Info 条目

GNU 项目提供了一个命令程序手册页的替代物,称为”info”。info 内容可通过 info 阅读器 程序读取。info 页是超级链接形式的,和网页很相似。这有个例子:

File: coreutils.info,    Node: ls invocation,    Next: dir invocation,
 Up: Directory listing

10.1 `ls': List directory contents
==================================
...

info 程序读取 info 文件,info 文件是树型结构,分化为各个结点,每一个包含一个题目。 info 文件包含超级链接,它可以让你从一个结点跳到另一个结点。一个超级链接可通过 它开头的星号来辨别出来,把光标放在它上面并按下 enter 键,就可以激活它。

输入”info”,接着输入程序名称,启动 info。下表中的命令,当显示一个 info 页面时, 用来控制阅读器。

命令 行为
? 显示命令帮助
PgUp or Backspace 显示上一页
PgDn or Space 显示下一页
n 下一个 - 显示下一个结点
p 上一个 - 显示上一个结点
u Up - 显示当前所显示结点的父结点,通常是个菜单
Enter 激活光标位置下的超级链接
q 退出

到目前为止,我们所讨论的大多数命令行程序,属于 GNU 项目”coreutils”包,所以输入:

[me@linuxbox ~]$ info coreutils

注:

  1. q退出info。

README 和其它程序文档

许多安装在你系统中的软件,都有自己的文档文件,这些文件位于/usr/share/doc 目录下。 这些文件大多数是以文本文件的形式存储的,可用 less 阅读器来浏览。一些文件是 HTML 格式, 可用网页浏览器来阅读。我们可能遇到许多以”.gz”结尾的文件。这表示 gzip 压缩程序 已经压缩了这些文件。gzip 软件包包括一个特殊的 less 版本,叫做 zless,zless 可以显示由 gzip 压缩的文本文件的内容。

用别名(alias)创建你自己的命令

现在是时候,感受第一次编程经历了!我们将用 alias 命令创建我们自己的命令。但在 开始之前,我们需要展示一个命令行小技巧。可以把多个命令放在同一行上,命令之间 用”;”分开。它像这样工作:

command1; command2; command3...

我们会用到下面的例子:

[chenfy@entrobus32 ~]$ cd /usr; ls; cd -
bin  games    lib    libexec  sbin   src
etc  include  lib64  local    share  tmp
/home/chenfy
[chenfy@entrobus32 ~]$

正如我们看到的,我们在一行上联合了三个命令。首先更改目录到/usr,然后列出目录 内容,最后回到原始目录(用命令”cd -“),结束在开始的地方。现在,通过 alias 命令 把这一串命令转变为一个命令。我们要做的第一件事就是为我们的新命令构想一个名字。 比方说”test”。在使用”test”之前,查明是否”test”命令名已经存在系统中,是个很不错 的主意。为了查清此事,可以使用 type 命令:

[chenfy@entrobus32 ~]$ type test
test 是 shell 内嵌
[chenfy@entrobus32 ~]$

哦!”test”名字已经被使用了。试一下”foo”:

[chenfy@entrobus32 ~]$ type foo
-bash: type: foo: 未找到
[chenfy@entrobus32 ~]$

太棒了!”foo”还没被占用。创建命令别名:

[chenfy@entrobus32 ~]$ alias foo='cd /usr; ls; cd -'
[chenfy@entrobus32 ~]$ 

注意命令结构:

alias name='string'

在命令”alias”之后,输入“name”,紧接着(没有空格)是一个等号,等号之后是 一串用引号引起的字符串,字符串的内容要赋值给 name。我们定义了别名之后, 这个命令别名可以使用在任何地方。试一下:

[chenfy@entrobus32 ~]$ foo
bin  games    lib    libexec  sbin   src
etc  include  lib64  local    share  tmp
/home/chenfy
[chenfy@entrobus32 ~]$ 

我们也可以使用 type 命令来查看我们的别名:

[chenfy@entrobus32 ~]$ type foo
foo 是 `cd /usr; ls; cd -' 的别名
[chenfy@entrobus32 ~]$ 

删除别名,使用 unalias 命令,像这样:

[chenfy@entrobus32 ~]$ type foo
foo 是 `cd /usr; ls; cd -' 的别名
[chenfy@entrobus32 ~]$ unalias foo
[chenfy@entrobus32 ~]$ type foo
-bash: type: foo: 未找到
[chenfy@entrobus32 ~]$ 

虽然我们有意避免使用已经存在的命令名来命名我们的别名,但这是常做的事情。通常, 会把一个普遍用到的选项加到一个经常使用的命令后面。例如,之前见到的 ls 命令,会 带有色彩支持:

[chenfy@entrobus32 ~]$ type ls
ls 是 `ls --color=auto' 的别名
[chenfy@entrobus32 ~]$ 

在命令行中定义别名有点儿小问题。当你的 shell 会话结束时,它们会消失。随后的章节里, 我们会了解怎样把自己的别名添加到文件中去,每次我们登录系统,这些文件会建立系统环境。 现在,好好享受我们刚经历过的,步入 shell 编程世界的第一步吧,虽然微小。

拜访老朋友

既然我们已经学习了怎样找到命令的帮助文档,那就试着查阅,到目前为止,我们学到的所有 命令的文档。学习命令其它可用的选项,练习一下!

拓展阅读:(略)

第七章:重定向

这堂课,我们来介绍可能是命令行最酷的特性。它叫做 I/O 重定向。”I/O”代表输入/输出, 通过这个工具,你可以将命令的输入来源以及输出地点重定向为文件。也可以把多个命令连接起来组成一个强大的命令管道。为了展示这个工具,我们将叙述 以下命令:

标准输入、输出和错误

到目前为止,我们用到的许多程序都会产生某种输出。这种输出,经常由两种类型组成。 第一,程序运行结果;这是说,程序要完成的功能。第二,我们得到状态和错误信息, 这些告诉我们程序进展。如果我们观察一个命令,像 ls,会看到它的运行结果和错误信息 显示在屏幕上。

与 Unix 主题“任何东西都是一个文件”保持一致,程序,比方说 ls,实际上把他们的运行结果 输送到一个叫做标准输出的特殊文件(经常用 stdout 表示),而它们的状态信息则送到另一个 叫做标准错误的文件(stderr)。默认情况下,标准输出和标准错误都连接到屏幕,而不是 保存到磁盘文件。除此之外,许多程序从一个叫做标准输入(stdin)的设备得到输入,默认情况下, 标准输入连接到键盘。

I/O 重定向允许我们更改输出地点和输入来源。一般地,输出送到屏幕,输入来自键盘, 但是通过 I/O 重定向,我们可以做出改变。

标准输出重定向

I/O 重定向允许我们来重定义标准输出的地点。我们使用 “>” 重定向符后接文件名将标准输出重定向到除屏幕 以外的另一个文件。为什么我们要这样做呢?因为有时候把一个命令的运行结果存储到 一个文件很有用处。例如,我们可以告诉 shell 把 ls 命令的运行结果输送到文件 ls-output.txt 中去, 由文件代替屏幕。

[chenfy@entrobus32 ~]$ ls -l /usr/bin > ls-output.txt
[chenfy@entrobus32 ~]$ 

这里,我们创建了一个长长的目录/usr/bin 列表,并且输送程序运行结果到文件 ls-output.txt 中。 我们检查一下重定向的命令输出结果:

[chenfy@entrobus32 ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 chenfy chenfy 98004 9月  30 21:05 ls-output.txt
[chenfy@entrobus32 ~]$

好;一个不错的大型文本文件。如果我们用 less 阅读器来查看这个文件,我们会看到文件 ls-output.txt 的确包含 ls 命令的执行结果。

[chenfy@entrobus32 ~]$ less ls-output.txt
...

现在,重复我们的重定向测试,但这次有改动。我们把目录换成一个不存在的目录。

[chenfy@entrobus32 ~]$ ls -l /bin/usr > ls-output.txt
ls: 无法访问/bin/usr: 没有那个文件或目录
[chenfy@entrobus32 ~]$ 

我们收到一个错误信息。这讲得通,因为我们指定了一个不存在的目录/bin/usr, 但是为什么这条错误信息显示在屏幕上而不是被重定向到文件 ls-output.txt?答案是, ls 程序不把它的错误信息输送到标准输出。反而,像许多写得不错的 Unix 程序,ls 把 错误信息送到标准错误。因为我们只是重定向了标准输出,而没有重定向标准错误, 所以错误信息被送到屏幕。马上,我们将知道怎样重定向标准错误,但是首先看一下 我们的输出文件发生了什么事情。

[chenfy@entrobus32 ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 chenfy chenfy 0 9月  30 21:08 ls-output.txt
[chenfy@entrobus32 ~]$ 

文件长度为零!这是因为,当我们使用 “>” 重定向符来重定向输出结果时,目标文件总是从开头被重写。 因为我们 ls 命令没有产生运行结果,只有错误信息,重定向操作开始重写文件,然后 由于错误而停止,导致文件内容清空。事实上,如果我们需要清空一个文件内容(或者创建一个 新的空文件),可以使用这样的技巧:

[chenfy@entrobus32 ~]$ > ls-output.txt
[chenfy@entrobus32 ~]$

简单地使用重定向符,没有命令在它之前,这会清空一个已存在文件的内容或是 创建一个新的空文件。

所以,怎样才能把重定向结果追加到文件内容后面,而不是从开头重写文件?为了这个目的, 我们使用”>>“重定向符,像这样:

[chenfy@entrobus32 ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 chenfy chenfy 0 9月  30 21:08 ls-output.txt
[chenfy@entrobus32 ~]$ > ls-output.txt
[chenfy@entrobus32 ~]$ ls -l /usr/bin >> ls-output.txt
[chenfy@entrobus32 ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 chenfy chenfy 98004 9月  30 21:13 ls-output.txt
[chenfy@entrobus32 ~]$ ls -l /usr/bin >> ls-output.txt
[chenfy@entrobus32 ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 chenfy chenfy 196008 9月  30 21:14 ls-output.txt
[chenfy@entrobus32 ~]$  

使用”>>“操作符,将导致输出结果添加到文件内容之后。如果文件不存在,文件会 被创建,就如使用了”>”操作符。把它放到测试中,如上示例所示。

我们重复执行命令二次,导致输出文件大小是原来的二倍。

标准错误重定向没有专用的重定向操作符。为了重定向标准错误,我们必须参考其文件描述符。 一个程序可以在几个编号的文件流中的任一个上产生输出。虽然我们已经将这些文件流的前 三个称作标准输入、输出和错误,shell 内部分别将其称为文件描述符0、1和2。shell 使用文件描述符提供 了一种表示法来重定向文件。因为标准错误和文件描述符2一样,我们用这种 表示法来重定向标准错误:

[chenfy@entrobus32 ~]$ ls -l /bin/usr 2> ls-error.txt
[chenfy@entrobus32 ~]$ 

文件描述符”2”,紧挨着放在重定向操作符之前,来执行重定向标准错误到文件 ls-error.txt 任务。

重定向标准输出和错误到同一个文件

可能有这种情况,我们希望捕捉一个命令的所有输出到一个文件。为了完成这个,我们 必须同时重定向标准输出和标准错误。有两种方法来完成任务。第一个,传统的方法, 在旧版本 shell 中也有效:

[chenfy@entrobus32 ~]$ ls -l /bin/usr > ls-output.txt 2>&1
[chenfy@entrobus32 ~]$ 

使用这种方法,我们完成两个重定向。首先重定向标准输出到文件 ls-output.txt,然后 重定向文件描述符2(标准错误)到文件描述符1(标准输出)使用表示法2>&1。

注意重定向的顺序安排非常重要。标准错误的重定向必须总是出现在标准输出 重定向之后,要不然它不起作用。上面的例子:

ls-output.txt 2>&1

重定向标准错误到文件 ls-output.txt,但是如果命令顺序改为:

2>&1 >ls-output.txt

则标准错误定向到屏幕。

现在的 bash 版本提供了第二种方法,更精简合理的方法来执行这种联合的重定向。

[chenfy@entrobus32 ~]$ ls -l /bin/usr &> ls-output.txt
[chenfy@entrobus32 ~]$ 

在这个例子里面,我们使用单单一个表示法 &> 来重定向标准输出和错误到文件 ls-output.txt。

处理不需要的输出

有时候“沉默是金”,我们不想要一个命令的输出结果,只想把它们扔掉。这种情况 尤其适用于错误和状态信息。系统通过重定向输出结果到一个叫做”/dev/null”的特殊文件, 为我们提供了解决问题的方法。这个文件是系统设备,叫做位存储桶,它可以 接受输入,并且对输入不做任何处理。为了隐瞒命令错误信息,我们这样做:

[chenfy@entrobus32 ~]$ ls -l /bin/usr 2> /dev/null
[chenfy@entrobus32 ~]$ 

/dev/null in Unix Culture

Unix 文化中的/dev/null

The bit bucket is an ancient Unix concept and due to its universality, has appeared in many parts of Unix culture. When someone says he/she is sending your comments to /dev/null, now you know what it means. For more examples, see the Wikipedia article on “/dev/null”.

位存储桶是个古老的 Unix 概念,由于它的普遍性,它的身影出现在 Unix 文化的 许多部分。当有人说他/她正在发送你的评论到/dev/null,现在你应该知道那是 什么意思了。更多的例子,可以阅读 Wikipedia 关于”/dev/null”的文章。

标准输入重定向

到目前为止,我们还没有遇到一个命令是利用标准输入的(实际上我们遇到过了,但是 一会儿再揭晓谜底),所以我们需要介绍一个。

cat - 连接文件

cat 命令读取一个或多个文件,然后复制它们到标准输出,就像这样:

cat [file]

在大多数情况下,你可以认为 cat 命令相似于 DOS 中的 TYPE 命令。你可以使用 cat 来显示 文件而没有分页,例如:

[chenfy@entrobus32 ~]$ cat ls-output.txt
ls: 无法访问/bin/usr: 没有那个文件或目录
[chenfy@entrobus32 ~]$ 

将会显示文件 ls-output.txt 的内容。cat 经常被用来显示简短的文本文件。因为 cat 可以 接受不只一个文件作为参数,所以它也可以用来把文件连接在一起。比方说我们下载了一个 大型文件,这个文件被分离成多个部分(USENET 中的多媒体文件经常以这种方式分离), 我们想把它们连起来。如果文件命名为: movie.mpeg.001 movie.mpeg.002 … movie.mpeg.099

我们能用这个命令把它们连接起来:

cat movie.mpeg.0* > movie.mpeg

因为通配符总是以有序的方式展开,所以这些参数会以正确顺序安排。

这很好,但是这和标准输入有什么关系呢?没有任何关系,让我们试着做些其他的工作。 如果我们输入不带参数的”cat”命令,会发生什么呢:

[chenfy@entrobus32 ~]$ cat

没有发生任何事情,它只是坐在那里,好像挂掉了一样。看起来是那样,但是它正在做它该做的事情:

如果 cat 没有给出任何参数,它会从标准输入读入数据,又因为标准输入默认情况下连接到键盘, 它正在等待我们输入数据!试试这个:

[chenfy@entrobus32 ~]$ cat
The quick brown fox jumped over the lazy dog.

下一步,输入 Ctrl-d(按住 Ctrl 键同时按下”d”),来告诉 cat,在标准输入中, 它已经到达文件末尾(EOF):

[chenfy@entrobus32 ~]$ cat
The quick brown fox jumped over the lazy dog.The quick brown fox jumped over the lazy dog.

由于没有文件名参数,cat 复制标准输入到标准输出,所以我们看到文本行重复出现。

注:

  1. 关于如何退出cat命令,可ctrl+z退出。

我们可以使用这种行为来创建简短的文本文件。比方说,我们想创建一个叫做”lazy_dog.txt” 的文件,这个文件包含例子中的文本。我们这样做:

[chenfy@entrobus32 ~]$ cat > lazy_dog.txt
The quick brown fox jumped over the lazy dog.

输入命令,其后输入要放入文件中的文本。记住,最后输入 Ctrl-d。通过使用这个命令,我们 实现了世界上最低能的文字处理器!看一下运行结果,我们使用 cat 来复制文件内容到 标准输出:

[chenfy@entrobus32 ~]$  cat lazy_dog.txt
The quick brown fox jumped over the lazy dog.

现在我们知道 cat 怎样接受标准输入,除了文件名参数,让我们试着重定向标准输入:

[chenfy@entrobus32 ~]$ cat < lazy_dog.txt
The quick brown fox jumped over the lazy dog.

使用“<”重定向操作符,我们把标准输入源从键盘改到文件 lazy_dog.tx。我们看到结果 和传递单个文件名作为参数的执行结果一样。把这和传递一个文件名参数作比较,不是特别有意义, 但它是用来说明把一个文件作为标准输入源。有其他的命令更好地利用了标准输入,我们不久将会看到。

在我们继续之前,查看 cat 的手册页,因为它有几个有趣的选项。

管道线

命令从标准输入读取数据并输送到标准输出的能力被一个称为管道线的 shell 特性所利用。 使用管道操作符”|”(竖杠),一个命令的标准输出可以通过管道送至另一个命令的标准输入:

command1 | command2

为了全面地说明这个命令,我们需要一些命令。是否记得我们说过,我们已经知道有一个 命令接受标准输入?它是 less 命令。我们用 less 来一页一页地显示任何命令的输出,命令把 它的运行结果输送到标准输出:

[chenfy@entrobus32 ~]$ ls -l /usr/bin | less

这极其方便!使用这项技术,我们可以方便地检测会产生标准输出的任一命令的运行结果。

过滤器

管道线经常用来对数据完成复杂的操作。有可能会把几个命令放在一起组成一个管道线。 通常,以这种方式使用的命令被称为过滤器。过滤器接受输入,以某种方式改变它,然后 输出它。第一个我们想试验的过滤器是 sort。想象一下,我们想把目录/bin 和/usr/bin 中 的可执行程序都联合在一起,再把它们排序,然后浏览执行结果:

[chenfy@entrobus32 ~]$ ls /bin /usr/bin | sort | less

因为我们指定了两个目录(/bin 和/usr/bin),ls 命令的输出结果由有序列表组成, 各自针对一个目录。通过在管道线中包含 sort,我们改变输出数据,从而产生一个 有序列表。

uniq - 报道或忽略重复行

uniq 命令经常和 sort 命令结合在一起使用。uniq 从标准输入或单个文件名参数接受数据有序 列表(详情查看 uniq 手册页),默认情况下,从数据列表中删除任何重复行。所以,为了确信 我们的列表中不包含重复句子(这是说,出现在目录/bin 和/usr/bin 中重名的程序),我们添加 uniq 到我们的管道线中:

[chenfy@entrobus32 ~]$ ls /bin /usr/bin | sort | uniq | less

在这个例子中,我们使用 uniq 从 sort 命令的输出结果中,来删除任何重复行。如果我们想看到 重复的数据列表,让 uniq 命令带上”-d”选项,就像这样:

[chenfy@entrobus32 ~]$ ls /bin /usr/bin | sort | uniq -d | less

wc - 打印行数、字数和字节数

wc(字计数)命令是用来显示文件所包含的行数、字数和字节数。例如:

[chenfy@entrobus32 ~]$ wc ls-output.txt
 1  3 54 ls-output.txt
[chenfy@entrobus32 ~]$ 

在这个例子中,wc 打印出来三个数字:包含在文件 ls-output.txt 中的行数,单词数和字节数, 正如我们先前的命令,如果 wc 不带命令行参数,它接受标准输入。”-l”选项限制命令输出只能 报道行数。添加 wc 到管道线来统计数据,是个很便利的方法。查看我们的有序列表中程序个数, 我们可以这样做:

[chenfy@entrobus32 ~]$ ls /bin /usr/bin | sort | uniq | wc -l
1406
[chenfy@entrobus32 ~]$ 

grep - 打印匹配行

grep 是个很强大的程序,用来找到文件中的匹配文本。这样使用 grep 命令:

grep pattern [file...]

当 grep 遇到一个文件中的匹配”模式”,它会打印出包含这个类型的行。grep 能够匹配的模式可以 很复杂,但是现在我们把注意力集中在简单文本匹配上面。在后面的章节中,我们将会研究 高级模式,叫做正则表达式。

比如说,我们想在我们的程序列表中,找到文件名中包含单词”zip”的所有文件。这样一个搜索, 可能让我们了解系统中的一些程序与文件压缩有关系。这样做:

[chenfy@entrobus32 ~]$ ls /bin /usr/bin | sort |uniq | grep zip
bunzip2
bzip2
bzip2recover
funzip
gpg-zip
gunzip
gzip
unzip
unzipsfx
zip
zipcloak
zipgrep
zipinfo
zipnote
zipsplit
[chenfy@entrobus32 ~]$ 

grep 有一些方便的选项:”-i”使得 grep 在执行搜索时忽略大小写(通常,搜索是大小写 敏感的),”-v”选项会告诉 grep 只打印不匹配的行。

head / tail - 打印文件开头部分/结尾部分

有时候你不需要一个命令的所有输出。可能你只想要前几行或者后几行的输出内容。 head 命令打印文件的前十行,而 tail 命令打印文件的后十行。默认情况下,两个命令 都打印十行文本,但是可以通过”-n”选项来调整命令打印的行数。

[chenfy@entrobus32 ~]$ head -n 5 ls-output.txt
ls: 无法访问/bin/usr: 没有那个文件或目录
[chenfy@entrobus32 ~]$ tail -n 5 ls-output.txt
ls: 无法访问/bin/usr: 没有那个文件或目录
[chenfy@entrobus32 ~]$ 

它们也能用在管道线中:

[chenfy@entrobus32 ~]$ ls /usr/bin | tail -n 5
zookeeper-server
zookeeper-server-cleanup
zookeeper-server-initialize
zsh
zsoelim
[chenfy@entrobus32 ~]$ 

tail 有一个选项允许你实时地浏览文件。当观察日志文件的进展时,这很有用,因为 它们同时在被写入。在以下的例子里,我们要查看目录/var/log 里面的信息文件。在 一些 Linux 发行版中,要求有超级用户权限才能阅读这些文件,因为文件/var/log/messages 可能包含安全信息。

[chenfy@entrobus32 ~]$ tail -f /var/log/messages
tail: 无法打开"/var/log/messages" 读取数据: 权限不够
tail: 没有剩余文件
[chenfy@entrobus32 ~]$ 

使用”-f”选项,tail 命令继续监测这个文件,当新的内容添加到文件后,它们会立即 出现在屏幕上。这会一直继续下去直到你输入 Ctrl-c。

tee - 从 Stdin 读取数据,并同时输出到 Stdout 和文件

为了和我们的管道隐喻保持一致,Linux 提供了一个叫做 tee 的命令,这个命令制造了 一个”tee”,安装到我们的管道上。tee 程序从标准输入读入数据,并且同时复制数据 到标准输出(允许数据继续随着管道线流动)和一个或多个文件。当在某个中间处理 阶段来捕捉一个管道线的内容时,这很有帮助。这里,我们重复执行一个先前的例子, 这次包含 tee 命令,在 grep 过滤管道线的内容之前,来捕捉整个目录列表到文件 ls.txt:

[chenfy@entrobus32 ~]$ ls /usr/bin | tee ls.txt | grep zip
bunzip2
bzip2
bzip2recover
funzip
gpg-zip
gunzip
gzip
unzip
unzipsfx
zip
zipcloak
zipgrep
zipinfo
zipnote
zipsplit
[chenfy@entrobus32 ~]$ 

总结归纳

一如既往,查看这章学到的每一个命令的文档。我们已经知道了他们最基本的用法。 它们还有很多有趣的选项。随着我们 Linux 经验的积累,我们会了解命令行重定向特性 在解决特殊问题时非常有用处。有许多命令利用标准输入和输出,而几乎所有的命令行 程序都使用标准错误来显示它们的详细信息。

Linux Is About Imagination

Linux 可以激发我们的想象

When I am asked to explain the difference between Windows and Linux, I often use a toy analogy.

当我被要求解释 Windows 与 Linux 之间的差异时,我经常拿玩具来作比喻。

Windows is like a Game Boy. You go to the store and buy one all shiny new in the box. You take it home, turn it on and play with it. Pretty graphics, cute sounds. After a while though, you get tired of the game that came with it so you go back to the store and buy another one. This cycle repeats over and over. Finally, you go back to the store and say to the person behind the counter, “I want a game that does this!” only to be told that no such game exists because there is no “market demand” for it. Then you say, “But I only need to change this one thing!” The person behind the counter says you can’t change it. The games are all sealed up in their cartridges. You discover that your toy is limited to the games that others have decided that you need and no more.

Windows 就像一个游戏机。你去商店,买了一个包装在盒子里面的全新的游戏机。 你把它带回家,打开盒子,开始玩游戏。精美的画面,动人的声音。玩了一段时间之后, 你厌倦了它自带的游戏,所以你返回商店,又买了另一个游戏机。这个过程反复重复。 最后,你玩腻了游戏机自带的游戏,你回到商店,告诉售货员,“我想要一个这样的游戏!” 但售货员告诉你没有这样的游戏存在,因为它没有“市场需求”。然后你说,“但是我只 需要修改一下这个游戏!“,售货员又告诉你不能修改它。所有游戏都被封装在它们的 存储器中。到头来,你发现你的玩具只局限于别人为你规定好的游戏。

Linux, on the other hand, is like the world’s largest Erector Set. You open it up and it’s just a huge collection of parts. A lot of steel struts, screws, nuts, gears, pulleys, motors, and a few suggestions on what to build. So you start to play with it. You build one of the suggestions and then another. After a while you discover that you have your own ideas of what to make. You don’t ever have to go back to the store, as you already have everything you need. The Erector Set takes on the shape of your imagination. It does what you want.

另一方面,Linux 就像一个全世界上最大的建造模型。你打开它,发现它只是一个巨大的 部件集合。有许多钢支柱、螺钉、螺母、齿轮、滑轮、发动机和一些怎样来建造它的说明书。 然后你开始摆弄它。你建造了一个又一个样板模型。过了一会儿,你发现你要建造自己的模型。 你不必返回商店,因为你已经拥有了你需要的一切。建造模型以你构想的形状为模板,搭建 你想要的模型。

Your choice of toys is, of course, a personal thing, so which toy would you find more satisfying?

当然,选择哪一个玩具,是你的事情,那么你觉得哪个玩具更令人满意呢?

第八章:从 shell 眼中看世界

在这一章我们将看到,当你按下 enter 键后,发生在命令行中的一些“魔法”。尽管我们会 深入研究几个复杂而有趣的 shell 特性,但我们只需要使用一个新命令:

(字符)展开

每当你输入一个命令并按下 enter 键,bash 会在执行你的命令之前对输入 的字符完成几个步骤的处理。我们已经见过几个例子:例如一个简单的字符序列”*”, 对 shell 来说有着多么丰富的涵义。这背后的的过程叫做(字符)展开。通过展开, 你输入的字符,在 shell 对它起作用之前,会展开成为别的字符。为了说明这一点 ,让我们看一看 echo 命令。echo 是一个 shell 内建命令,可以完成非常简单的任务。 它将它的文本参数打印到标准输出中。

[chenfy@entrobus32 ~]$ echo this is a test
this is a test
[chenfy@entrobus32 ~]$

这个命令的作用相当简单明了。传递到 echo 命令的任一个参数都会在(屏幕上)显示出来。 让我们试试另一个例子:

[chenfy@entrobus32 ~]$ echo *
: Anaconda3-5.1.0-Linux-x86_64.sh file filesort inputs lazy_dog.txt learn ls-error.txt ls-ouput.txt ls-output.txt ls.txt ml outputs patitioner people_new_json people_new_text people_new.txt pynotes rdd replaceContent.py resources secondarysort streaming TestPartitioner.py test.txt text tmp TopN.py try Untitled.ipynb vim word.txt writeback
[chenfy@entrobus32 ~]$ 

那么刚才发生了什么事情呢? 为什么 echo 不打印“*”呢?如果你回忆起我们所学过的关于通配符的内容,这个“*”字符意味着匹配文件名中的任意字符,但在原先的讨论中我们并不知道 shell 是怎样实现这个功能的。简单的答案就是 shell 在 echo 命 令被执行前把“*”展开成了另外的东西(在这里,就是在当前工作目录下的文件名字)。 当回车键被按下时,shell 在命令被执行前在命令行上自动展开任何符合条件的字符, 所以 echo 命令的实际参数并不是“*”,而是它展开后的结果。知道了这个以后, 我们就能明白 echo 的行为符合预期。

路径名展开

通配符所依赖的工作机制叫做路径名展开。如果我们试一下在之前的章节中使用的技巧, 我们会看到它们实际上是展开。给定一个家目录,它看起来像这样:

[chenfy@entrobus32 ~]$ ls
:                                ml                 streaming
Anaconda3-5.1.0-Linux-x86_64.sh  outputs            TestPartitioner.py
file                             patitioner         test.txt
filesort                         people_new_json    text
inputs                           people_new_text    tmp
lazy_dog.txt                     people_new.txt     TopN.py
learn                            pynotes            try
ls-error.txt                     rdd                Untitled.ipynb
ls-ouput.txt                     replaceContent.py  vim
ls-output.txt                    resources          word.txt
ls.txt                           secondarysort      writeback
[chenfy@entrobus32 ~]$

我们能够执行以下的展开:

[chenfy@entrobus32 ~]$ echo f*
file filesort
[chenfy@entrobus32 ~]$

和:

[chenfy@entrobus32 ~]$ echo *s
inputs outputs pynotes resources
[chenfy@entrobus32 ~]$

甚至是:

[chenfy@entrobus32 ~]$ echo [[:upper:]]*
Anaconda3-5.1.0-Linux-x86_64.sh TestPartitioner.py TopN.py Untitled.ipynb
[chenfy@entrobus32 ~]$ 

查看家目录之外的目录:

[chenfy@entrobus32 ~]$ echo /usr/*/share
/usr/local/share
[chenfy@entrobus32 ~]$ 

Pathname Expansion Of Hidden Files

隐藏文件路径名展开

As we know, filenames that begin with a period character are hidden. Pathname expansion also respects this behavior. An expansion such as:

正如我们知道的,以圆点字符开头的文件名是隐藏文件。路径名展开也尊重这种 行为。像这样的展开:

echo *

does not reveal hidden files.

不会显示隐藏文件

It might appear at first glance that we could include hidden files in an expansion by starting the pattern with a leading period, like this:

直觉告诉我们,如果展开模式以一个圆点开头,我们就能够在展开中包含隐藏文件, 就像这样:

echo .*

It almost works. However, if we examine the results closely, we will see that the names “.” and “..” will also appear in the results. Since these names refer to the current working directory and its parent directory, using this pattern will likely produce an incorrect result. We can see this if we try the command:

它几乎要起作用了。然而,如果我们仔细检查一下输出结果,我们会看到名字”.” 和”..”也出现在结果中。由于它们是指当前工作目录和父目录,使用这种 模式可能会产生不正确的结果。我们可以通过这个命令来验证:

ls -d .* | less

To correctly perform pathname expansion in this situation, we have to employ a more specific pattern. This will work correctly:

为了在这种情况下正确地完成路径名展开,我们应该使用一个更精确的模式。 这个模式会正确地工作:

ls -d .[!.]?*

This pattern expands into every filename that begins with a period, does not include a second period, contains at least one additional character and can be followed by any other characters. This will work correctly with most hidden files (though it still won’t include filenames with multiple leading periods). The ls command with the -A option (“almost all”) will provide a correct listing of hidden files:

这种模式展开成所有以圆点开头,第二个字符不包含圆点,再包含至少一个字符, 并且这个字符之后紧接着任意多个字符的文件名。这个命令将正确列出大多数的隐藏文件 (但仍不能包含以多个圆点开头的文件名)。带有 -A 选项(“几乎所有”)的 ls 命令能够提供一份正确的隐藏文件清单:

ls -A

波浪线展开

可能你从我们对 cd 命令的介绍中回想起来,波浪线字符(“~”)有特殊的含义。当它用在 一个单词的开头时,它会展开成指定用户的家目录名,如果没有指定用户名,则展开成当前用户的家目录:

[chenfy@entrobus32 ~]$ echo ~
/home/chenfy
[chenfy@entrobus32 ~]$

如果有用户”chenfy”这个帐号,那么:

[chenfy@entrobus32 ~]$ echo ~chenfy
/home/chenfy
[chenfy@entrobus32 ~]$ 

算术表达式展开

shell 在展开中执行算数表达式。这允许我们把 shell 提示当作计算器来使用:

[chenfy@entrobus32 ~]$ echo $((2 + 2))
4
[chenfy@entrobus32 ~]$ 

算术表达式展开使用这种格式:

$((expression))

(以上括号中的)表达式是指算术表达式,它由数值和算术操作符组成。

算术表达式只支持整数(全部是数字,不带小数点),但是能执行很多不同的操作。这里是 一些它支持的操作符:

表 8-1: 算术操作符

操作符 说明
+
-
*
/ 除(但是记住,因为展开只是支持整数除法,所以结果是整数。)
% 取余,只是简单的意味着,“余数”
** 取幂

在算术表达式中空格并不重要,并且表达式可以嵌套。例如,5的平方乘以3:

[chenfy@entrobus32 ~]$ echo $(($((5**2)) * 3))
75
[chenfy@entrobus32 ~]$ 

一对括号可以用来把多个子表达式括起来。通过这个技术,我们可以重写上面的例子, 同时用一个展开代替两个,来得到一样的结果:

[chenfy@entrobus32 ~]$ echo $(((5**2) * 3))
75
[chenfy@entrobus32 ~]$ 

这是一个使用除法和取余操作符的例子。注意整数除法的结果:

[chenfy@entrobus32 ~]$ echo Five divided by two equals $((5/2))
Five divided by two equals 2
[chenfy@entrobus32 ~]$ 

在35章会更深入地讨论算术表达式的内容。

花括号展开

可能最奇怪的展开是花括号展开。通过它,你可以从一个包含花括号的模式中 创建多个文本字符串。这是一个例子:

[chenfy@entrobus32 ~]$ echo Front-{A,B,C}-Black
Front-A-Black Front-B-Black Front-C-Black
[chenfy@entrobus32 ~]$ 

花括号展开模式可能包含一个开头部分叫做报头,一个结尾部分叫做附言。花括号表达式本身可 能包含一个由逗号分开的字符串列表,或者一个整数区间,或者单个的字符的区间。这种模式不能 嵌入空白字符。这个例子中使用了一个整数区间:

[chenfy@entrobus32 ~]$ echo Number_{1..5}
Number_1 Number_2 Number_3 Number_4 Number_5
[chenfy@entrobus32 ~]$ 

倒序排列的字母区间:

[chenfy@entrobus32 ~]$ echo {Z..A}
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
[chenfy@entrobus32 ~]$ 

花括号展开可以嵌套:

[chenfy@entrobus32 ~]$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b
[chenfy@entrobus32 ~]$ 

那么这对什么有好处呢?最常见的应用是,创建一系列的文件或目录列表。例如, 如果我们是摄影师,有大量的相片。我们想把这些相片按年月先后组织起来。首先, 我们要创建一系列以数值”年-月”形式命名的目录。通过这种方式,可以使目录名按照 年代顺序排列。我们可以手动键入整个目录列表,但是工作量太大了,并且易于出错。 反之,我们可以这样做:

[chenfy@entrobus32 ~]$ mkdir Pics
[chenfy@entrobus32 ~]$ cd Pics
[chenfy@entrobus32 Pics]$ mkdir {2007..2009}-0{1..9} {2007..2009}-{10..12}
[chenfy@entrobus32 Pics]$ ls
2007-01  2007-06  2007-11  2008-04  2008-09  2009-02  2009-07  2009-12
2007-02  2007-07  2007-12  2008-05  2008-10  2009-03  2009-08
2007-03  2007-08  2008-01  2008-06  2008-11  2009-04  2009-09
2007-04  2007-09  2008-02  2008-07  2008-12  2009-05  2009-10
2007-05  2007-10  2008-03  2008-08  2009-01  2009-06  2009-11
[chenfy@entrobus32 Pics]$ 

棒极了!

参数展开

在这一章我们将会简单介绍参数展开,但会在后续章节中进行详细讨论。这个特性在 shell 脚本中比直接在命令行中更有用。 它的许多功能和系统存储小块数据,并给每块数据命名的能力有关系。许多像这样的小块数据, 更恰当的称呼应该是变量,可供你方便地检查它们。例如,叫做”USER”的变量包含你的 用户名。可以这样做来调用参数,并查看 USER 中的内容,:

[chenfy@entrobus32 Pics]$ echo $USER
chenfy
[chenfy@entrobus32 Pics]$ 

要查看有效的变量列表,可以试试这个:

[chenfy@entrobus32 Pics]$ printenv | less

你可能注意到在其它展开类型中,如果你误输入一个模式,展开就不会发生。这时 echo 命令只简单地显示误键入的模式。但在参数展开中,如果你拼写错了一个变量名, 展开仍然会进行,只是展开的结果是一个空字符串:

[chenfy@entrobus32 Pics]$ echo $SUER

[chenfy@entrobus32 Pics]$

命令替换

命令替换允许我们把一个命令的输出作为一个展开模式来使用:

[chenfy@entrobus32 Pics]$ echo $(ls)
2007-01 2007-02 2007-03 2007-04 2007-05 2007-06 2007-07 2007-08 2007-09 2007-10 2007-11 2007-12 2008-01 2008-02 2008-03 2008-04 2008-05 2008-06 2008-07 2008-08 2008-09 2008-10 2008-11 2008-12 2009-01 2009-02 2009-03 2009-04 2009-05 2009-06 2009-07 2009-08 2009-09 2009-10 2009-11 2009-12
[chenfy@entrobus32 Pics]$ 

我最喜欢用的一行命令是像这样的:

[chenfy@entrobus32 ~]$ ls -l $(which cp)
-rwxr-xr-x. 1 root root 155176 4月  11 2018 /usr/bin/cp
[chenfy@entrobus32 ~]$ 

这里我们把 which cp 的执行结果作为一个参数传递给 ls 命令,因此可以在不知道 cp 命令 完整路径名的情况下得到它的文件属性列表。我们不只限制于简单命令。也可以使用整个管道线 (只展示部分输出):

[chenfy@entrobus32 ~]$ file $(ls /usr/bin/* | grep zip)
/usr/bin/bunzip2:      symbolic link to `bzip2'
...

在这个例子中,管道线的输出结果成为 file 命令的参数列表。

在旧版 shell 程序中,有另一种语法也支持命令替换,可与刚提到的语法轮换使用。 bash 也支持这种语法。它使用倒引号来代替美元符号和括号:

[chenfy@entrobus32 ~]$ ls -l `which cp`
-rwxr-xr-x. 1 root root 155176 4月  11 2018 /usr/bin/cp
[chenfy@entrobus32 ~]$ 

引用

我们已经知道 shell 有许多方式可以完成展开,现在是时候学习怎样来控制展开了。 以下面例子来说明:

[chenfy@entrobus32 ~]$ echo this is a      tree
this is a tree
[chenfy@entrobus32 ~]$

或者:

[chenfy@entrobus32 ~]$ echo The total is $100.00
The total is 00.00
[chenfy@entrobus32 ~]$ 

在第一个例子中,shell 利用单词分割删除掉 echo 命令的参数列表中多余的空格。在第二个例子中, 参数展开把 $1 的值替换为一个空字符串,因为 1 是没有定义的变量。shell 提供了一种 叫做引用的机制,来有选择地禁止不需要的展开。

我们将要看一下引用的第一种类型,双引号。如果你把文本放在双引号中, shell 使用的特殊字符,都失去它们的特殊含义,被当作普通字符来看待。 有几个例外: $,\ (反斜杠),和 `(倒引号)。这意味着单词分割、路径名展开、 波浪线展开和花括号展开都将失效,然而参数展开、算术展开和命令替换 仍然执行。使用双引号,我们可以处理包含空格的文件名。比方说我们是不幸的 名为 two words.txt 文件的受害者。如果我们试图在命令行中使用这个 文件,单词分割机制会导致这个文件名被看作两个独自的参数,而不是所期望 的单个参数:

[chenfy@entrobus32 ~]$ ls two words.txt
ls: 无法访问two: 没有那个文件或目录
ls: 无法访问words.txt: 没有那个文件或目录
[chenfy@entrobus32 ~]$ 

使用双引号,我们可以阻止单词分割,得到期望的结果;进一步,我们甚至可以修复 破损的文件名。

[chenfy@entrobus32 ~]$ ls -l "two words.txt"
-rw-rw-r-- 1 chenfy chenfy 27 10月  1 16:12 two words.txt
[chenfy@entrobus32 ~]$ mv "two words.txt" two_words.txt
[chenfy@entrobus32 ~]$ 

你瞧!现在我们不必一直输入那些讨厌的双引号了。

记住,在双引号中,参数展开、算术表达式展开和命令替换仍然有效:

[chenfy@entrobus32 ~]$ echo "$USER $((2+2)) $(cal)"
chenfy 4       十月 2019     
日 一 二 三 四 五 六
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
[chenfy@entrobus32 ~]$ 

我们应该花费一点时间来看一下双引号在命令替换中的效果。首先仔细研究一下单词分割 是怎样工作的。在之前的范例中,我们已经看到单词分割机制是怎样来删除文本中额外空格的:

[chenfy@entrobus32 ~]$ echo this is a        tree
this is a tree
[chenfy@entrobus32 ~]$ 

在默认情况下,单词分割机制会在单词中寻找空格,制表符,和换行符,并把它们看作 单词之间的界定符。这意味着无引用的空格,制表符和换行符都不是文本的一部分, 它们只作为分隔符使用。由于它们把单词分为不同的参数,所以在上面的例子中, 命令行包含一个带有四个不同参数的命令。如果我们加上双引号:

[chenfy@entrobus32 ~]$ echo "this is a       tree"
this is a       tree
[chenfy@entrobus32 ~]$ 

单词分割被禁止,内嵌的空格也不会被当作界定符,它们成为参数的一部分。 一旦加上双引号,我们的命令行就包含一个带有一个参数的命令。

事实上,单词分割机制把换行符看作界定符,对命令替换产生了一个虽然微妙但有趣的影响。 考虑下面的例子:

[chenfy@entrobus32 ~]$ echo $(cal)
十月 2019 日 一 二 三 四 五 六 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
[chenfy@entrobus32 ~]$ echo "$(cal)"
      十月 2019     
日 一 二 三 四 五 六
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
[chenfy@entrobus32 ~]$ 

在第一个实例中,没有引用的命令替换导致命令行包含38个参数。在第二个例子中, 命令行只有一个参数,参数中包括嵌入的空格和换行符。

单引号

如果需要禁止所有的展开,我们要使用单引号。以下例子是无引用,双引号,和单引号的比较结果:

[chenfy@entrobus32 ~]$ echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
text /home/chenfy/lazy_dog.txt /home/chenfy/ls-error.txt /home/chenfy/ls-ouput.txt /home/chenfy/ls-output.txt /home/chenfy/ls.txt /home/chenfy/people_new.txt /home/chenfy/test.txt /home/chenfy/two_words.txt /home/chenfy/word.txt a b foo 4 chenfy
[chenfy@entrobus32 ~]$ echo "text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER"
text ~/*.txt {a,b} foo 4 chenfy
[chenfy@entrobus32 ~]$ echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER'
text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
[chenfy@entrobus32 ~]$ 

正如我们所看到的,随着引用程度加强,越来越多的展开被禁止。

转义字符

有时候我们只想引用单个字符。我们可以在字符之前加上一个反斜杠,在这里叫做转义字符。 经常在双引号中使用转义字符,来有选择地阻止展开。

[chenfy@entrobus32 ~]$ echo "The balance for user $USER is: \$5.00"
The balance for user chenfy is: $5.00
[chenfy@entrobus32 ~]$ 

使用转义字符来消除文件名中一个字符的特殊含义,是很普遍的。例如,在文件名中可能使用 一些对于 shell 来说有特殊含义的字符。这些字符包括”$”, “!”, “&”等字符。在文件名 中包含特殊字符,你可以这样做:

[chenfy@entrobus32 ~]$ mv bad\&filename good_filename
mv: 无法获取"bad&filename" 的文件状态(stat): 没有那个文件或目录
[chenfy@entrobus32 ~]$ 

为了允许反斜杠字符出现,输入“\”来转义。注意在单引号中,反斜杠失去它的特殊含义,它被看作普通字符。

Backslash Escape Sequences

反斜杠转义字符序列

In addition to its role as the escape character, the backslash is also used as part of a notation to represent certain special characters called control codes. The first thirty-two characters in the ASCII coding scheme are used to transmit commands to teletype-like devices. Some of these codes are familiar (tab, backspace, linefeed, and carriage return), while others are not (null, end-of-transmission, and acknowledge).

反斜杠除了作为转义字符外,也可以构成一种表示法,来代表某种 特殊字符,这些特殊字符叫做控制码。ASCII 编码表中前32个字符被用来把命令转输到电报机 之类的设备。一些编码是众所周知的(制表符,退格符,换行符,和回车符),而其它 一些编码就不熟悉了(空值,传输结束码,和确认)。

Escape Sequence Meaning
\a Bell(“Alert”-causes the computer to beep)
\b Backspace
\n Newline. On Unix-like systems, this produces a linefeed.
\r Carriage return
\t Tab
转义序列 含义
\a 响铃(”警告”-导致计算机嘟嘟响)
\b 退格符
\n 新的一行。在类 Unix 系统中,产生换行。
\r 回车符
\t 制表符

The table above lists some of the common backslash escape sequences. The idea behind this representation using the backslash originated in the C programming language and has been adopted by many others, including the shell.

上表列出了一些常见的反斜杠转义字符序列。这种利用反斜杠的表示法背后的思想来源于 C 编程语言, 许多其它语言也采用了这种表示方法,包括 shell。

Adding the ‘-e’ option to echo will enable interpretation of escape sequences. You may also place them inside $' '. Here, using the sleep command, a simple program that just waits for the specified number of seconds and then exits, we can create a primitive countdown timer:

echo 命令带上 ‘-e’ 选项,能够解释转义序列。你可以把转义序列放在 $' ' 里面。 以下例子中,我们可以使用 sleep 命令创建一个简单的倒数计数器( sleep 是一个简单的程序, 它会等待指定的秒数,然后退出):

sleep 10; echo -e "Time's up\a"

We could also do this: 我们也可以这样做:

sleep 10; echo "Time's up" $'\a'

总结归纳

随着我们继续学习 shell,你会发现使用展开和引用的频率逐渐多起来,所以能够很好的 理解它们的工作方式很有意义。事实上,可以这样说,它们是学习 shell 的最重要的主题。 如果没有准确地理解展开模式,shell 总是神秘和混乱的源泉,并且 shell 潜在的能力也 浪费掉了。

拓展阅读(略)

第九章:键盘高级操作技巧

开玩笑地说,我经常把 Unix 描述为“这个操作系统是为喜欢敲键盘的人们服务的。” 当然,Unix 甚至还有一个命令行这件事证明了我所说的话。但是命令行用户不喜欢敲入 那么多字。要不为什么会有如此多的命令有这样简短的命令名,像cp、ls、mv和 rm?事实上 ,命令行最为珍视的目标之一就是懒惰;用最少的击键次数来完成最多的工作。另一个 目标是你的手指永远不必离开键盘,永不触摸鼠标。在这一章节,我们将看一下 bash 特性 ,这些特性使键盘使用起来更加迅速,更加高效。

以下命令将会露面:

命令行编辑

Bash 使用了一个名为 Readline 的库(共享的例程集合,可以被不同的程序使用), 来实现命令行编辑。我们已经看到一些例子。我们知道,例如,箭头按键可以移动光标, 此外还有许多特性。想想这些额外的工具,我们可以在工作中使用。学会所有的特性 并不重要,但许多特性非常有帮助。选择自己需要的特性。

注意:下面一些按键组合(尤其使用 Alt 键的组合),可能会被 GUI 拦截来触发其它的功能。 当使用虚拟控制台时,所有的按键组合都应该正确地工作。

移动光标

下表列出了移动光标所使用的按键:

表9-1: 光标移动命令

按键 行动
Ctrl-a 移动光标到行首。
Ctrl-e 移动光标到行尾。
Ctrl-f 光标前移一个字符;和右箭头作用一样。
Ctrl-b 光标后移一个字符;和左箭头作用一样。
Alt-f 光标前移一个字。
Alt-b 光标后移一个字。
Ctrl-l 清空屏幕,移动光标到左上角。clear 命令完成同样的工作。

修改文本

表9-2列出了键盘命令,这些命令用来在命令行中编辑字符。

表9-2: 文本编辑命令

按键 行动
Ctrl-d 删除光标位置的字符。
Ctrl-t 光标位置的字符和光标前面的字符互换位置。
Alt-t 光标位置的字和其前面的字互换位置。
Alt-l 把从光标位置到字尾的字符转换成小写字母。
Alt-u 把从光标位置到字尾的字符转换成大写字母。

剪切和粘贴文本

Readline 的文档使用术语 killing 和 yanking 来指我们平常所说的剪切和粘贴。 剪切下来的本文被存储在一个叫做剪切环(kill-ring)的缓冲区中。

表9-3: 剪切和粘贴命令

按键 行动
Ctrl-k 剪切从光标位置到行尾的文本。
Ctrl-u 剪切从光标位置到行首的文本。
Alt-d 剪切从光标位置到词尾的文本。
Alt-Backspace 剪切从光标位置到词头的文本。如果光标在一个单词的开头,剪切前一个单词。
Ctrl-y 把剪切环中的文本粘贴到光标位置。

The Meta Key

元键

If you venture into the Readline documentation, which can be found in the READLINE section of the bash man page, you will encounter the term “meta key.” On modern keyboards this maps to the Alt key but it wasn’t always so.

如果你冒险进入到 Readline 的文档中,你会在 bash 手册页的 READLINE 段落, 遇到一个术语”元键”(meta key)。在当今的键盘上,这个元键是指 Alt 键,但 并不总是这样。

Back in the dim times (before PCs but after Unix) not everybody had their own computer. What they might have had was a device called a terminal. A terminal was a communication device that featured a text display screen and a keyboard and just enough electronics inside to display text characters and move the cursor around. It was attached (usually by serial cable) to a larger computer or the communication network of a larger computer. There were many different brands of terminals and they all had different keyboards and display feature sets. Since they all tended to at least understand ASCII, software developers wanting portable applications wrote to the lowest common denominator. Unix systems have a very elaborate way of dealing with terminals and their different display features. Since the developers of Readline could not be sure of the presence of a dedicated extra control key, they invented one and called it “meta.” While the Alt key serves as the meta key on modern keyboards, you can also press and release the Esc key to get the same effect as holding down the Alt key if you’re still using a terminal (which you can still do in Linux!).

回到昏暗的年代(在 PC 之前 Unix 之后),并不是每个人都有他们自己的计算机。 他们可能有一个叫做终端的设备。一个终端是一种通信设备,它以一个文本显示 屏幕和一个键盘作为其特色,它里面有足够的电子器件来显示文本字符和移动光标。 它连接到(通常通过串行电缆)一个更大的计算机或者是一个大型计算机的通信 网络。有许多不同的终端产品商标,它们有着不同的键盘和特征显示集。因为它们 都倾向于至少能理解 ASCII,所以软件开发者想要符合最低标准的可移植的应用程序。 Unix 系统有一个非常精巧的方法来处理各种终端产品和它们不同的显示特征。因为 Readline 程序的开发者们,不能确定一个专用多余的控制键的存在,他们发明了一个 控制键,并把它叫做”元”(”meta”)。然而在现代的键盘上,Alt 键作为元键来服务。 如果你仍然在使用终端(在 Linux 中,你仍然可以得到一个终端),你也可以按下和 释放 Esc 键来得到如控制 Alt 键一样的效果。

自动补全

shell 能帮助你的另一种方式是通过一种叫做自动补全的机制。当你敲入一个命令时, 按下 tab 键,自动补全就会发生。让我们看一下这是怎样工作的。给出一个看起来 像这样的家目录:

[chenfy@entrobus32 ~]$ ls
:                                outputs            TestPartitioner.py
Anaconda3-5.1.0-Linux-x86_64.sh  patitioner         test.txt
file                             people_new_json    text
filesort                         people_new_text    tmp
inputs                           people_new.txt     TopN.py
lazy_dog.txt                     Pics               try
learn                            pynotes            two_words.txt
ls-error.txt                     rdd                Untitled.ipynb
ls-ouput.txt                     replaceContent.py  vim
ls-output.txt                    resources          word.txt
ls.txt                           secondarysort      writeback
ml                               streaming
[chenfy@entrobus32 ~]$

试着输入下面的命令,但不要按下 Enter 键:

[chenfy@entrobus32 ~]$  ls l

现在按下 tab 键:

[chenfy@entrobus32 ~]$  ls l
lazy_dog.txt   ls-error.txt   ls-output.txt  
learn/         ls-ouput.txt   ls.txt         
[chenfy@entrobus32 ~]$  ls l

看一下 shell 是怎样补全这一行的?让我们再试试另一个例子。这回,也 不要按下 Enter:

[me@linuxbox ~]$ ls D

按下 tab:

[me@linuxbox ~]$ ls D

没有补全,只是嘟嘟响。因为”D”不止匹配目录中的一个条目。为了自动补全执行成功, 你给它的”线索”不能模棱两可。如果我们继续输入:

[me@linuxbox ~]$ ls Do

然后按下 tab:

[me@linuxbox ~]$ ls Documents

自动补全成功了。

这个实例展示了路径名自动补全,这是最常用的形式。自动补全也能对变量(如果 字的开头是一个”$”)、用户名字(单词以”~”开始)、命令(如果单词是一行的第一个单词) 和主机名(如果单词的开头是”@”)起作用。主机名自动补全只对包含在文件/etc/hosts 中的主机名有效。

有一系列的控制和元键序列与自动补全相关联:

表9-4: 自动补全命令

按键 行动
Alt-? 显示可能的自动补全列表。在大多数系统中,你也可以完成这个通过按 两次 tab 键,这会更容易些。
Alt-* 插入所有可能的自动补全。当你想要使用多个可能的匹配项时,这个很有帮助。

注:

  1. 因版本,xsell远程等原因,以上快捷键试验后并不都有效。

Programmable Completion

可编程自动补全

Recent versions of bash have a facility called programmable completion. Programmable completion allows you (or more likely, your distribution provider) to add additional completion rules. Usually this is done to add support for specific applications. For example it is possible to add completions for the option list of a command or match particular file types that an application supports. Ubuntu has a fairly large set defined by default. Programmable completion is implemented by shell functions, a kind of mini shell script that we will cover in later chapters. If you are curious, try:

目前的 bash 版本有一个叫做可编程自动补全工具。可编程自动补全允许你(更可能是,你的 发行版提供商)来加入额外的自动补全规则。通常需要加入对特定应用程序的支持,来完成这个 任务。例如,有可能为一个命令的选项列表,或者一个应用程序支持的特殊文件类型加入自动补全。 默认情况下,Ubuntu 已经定义了一个相当大的规则集合。可编程自动补全是由 shell 函数实现的,shell 函数是一种小巧的 shell 脚本,我们会在后面的章节中讨论到。如果你感到好奇,试一下:

set | less

and see if you can find them. Not all distributions include them by default.

查看一下如果你能找到它们的话。默认情况下,并不是所有的发行版都包括它们。

利用历史命令

正如我们在第二章中讨论到的,bash 维护着一个已经执行过的命令的历史列表。这个命令列表 被保存在你家目录下,一个叫做 .bash_history 的文件里。这个 history 工具是个有用资源, 因为它可以减少你敲键盘的次数,尤其当和命令行编辑联系起来时。

搜索历史命令

在任何时候,我们都可以浏览历史列表的内容,通过:

[chenfy@entrobus32 ~]$ history | less

在默认情况下,bash 会存储你所输入的最后 500 个命令。在随后的章节里,我们会知道 怎样调整这个数值。比方说我们想在自己曾经用过的命令中,找出和/usr/bin这一目录相关的。那么我们就可以这样做:

[chenfy@entrobus32 ~]$ history | grep /usr/bin
  237  cd /usr/bin/hadoop
  239  cd /usr/bin/hadoop
  570  cd /usr/bin
  573  cd /usr/bin
  765  echo 'export PATH=/usr/bin/:$PATH' >> .bashrc
  813  ls -1 /usr/bin > ls-ouput.txt
  815  ls -l /usr/bin ls-output.txt
  816  ls -l /usr/bin > ls-output.txt
  822  ls -l /usr/bin >> ls-output.txt
  824  ls -l /usr/bin >> ls-output.txt
  836  ls -l /usr/bin | less
  837  ls /bin /usr/bin | sort | less
  838  ls /bin /usr/bin | sort | uniq | less
  839  ls /bin /usr/bin | sort | uniq -d | less
  841  ls /bin /usr/bin | sort | uniq | wc -l
  842  ls /bin /usr/bin | sort |uniq | grep zip
  844  ls /usr/bin | taail -n 5
  845  ls /usr/bin | tail -n 5
  847  ls /usr/bin | tee ls.txt | grep zip
  893  file $(ls /usr/bin/* | grep zip)
  918  history | grep /usr/bin
  920  ls /bin /usr/bin | sort | uniq | wc -l
  921  /usr/bin
  923  history | grep /usr/bin
[chenfy@entrobus32 ~]$ 

比方说在我们的搜索结果之中,我们得到一行,包含了有趣的命令,像这样;

816  ls -l /usr/bin > ls-output.txt

数字 “816” 是这个命令在历史列表中的行号。我们可以使用另一种叫做 历史命令展开的方式,来调用“816”所代表的这一行命令:

[chenfy@entrobus32 ~]$ $ !816
$ ls -l /usr/bin > ls-output.txt
-bash: $: 未找到命令
[chenfy@entrobus32 ~]$ 

bash 会把 “!816” 展开成为历史列表中816行的内容。还有其它的历史命令展开形式,我们一会儿 讨论它们。bash 也具有增量搜索历史列表的能力。意思是在字符输入的同时,bash 会去搜索历史列表(直接出结果,并高亮匹配的第一个字),每多输入一个字符都会使搜索结果更接近目标。输入 Ctrl-r来启动增量搜索, 接着输入你要寻找的字。当你找到它以后,你可以敲入 Enter 来执行命令, 或者输入 Ctrl-j,从历史列表中复制这一行到当前命令行。再次输入 Ctrl-r,来找到下一个 匹配项(历史列表中向上移动)。输入 Ctrl-g 或者 Ctrl-c,退出搜索。现在看看它的实际效果:

[chenfy@entrobus32 ~]$ $

首先输入 Ctrl-r:

(reverse-i-search)`': $

提示符改变,显示我们正在执行反向增量搜索。搜索过程是”反向的”,因为我们按照从”现在”到过去 某个时间段的顺序来搜寻。下一步,我们开始输入要查找的文本。在这个例子里是 “/usr/bin”:

(reverse-i-search)`/usr/bin': ls -l /usr/bin > ls-output.txt

上面这一行冒号后面的第一个”/”会高亮显示。

即刻,搜索返回我们需要的结果。我们可以按下 Enter 键来执行这个命令,或者我们可以按下Ctrl-j复制 这个命令到我们当前的命令行,来进一步编辑它。好了现在我们复制它,输入 Ctrl-j:

[chenfy@entrobus32 ~]$ ls -l /usr/bin > ls-output.txt

我们的 shell 提示符重新出现,命令行加载完毕,准备接受下一命令! 下表列出了一些按键组合, 这些按键可以用来操作历史列表:

表9-5: 历史命令

按键 行为
Ctrl-p 移动到上一个历史条目。类似于上箭头按键。
Ctrl-n 移动到下一个历史条目。类似于下箭头按键。
Alt-< 移动到历史列表开头。
Alt-> 移动到历史列表结尾,即当前命令行。
Ctrl-r 反向增量搜索。从当前命令行开始,向上增量搜索。
Alt-p 反向搜索,非增量搜索。(输入要查找的字符串,按下 Enter来执行搜索)。
Alt-n 向前搜索,非增量。
Ctrl-o 执行历史列表中的当前项,并移到下一个。如果你想要执行历史列表中一系列的命令,这很方便。

历史命令展开

通过使用 “!” 字符,shell 为历史列表中的命令,提供了一个特殊的展开类型。我们已经知道一个感叹号 ,其后再加上一个数字,可以把来自历史列表中的命令插入到命令行中。这里还有一些其它的展开特性:

表9-6: 历史展开命令

序列 行为
!! 重复最后一次执行的命令。可能按下上箭头按键和 enter 键更容易些。
!number 重复历史列表中第 number 行的命令。
!string 重复最近历史列表中,以这个字符串开头的命令。
!?string 重复最近历史列表中,包含这个字符串的命令。

应该小心谨慎地使用 “!string” 和 “!?string” 格式,除非你完全确信历史列表条目的内容。

在历史展开机制中,还有许多可利用的特点,但是这个题目已经太晦涩难懂了, 如果我们再继续讨论的话,我们的头可能要爆炸了。bash 手册页的 HISTORY EXPANSION 部分详尽地讲述了所有要素。

script

脚本

In addition to the command history feature in bash, most Linux distributions include a program called script that can be used to record an entire shell session and store it in a file. The basic syntax of the command is:

除了 bash 中的命令历史特性,许多 Linux 发行版包括一个叫做 script 的程序, 这个程序可以记录整个 shell 会话,并把 shell 会话存在一个文件里面。这个命令的基本语法是:

script [file]

where file is the name of the file used for storing the recording. If no file is specified, the file typescript is used. See the script man page for a complete list of the program’s options and features.

命令中的 file 是指用来存储 shell 会话记录的文件名。如果没有指定文件名,则使用文件 typescript。查看脚本的手册页,可以得到一个关于 script 程序选项和特点的完整列表。

总结归纳

在这一章中,我们已经讨论了一些由 shell 提供的键盘操作技巧,这些技巧是来帮助打字员减少工作量的。 随着时光流逝,你和命令行打交道越来越多,我猜想你会重新翻阅这一章的内容,学会更多的技巧。 目前,你就认为它们是可选的,潜在地有帮助的。

拓展阅读(略)

第十章:权限

Unix 传统中的操作系统不同于那些 MS-DOS 传统中的系统,区别在于它们不仅是多任务系统,而且也是 多用户系统。这到底意味着什么?它意味着多个用户可以在同一时间使用同一台计算机。然而一个 典型的计算机可能只有一个键盘和一个监视器,但是它仍然可以被多个用户使用。例如,如果一台 计算机连接到一个网络或者因特网,那么远程用户通过 ssh(安全 shell)可以登录并操纵这台电脑。 事实上,远程用户也能运行图形界面应用程序,并且图形化的输出结果会出现在远端的显示器上。 X 窗口系统把这个作为基本设计理念的一部分,并支持这种功能。

Linux 系统的多用户性能,不是最近的“创新”,而是一种深深地嵌入到了 Linux 操作系统的 设计中的特性。想想 Unix 系统的诞生环境,这一点就很好理解了。多年前,在个人电脑出现之前,计算机 都是大型、昂贵的、集中化的。例如一个典型的大学计算机系统,是由坐落在一座建筑中的一台 大型中央计算机和许多散布在校园各处的终端机组成,每个终端都连接到这台大型中央计算机。 这台计算机可以同时支持很多用户。

为了使多用户特性付诸实践,那么必须发明一种方法来阻止用户彼此之间受到影响。毕竟,一个 用户的行为不能导致计算机崩溃,也不能乱动属于另一个用户的文件。

拥有者、组成员和其他人

在第四章探究文件系统时,当我们试图查看一个像/etc/shadow 那样的文件的时候,我们会遇到一个问题。

[chenfy@entrobus32 ~]$ file /etc/shadow
/etc/shadow: regular file, no read permission
[chenfy@entrobus32 ~]$ 

产生这种错误信息的原因是,作为一个普通用户,我们没有权限来读取这个文件。

在 Unix 安全模型中,一个用户可能拥有文件和目录。当一个用户拥有一个文件或目录时, 用户对这个文件或目录的访问权限拥有控制权。用户反过来又属于一个由一个或多个 用户组成的用户组,用户组成员由文件和目录的所有者授予对文件和目录的访问权限。除了 对一个用户组授予权限之外,文件所有者可能会给每个人一些权限,在 Unix 术语中,每个人 是指整个世界。可以用 id 命令,来找到关于你自己身份的信息:

[chenfy@entrobus32 ~]$ id
uid=1021(chenfy) gid=1022(chenfy) 组=1022(chenfy)
[chenfy@entrobus32 ~]$ 

让我们看一下输出结果。当用户创建帐户之后,系统会给用户分配一个号码,叫做用户 ID 或者 uid,然后,为了符合人类的习惯,这个 ID 映射到一个用户名。系统又会给这个用户 分配一个原始的组 ID 或者是 gid,这个 gid 可能属于另外的组。上面的例子来自于 Fedora 系统, 比方说 Ubuntu 的输出结果可能看起来有点儿不同:

[me@linuxbox ~]$ id
uid=1000(me) gid=1000(me)
groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(v
ideo),46(plugdev),108(lpadmin),114(admin),1000(me)

正如我们能看到的,两个系统中用户的 uid 和 gid 号码是不同的。原因很简单,因为 Fedora 系统 从500开始进行普通用户帐户的编号,而 Ubuntu 从1000开始。我们也能看到 Ubuntu 的用户属于 更多的用户组。这和 Ubuntu 管理系统设备和服务权限的方式有关系。

那么这些信息来源于哪里呢?像 Linux 系统中的许多东西一样,来自一系列的文本文件。用户帐户 定义在/etc/passwd 文件里面,用户组定义在/etc/group 文件里面。当用户帐户和用户组创建以后, 这些文件随着文件/etc/shadow 的变动而修改,文件/etc/shadow 包含了关于用户密码的信息。 对于每个用户帐号,文件/etc/passwd 定义了用户(登录)名、uid、gid、帐号的真实姓名、家目录 和登录 shell。如果你查看一下文件/etc/passwd 和文件/etc/group 的内容,你会注意到除了普通 用户帐号之外,还有超级用户(uid 0)帐号,和各种各样的系统用户。

在下一章中,当我们讨论进程时,你会知道这些其他的“用户”是谁,实际上,他们相当忙碌。

然而许多像 Unix 的系统会把普通用户分配到一个公共的用户组中,例如“users”,现在的 Linux 会创建一个独一无二的,只有一个成员的用户组,这个用户组与用户同名。这样使某种类型的 权限分配更容易些。

读取、写入和执行

对于文件和目录的访问权力是根据读访问、写访问和执行访问来定义的。如果我们看一下 ls 命令的输出结果,我们能得到一些线索,这是怎样实现的:

[chenfy@entrobus32 ~]$ > foo.txt
[chenfy@entrobus32 ~]$ ls -l foo.txt
-rw-rw-r-- 1 chenfy chenfy 0 10月  1 19:22 foo.txt
[chenfy@entrobus32 ~]$ 

列表的前十个字符是文件的属性。这十个字符的第一个字符表明文件类型。下表是你可能经常看到 的文件类型(还有其它的,不常见类型):

表10-1: 文件类型

属性 文件类型
- 一个普通文件
d 一个目录
l 一个符号链接。注意对于符号链接文件,剩余的文件属性总是"rwxrwxrwx",而且都是 虚拟值。真正的文件属性是指符号链接所指向的文件的属性。
c 一个字符设备文件。这种文件类型是指按照字节流来处理数据的设备。 比如说终端机或者调制解调器
b 一个块设备文件。这种文件类型是指按照数据块来处理数据的设备,例如一个硬盘或者 CD-ROM 盘。

剩下的九个字符叫做文件模式,代表着文件所有者、文件组所有者和其他人的读、写和执行权限。

Owner Group World
rwx rwx rwx

当设置文件模式后,r、w和x 模式属性对文件和目录会产生以下影响:

表 10-2: 权限属性

属性 文件 目录
r 允许打开并读取文件内容。 允许列出目录中的内容,前提是目录必须设置了可执行属性(x)。
w 允许写入文件内容或截断文件。但是不允许对文件进行重命名或删除,重命名或删除是由目录的属性决定的。 允许在目录下新建、删除或重命名文件,前提是目录必须设置了可执行属性(x)。
x 允许将文件作为程序来执行,使用脚本语言编写的程序必须设置为可读才能被执行。 允许进入目录,例如:cd directory 。

下面是权限属性的一些例子:

表 10-3: 权限属性示例

文件属性 含义
-rwx------ 一个普通文件,对文件所有者来说可读、可写、可执行。其他人无法访问。
-rw------- 一个普通文件,对文件所有者来说可读可写。其他人无法访问。
-rw-r--r-- 一个普通文件,对文件所有者来说可读可写,文件所有者的组成员可以读该文件,其他所有人都可以读该文件。
-rwxr-xr-x 一个普通文件,对文件所有者来说可读、可写、可执行。也可以被其他的所有人读取和执行。
-rw-rw---- 一个普通文件,对文件所有者以及文件所有者的组成员来说可读可写。
lrwxrwxrwx 一个符号链接,符号链接的权限都是虚拟的,真实的权限应该以符号链接指向的文件为准。
drwxrwx--- 一个目录,文件所有者以及文件所有者的组成员可以访问该目录,并且可以在该目录下新建、重命名、删除文件。
drwxr-x--- 一个目录,文件所有者可以访问该目录,并且可以在该目录下新建、重命名、删除文件,文件所有者的组成员可以访问该目录,但是不能新建、重命名、删除文件。

chmod - 更改文件模式

更改文件或目录的模式(权限),可以利用 chmod 命令。注意只有文件的所有者或者超级用户才 能更改文件或目录的模式。chmod 命令支持两种不同的方法来改变文件模式:八进制数字表示法或 符号表示法。首先我们讨论一下八进制数字表示法。

What The Heck Is Octal?

究竟什么是八进制?

Octal (base 8), and its cousin, hexadecimal (base 16) are number systems often used to express numbers on computers. We humans, owing to the fact that we (or at least most of us) were born with ten fingers, count using a base 10 number system. Computers, on the other hand, were born with only one finger and thus do all their counting in binary (base 2). Their number system only has two numerals, zero and one. So in binary, counting looks like this:

八进制(以8为基数)及其亲戚十六进制(以16为基数)都是数字系统,通常 被用来表示计算机中的数字。我们人类,因为(或者至少大多数人)天生有 十个手指的事实,利用以10为基数的数字系统来计数。计算机,从另一方面讲,生来只有一个 手指,因此它以二进制(以2为基数)来计数。它们的数字系统只有两个数值,0和1。 因此在二进制中,计数看起来像这样:

0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001, 1010, 1011…

In octal, counting is done with the numerals zero through seven, like so:

在八进制中,逢八进一,用数字0到7来计数,像这样:

0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 20, 21…

Hexadecimal counting uses the numerals zero through nine plus the letters “A” through “F”:

十六进制中,使用数字0到9,加上大写字母”A”到”F”来计数,逢16进一:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13…

While we can see the sense in binary (since computers only have one finger), what are octal and hexadecimal good for? The answer has to do with human convenience. Many times, small portions of data are represented on computers as bit patterns. Take for example an RGB color. On most computer displays, each pixel is composed of three color components: eight bits of red, eight bits of green, and eight bits of blue. A lovely medium blue would be a twenty-four digit number:

虽然我们能知道二进制的意义(因为计算机只有一个手指),但是八进制和十六进制对什么 有好处呢? 答案是为了人类的便利。许多时候,在计算机中,一小部分数据以二进制的形式表示。 以 RGB 颜色为例来说明。大多数的计算机显示器,每个像素由三种颜色组成:8位红色,8位绿色, 8位蓝色。这样,一种可爱的中蓝色就由24位数字来表示:

010000110110111111001101

How would you like to read and write those kinds of numbers all day? I didn’t think so. Here’s where another number system would help. Each digit in a hexadecimal number represents four digits in binary. In octal, each digit represents three binary digits. So our twenty-four digit medium blue could be condensed down to a six digit hexadecimal number:

我不认为你每天都喜欢读写这类数字。另一种数字系统对我们更有帮助。每个十六进制 数字代表四个二进制。在八进制中,每个数字代表三个二进制数字。那么代表中蓝色的24位 二进制能够压缩成6位十六进制数:

436FCD

Since the digits in the hexadecimal number “line up” with the bits in the binary number we can see that the red component of our color is “43”, the green “6F”, and the blue “CD”.

因为十六进制中的两个数字对应二进制的8位数字,我们可以看到”43“代表红色,“6F” 代表绿色,“CD”代表蓝色。

These days, hexadecimal notation (often spoken as “hex”) is more common than octal, but as we shall soon see, octal’s ability to express three bits of binary will be very useful…

现在,十六进制表示法(经常叫做“hex”)比八进制更普遍,但是我们很快会看到,用八进制 来表示3个二进制数非常有用处…

通过八进制表示法,我们使用八进制数字来设置所期望的权限模式。因为每个八进制数字代表了 3个二进制数字,这种对应关系,正好映射到用来存储文件模式所使用的方案上。下表展示了 我们所要表达的意思:

Octal Binary File Mode
0 000 ---
1 001 --x
2 010 -w-
3 011 -wx
4 100 r--
5 101 r-x
6 110 rw-
7 111 rwx

通过使用3个八进制数字,我们能够设置文件所有者、用户组和其他人的权限:

[chenfy@entrobus32 ~]$ > foo.txt
[chenfy@entrobus32 ~]$ ls -l foo.txt
-rw-rw-r-- 1 chenfy chenfy 0 10月  1 19:22 foo.txt
[chenfy@entrobus32 ~]$ chmod 600 foo.txt
[chenfy@entrobus32 ~]$ ls -l foo.txt
-rw------- 1 chenfy chenfy 0 10月  1 19:22 foo.txt
[chenfy@entrobus32 ~]$ 

通过传递参数 “600”,我们能够设置文件所有者的权限为读写权限,而删除用户组和其他人的所有 权限。虽然八进制到二进制的映射看起来不方便,但通常只会用到一些常见的映射关系: 7 (rwx),6 (rw-),5 (r-x),4 (r--),和 0 (---)。

chmod 命令支持一种符号表示法,来指定��件模式。符号表示法分为三部分:更改会影响谁, 要执行哪个操作,要设置哪种权限。通过字符 “u”、“g”、“o”和 “a” 的组合来指定 要影响的对象,如下所示:

表10-4: chmod 命令符号表示法

命令符号 含义
u "user"的简写,意思是文件或目录的所有者。
g 用户组。
o "others"的简写,意思是其他所有的人。
a "all"的简写,是"u", "g"和“o”三者的联合。

如果没有指定字符,则假定使用”all”。执行的操作可能是一个“+”字符,表示加上一个权限, 一个“-”,表示删掉一个权限,或者是一个“=”,表示只有指定的权限可用,其它所有的权限被删除。

权限由 “r”、“w”和 “x” 来指定。这里是一些符号表示法的实例:

表10-5: chmod 符号表示法实例

命令符号 含义
u+x 为文件所有者添加可执行权限。
u-x 删除文件所有者的可执行权限。
+x 为文件所有者,用户组,和其他所有人添加可执行权限。 等价于 a+x。
o-rw 除了文件所有者和用户组,删除其他人的读权限和写权限。
go=rw 给文件所属的组和文件所属者/组以外的人读写权限。如果文件所属组或其他人已经拥有执行的权限,执行权限将被移除。
u+x,go=rw 给文件拥有者执行权限并给组和其他人读和执行的权限。多种设定可以用逗号分开。

一些人喜欢使用八进制表示法,而另一些人则非常喜欢符号表示法。符号表示法的优点是, 允许你设置文件模式的某个属性,而不影响其他的属性。

看一下 chmod 命令的手册页,可以得到更详尽的信息和 chmod 命令的各个选项。要注意”--recursive”选项: 它可以同时作用于文件和目录,所以它并不是如我们期望的那么有用处,因为我们很少希望文件和 目录拥有同样的权限。

借助 GUI 来设置文件模式

现在我们已经知道了怎样设置文件和目录的权限,这样我们就可以更好的理解 GUI 中的设置 权限对话框。在 Nautilus (GNOME)和 Konqueror (KDE)中,右击一个文件或目录图标将会弹出一个属性对话框。下面这个例子来自 KDE 3.5:

从这个对话框中,我们看到可以设置文件所有者、用户组和其他人的访问权限。 在 KDE 中,右击”Advanced Permissions”按钮,会打开另一个对话框,这个对话框允许 你单独设置各个模式属性。这也可以通过命令行来理解!

umask - 设置默认权限

当创建一个文件时,umask 命令控制着文件的默认权限。umask 命令使用八进制表示法来表达 从文件模式属性中删除一个位掩码。大家看下面的例子:

[chenfy@entrobus32 ~]$ rm -f foo.txt
[chenfy@entrobus32 ~]$ umask
0002
[chenfy@entrobus32 ~]$ > foo.txt
[chenfy@entrobus32 ~]$ ls -l foo.txt
-rw-rw-r-- 1 chenfy chenfy 0 10月  1 20:41 foo.txt
[chenfy@entrobus32 ~]$ 

首先,删除文件 foo.txt,以此确定我们重新开始。下一步,运行不带参数的 umask 命令, 看一下当前的掩码值。响应的数值是0002(0022是另一个常用值),这个数值是掩码的八进制 表示形式。下一步,我们创建文件 foo.txt,并且保留它的权限。

我们可以看到文件所有者和用户组都得到读权限和写权限,而其他人只是得到读权限。 其他人没有得到写权限的原因是由掩码值决定的。重复我们的实验,这次自己设置掩码值:

[chenfy@entrobus32 ~]$ rm foo.txt
[chenfy@entrobus32 ~]$ umask 0000
[chenfy@entrobus32 ~]$ > foo.txt
[chenfy@entrobus32 ~]$ ls -l foo.txt
-rw-rw-rw- 1 chenfy chenfy 0 10月  1 20:43 foo.txt
[chenfy@entrobus32 ~]$ 

当掩码设置为0000(实质上是关掉它)之后,我们看到其他人能够读写文件。为了弄明白这是 怎么回事,我们需要看一下掩码的八进制形式。把掩码展开成二进制形式,然后与文件属性 相比较,看看有什么区别:

Original file mode Mask Result
--- rw- rw- rw- 000 000 000 010 --- rw- rw- r--

此刻先忽略掉开头的三个零(我们一会儿再讨论),注意掩码中若出现一个数字1,则 删除文件模式中和这个1在相同位置的属性,在这是指其他人的写权限。这就是掩码要完成的 任务。掩码的二进制形式中,出现数字1的位置,相应地关掉一个文件模式属性。看一下 掩码0022的作用:

Original file mode Mask Result
--- rw- rw- rw- 000 000 010 010 --- rw- r-- r--

又一次,二进制中数字1出现的位置,相对应的属性被删除。再试一下其它的掩码值(一些带数字7的) ,习惯于掩码的工作原理。当你实验完成之后,要记得清理现场:

[chenfy@entrobus32 ~]$ rm foo.txt; umask 0002
[chenfy@entrobus32 ~]$

大多数情况下,你不必修改掩码值,系统提供的默认掩码值就很好了。然而,在一些高 安全级别下,你要能控制掩码值。

Some Special Permissions

一些特殊权限

Though we usually see an octal permission mask expressed as a three digit number, it is more technically correct to express it in four digits. Why? Because, in addition to read, write, and execute permission, there are some other, less used, permission settings.

虽然我们通常看到一个八进制的权限掩码用三位数字来表示,但是从技术层面上来讲, 用四位数字来表示它更确切些。为什么呢?因为除了读取、写入和执行权限之外,还有 其它较少用到的权限设置。

The first of these is the setuid bit (octal 4000). When applied to an executable file, it sets the effective user ID from that of real user (the user actually running the program) to that of the program’s owner. Most often this is given to a few programs owned by the superuser. When an ordinary user runs a program that is “setuid root” , the program runs with the effective privileges of the superuser. This allows the program to access files and directories that an ordinary user would normally be prohibited from accessing. Clearly, because this raises security concerns, number of setuid programs must be held to an absolute minimum.

其中之一是 setuid 位(八进制4000)。当应用到一个可执行文件时,它把有效用户 ID 从真正的用户(实际运行程序的用户)设置成程序所有者的 ID。这种操作通常会应用到 一些由超级用户所拥有的程序。当一个普通用户运行一个程序,这个程序由根用户(root) 所有,并且设置了 setuid 位,这个程序运行时具有超级用户的特权,这样程序就可以 访问普通用户禁止访问的文件和目录。很明显,因为这会引起安全方面的问题,所有可以 设置 setuid 位的程序个数,必须控制在绝对小的范围内。

The second is the setgid bit (octal 2000) which, like the setuid bit, changes the effective group ID from the real group ID of the user to that of the file owner. If the setgid bit is set on a directory, newly created files in the directory will be given the group ownership of the directory rather the group ownership of the file’s creator. This is useful in a shared directory when members of a common group need access to all the files in the directory, regardless of the file owner’s primary group.

第二个是 setgid 位(八进制2000),这个相似于 setuid 位,把有效用户组 ID 从真正的 用户组 ID 更改为文件所有者的组 ID。如果设置了一个目录的 setgid 位,则目录中新创建的文件 具有这个目录用户组的所有权,而不是文件创建者所属用户组的所有权。对于共享目录来说, 当一个普通用户组中的成员,需要访问共享目录中的所有文件,而不管文件所有者的主用户组时, 那么设置 setgid 位很有用处。

The third is called the sticky bit (octal 1000). This is a holdover from ancient Unix, where it was possible to mark an executable file as “not swappable.” On files, Linux ignores the sticky bit, but if applied to a directory, it prevents users from deleting or renaming files unless the user is either the owner of the directory, the owner of the file, or the superuser. This is often used to control access to a shared directory, such as /tmp.

第三个是 sticky 位(八进制1000)。这个继承于 Unix,在 Unix 中,它可能把一个可执行文件 标志为“不可交换的”。在 Linux 中,会忽略文件的 sticky 位,但是如果一个目录设置了 sticky 位, 那么它能阻止用户删除或重命名文件,除非用户是这个目录的所有者,或者是文件所有者,或是 超级用户。这个经常用来控制访问共享目录,比方说/tmp。

Here are some examples of using chmod with symbolic notation to set these special permissions. First assigning setuid to a program:

这里有一些例子,使用 chmod 命令和符号表示法,来设置这些特殊的权限。首先, 授予一个程序 setuid 权限。

chmod u+s program

Next, assigning setgid to a directory:

下一步,授予一个目录 setgid 权限:

chmod g+s dir

Finally, assigning the sticky bit to a directory:

最后,授予一个目录 sticky 权限:

chmod +t dir

When viewing the output from ls, you can determine the special permissions. Here are some examples. First, a program that is setuid:

当浏览 ls 命令的输出结果时,你可以确认这些特殊权限。这里有一些例子。首先,一个程序被设置为setuid属性:

-rwsr-xr-x

A directory that has the setgid attribute:

具有 setgid 属性的目录:

drwxrwsr-x

A directory with the sticky bit set:

设置了 sticky 位的目录:

drwxrwxrwt

更改身份

在不同的时候,我们会发现很有必要具有另一个用户的身份。经常地,我们想要得到超级 用户特权,来执行一些管理任务,但是也有可能”变为”另一个普通用户,比如说测试一个帐号。 有三种方式,可以拥有多重身份:

  1. 注销系统并以其他用户身份重新登录系统。
  2. 使用 su 命令。
  3. 使用 sudo 命令。

我们将跳过第一种方法,因为我们知道怎样使用它,并且它缺乏其它两种方法的方便性。 在我们自己的 shell 会话中,su 命令允许你假定为另一个用户的身份,以这个用户的 ID 启动一个新的 shell 会话,或者是以这个用户的身份来发布一个命令。sudo 命令允许一个管理员 设置一个叫做/etc/sudoers 的配置文件,并且定义了一些具体命令,在假定的身份下,特殊用户 可以执行这些命令。选择使用哪个命令,很大程度上是由你使用的 Linux 发行版来决定的。 你的发行版可能这两个命令都包含,但系统配置可能会偏袒其中之一。我们先介绍 su 命令。

su - 以其他用户身份和组 ID 运行一个 shell

su 命令用来以另一个用户的身份来启动 shell。这个命令语法看起来像这样:

su [-[l]] [user]

如果包含”-l”选项,那么会为指定用户启动一个需要登录的 shell。这意味着会加载此用户的 shell 环境, 并且工作目录会更改到这个用户的家目录。这通常是我们所需要的。如果不指定用户,那么就假定是 超级用户。注意(不可思议地),选项”-l”可以缩写为“-”,这是经常用到的形式。启动超级用户的 shell, 我们可以这样做:

[chenfy@entrobus32 ~]$ su -
密码:
qqqqqsu: 鉴定故障
[chenfy@entrobus32 ~]$ 

按下回车符之后,shell 提示我们输入超级用户的密码。如果密码输入正确,出现一个新的 shell 提示符, 这表明这个 shell 具有超级用户特权(提示符的末尾字符是”#”而不是”$”),并且当前工作目录是超级用户的家目录 (通常是/root)。一旦进入一个新的 shell,我们能执行超级用户所使用的命令。当工作完成后, 输入”exit”,则返回到原来的 shell:

[root@linuxbox ~]# exit
[me@linuxbox ~]$

以这样的方式使用 su 命令,也可以只执行单个命令,而不是启动一个新的可交互的 shell:

su -c 'command'

使用这种模式,命令传递到一个新 shell 中执行。把命令用单引号引起来很重要,因为我们不想 命令在我们的 shell 中展开,但需要在新 shell 中展开。

[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 ~]$

sudo - 以另一个用户身份执行命令

sudo 命令在很多方面都相似于 su 命令,但是 sudo 还有一些非常重要的功能。管理员能够配置 sudo 命令,从而允许一个普通用户以不同的身份(通常是超级用户),通过一种非常可控的方式 来执行命令。尤其是,只有一个用户可以执行一个或多个特殊命令时,(更体现了 sudo 命令的方便性)。 另一个重要差异是 sudo 命令不要求超级用户的密码。使用 sudo 命令时,用户使用他/她自己的密码 来认证。比如说,例如,sudo 命令经过配置,允许我们运行一个虚构的备份程序,叫做”backup_script”, 这个程序要求超级用户权限。通过 sudo 命令,这个程序会像这样运行:

[chenfy@entrobus32 ~]$ sudo backup_script
[sudo] chenfy 的密码:
chenfy 不在 sudoers 文件中。此事将被报告。
[chenfy@entrobus32 ~]$ 

按下回车键之后,shell 提示我们输入我们的密码(不是超级用户的)。一旦认证完成,则执行 具体的命令。su 和 sudo 之间的一个重要区别是 sudo 不会重新启动一个 shell,也不会加载另一个 用户的 shell 运行环境。这意味者命令不必用单引号引起来。注意通过指定各种各样的选项,这 种行为可以被推翻。详细信息,阅读 sudo 手册页。

想知道 sudo 命令可以授予哪些权限,使用”-l”选项,列出所有权限:

[chenfy@entrobus32 ~]$ sudo -l
[sudo] chenfy 的密码:
对不起,用户 chenfy 不能在 entrobus32 上运行 sudo。
[chenfy@entrobus32 ~]$ 

Ubuntu And sudo

Ubuntu 与 sudo

One of the recurrent problems for regular users is how to perform certain tasks that require superuser privileges. These tasks include installing and updating software, editing system configuration files, and accessing devices. In the Windows world, this is often done by giving users administrative privileges. This allows users to perform these tasks. However, it also enables programs executed by the user to have the same abilities. This is desirable in most cases, but it also permits malware (malicious software) such as viruses to have free reign of the computer.

普通用户经常会遇到这样的问题,怎样完成某些需要超级用户权限的任务。这些任务 包括安装和更新软件,编辑系统配置文件,和访问设备。在 Windows 世界里,这些任务是 通过授予用户管理员权限来完成的。这允许用户执行这些任务。然而,这也会导致用户所 执行的程序拥有同样的能力。在大多数情况下,这是我们所期望的,但是它也允许 malware (恶意软件),比方说电脑病毒,自由地支配计算机。

In the Unix world, there has always been a larger division between regular users and administrators, owing to the multi-user heritage of Unix. The approach taken in Unix is to grant superuser privileges only when needed. To do this, the su and sudo commands are commonly used.

在 Unix 世界中,由于 Unix 是多用户系统,所以在普通用户和管理员之间总是存在很大的 差别。Unix 采取的方法是只有在需要的时候,才授予普通用户超级用户权限。这样,普遍会 用到 su 和 sudo 命令。

Up until a couple of years ago, most Linux distributions relied on su for this purpose. su didn’t require the configuration that sudo required, and having a root account is traditional in Unix. This introduced a problem. Users were tempted to operate as root unnecessarily. In fact, some users operated their systems as the root user exclusively, since it does away with all those annoying “permission denied” messages. This is how you reduce the security of a Linux system to that of a Windows system. Not a good idea.

几年前,大多数的 Linux 发行版都依赖于 su 命令,来达到目的。su 命令不需要 sudo 命令 所要求的配置,su 命令拥有一个 root 帐号,是 Unix 中的传统。但这会引起问题。所有用户 会企图以 root 用户帐号来操纵系统。事实上,一些用户专门以 root 用户帐号来操作系统, 因为这样做,的确消除了所有那些讨厌的“权限被拒绝”的消息。你这样做就会使得 Linux 系统的 安全性能被降低到和 Windows 系统相同的级别。不是一个好主意。

When Ubuntu was introduced, its creators took a different tack. By default, Ubuntu disables logins to the root account (by failing to set a password for the account), and instead uses sudo to grant superuser privileges. The initial user account is granted full access to superuser privileges via sudo and may grant similar powers to subsequent user accounts.

当引进 Ubuntu 的时候,它的创作者们采取了不同的策略。默认情况下,Ubuntu 不允许用户登录 到 root 帐号(因为没有root帐号的密码),而是使用 sudo 命令授予普通用户超级用户权限。 通过 sudo 命令,新建用户可以拥有超级用户权限,也可以授予随后的用户帐号相似的权力。

chown - 更改文件所有者和用户组

chown 命令被用来更改文件或目录的所有者和用户组。使用这个命令需要超级用户权限。chown 命令 的语法看起来像这样:

chown [owner] [:[group]] file...

chown 可以根据这个命令的第一个参数更改文件所有者和/或文件用户组。这里有 一些例子:

表10-6: chown 参数实例

参数 结果
bob 把文件所有者从当前属主更改为用户 bob。
bob:users 把文件所有者改为用户 bob,文件用户组改为用户组 users。
:admins 把文件用户组改为组 admins,文件所有者不变。
bob: 文件所有者改为用户 bob,文件用户组改为用户 bob 登录系统时所属的用户组。

比方说,我们有两个用户,janet拥有超级用户访问权限,而 tony 没有。用户 janet 想要从 她的家目录复制一个文件到用户 tony 的家目录。因为用户 janet 想要 tony 能够编辑这个文件, janet 把这个文件的所有者更改为 tony:

[janet@linuxbox ~]$ sudo cp myfile.txt ~tony
Password:
[janet@linuxbox ~]$ sudo ls -l ~tony/myfile.txt
-rw-r--r-- 1 root  root 8031 2008-03-20 14:30 /home/tony/myfile.txt
[janet@linuxbox ~]$ sudo chown tony: ~tony/myfile.txt
[janet@linuxbox ~]$ sudo ls -l ~tony/myfile.txt
-rw-r--r-- 1 tony  tony 8031 2008-03-20 14:30 /home/tony/myfile.txt

这里,我们看到用户 janet 把文件从她的目录复制到 tony 的家目录。下一步,janet 把文件所有者 从 root(使用 sudo 命令的原因)改到 tony。通过在第一个参数中使用末尾的”:”字符,janet 同时把 文件用户组改为 tony 登录系统时,所属的用户组,碰巧是用户组 tony。

注意,第一次使用 sudo 命令之后,为什么(shell)没有提示 janet 输入她的密码?这是因为,在 大多数的配置中,sudo 命令会相信你几分钟,直到计时结束。

chgrp - 更改用户组所有权

在旧版 Unix 系统中,chown 命令只能更改文件所有权,而不是用户组所有权。为了达到目的, 使用一个独立的命令,chgrp 来完成。除了限制多一点之外,chgrp 命令与 chown 命令使用起来很相似。

练习使用权限

到目前为止,我们已经知道了权限这类东西是怎样工作的,现在是时候炫耀一下了。我们 将展示一个常见问题的解决方案,这个问题是如何设置一个共享目录。假想我们有两个用户, 他们分别是 “bill” 和 “karen”。他们都有音乐 CD 收藏品,也愿意设置一个共享目录,在这个 共享目录中,他们分别以 Ogg Vorbis 或 MP3 的格式来存储他们的音乐文件。通过 sudo 命令, 用户 bill 具有超级用户访问权限。

我们需要做的第一件事,是创建一个以 bill 和 karen 为成员的用户组。使用图形化的用户管理工具, bill 创建了一个叫做 music 的用户组,并且把用户 bill 和 karen 添加到用户组 music 中:

下一步,bill 创建了存储音乐文件的目录:

[bill@linuxbox ~]$ sudo mkdir /usr/local/share/Music
password:

因为 bill 正在他的家目录之外操作文件,所以需要超级用户权限。这个目录创建之后,它具有 以下所有权和权限:

[bill@linuxbox ~]$ ls -ld /usr/local/share/Music
drwxr-xr-x 2 root root 4096 2008-03-21 18:05 /usr/local/share/Music

正如我们所见到的,这个目录由 root 用户拥有,并且具有权限755。为了使这个目录共享,允许(用户 karen)写入,bill 需要更改目录用户组所有权和权限:

[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 2008-03-21 18:05 /usr/local/share/Music

那么这是什么意思呢? 它的意思是,现在我们拥有一个目录,/usr/local/share/Music,这个目录由 root 用户拥有,并且 允许用户组 music 读取和写入。用户组 music 有两个成员 bill 和 karen,这样 bill 和 karen 能够在目录 /usr/local/share/Music 中创建文件。其他用户能够列出目录中的内容,但是不能在其中创建文件。

但是我们仍然会遇到问题。通过我们目前所拥有的权限,在 Music 目录中创建的文件,只具有用户 bill 和 karen 的普通权限:

[bill@linuxbox ~]$ > /usr/local/share/Music/test_file
[bill@linuxbox ~]$ ls -l /usr/local/share/Music
-rw-r--r-- 1 bill    bill    0 2008-03-24 20:03 test_file

实际上,存在两个问题。第一个,系统中默认的掩码值是0022,这会禁止用户组成员编辑属于同 组成员的文件。如果共享目录中只包含文件,这就不是个问题,但是因为这个目录将会存储音乐, 通常音乐会按照艺术家和唱片的层次结构来组织分类。所以用户组成员需要在同组其他成员创建的 目录中创建文件和目录。我们将把用户 bill 和 karen 使用的掩码值改为0002。

第二个问题是,用户组成员创建的文件和目录的用户组,将会设置为用户的主要组,而不是用户组 music。 通过设置此目录的 setgid 位来解决这个问题:

[bill@linuxbox ~]$ sudo chmod g+s /usr/local/share/Music
[bill@linuxbox ~]$ ls -ld /usr/local/share/Music
drwxrwsr-x 2 root music 4096 2008-03-24 20:03 /usr/local/share/Music

现在测试一下,看看是否新的权限解决了这个问题。bill 把他的掩码值设为0002,删除 先前的测试文件,并创建了一个新的测试文件和目录:

[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 2008-03-24 20:24 test_dir
-rw-rw-r-- 1 bill   music 0 2008-03-24 20:22 test_file
[bill@linuxbox ~]$

现在,创建的文件和目录都具有正确的权限,允许用户组 music 的所有成员在目录 Music 中创建 文件和目录。

剩下一个问题是关于 umask 命令的。umask 命令设置的掩码值只能在当前 shell 会话中生效,若当前 shell 会话结束后,则必须重新设置。在这本书的第三部分,我们将看一下,怎样使掩码值永久生效。

更改用户密码

这一章最后一个话题,我们将讨论自己帐号的密码(和其他人的密码,如果你具有超级用户权限)。 使用 passwd 命令,来设置或更改用户密码。命令语法如下所示:

passwd [user]

只要输入 passwd 命令,就能更改你的密码。shell 会提示你输入你的旧密码和你的新密码:

[me@linuxbox ~]$ passwd
(current) UNIX password:
New UNIX password:

passwd 命令将会试着强迫你使用“强”密码。这意味着它会拒绝接受太短的密码、与先前相似的密码、 字典中的单词作为密码或者是太容易猜到的密码:

[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 命令选项对超级用户有效,允许帐号锁定,密码失效,等等。 详细内容,参考 passwd 命令的手册页。

拓展阅读(略)

第十一章:进程

通常,现在的操作系统都支持多任务,意味着操作系统通过在一个执行中的程序和另一个 程序之间快速地切换造成了一种它同时能够做多件事情的假象。Linux 内核通过使用进程来 管理多任务。进程,就是Linux 组织安排正在等待使用 CPU的各种程序的方式。

有时候,计算机变得呆滞,运行缓慢,或者一个应用程序停止响应。在这一章中,我们将看一些 可用的命令行工具,这些工具帮助我们查看程序的执行状态,以及怎样终止行为不当的进程。

这一章将介绍以下命令:

进程是怎样工作的

当系统启动的时候,内核先把一些它自己的活动初始化为进程,然后运行一个叫做 init 的程序。init, 依次地,再运行一系列的称为 init 脚本的 shell 脚本(位于/etc),它们可以启动所有的系统服务。 其中许多系统服务以守护(daemon)程序的形式实现,守护程序仅在后台运行,没有任何用户接口(User Interface)。 这样,即使我们没有登录系统,至少系统也在忙于执行一些例行事务。

在进程方案中,一个程序可以发动另一个程序被表述为一个父进程可以产生一个子进程。

内核维护每个进程的信息,以此来保持事情有序。例如,系统分配给每个进程一个数字,这个数字叫做 进程(process) ID 或 PID。PID 号按升序分配,init 进程的 PID 总是1。内核也对分配给每个进程的内存和就绪状态进行跟踪以便继续执行这个进程。 像文件一样,进程也有所有者和用户 ID,有效用户 ID,等等。

查看进程

查看进程,最常使用地命令(有几个命令)是 ps(process status)。ps 程序有许多选项,它最简单地使用形式是这样的:

[chenfy@entrobus32 ~]$ ps
  PID TTY          TIME CMD
10543 pts/0    00:00:00 ps
17958 pts/0    00:00:00 bash
[chenfy@entrobus32 ~]$ 
[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”(直译电传打字机) 的简写,是指进程的控制终端。TTY足足显示了 Unix 的年代久远。TIME 字段表示 进程所消耗的 CPU 时间数量。正如我们所看到的,这两个进程使计算机工作起来很轻松。

如果给 ps 命令加上选项,我们可以得到更多关于系统运行状态的信息:

[chenfy@entrobus32 ~]$ ps x
  PID TTY      STAT   TIME COMMAND
11049 pts/0    R+     0:00 ps x
17957 ?        S      0:00 sshd: chenfy@pts/0
17958 pts/0    Ss     0:00 -bash
[chenfy@entrobus32 ~]$ 
[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 --

and many more...

加上 “x” 选项(注意没有开头的 “-“ 字符),告诉 ps 命令,展示所有进程,不管它们由什么 终端(如果有的话)控制。在 TTY 一栏中出现的 “?” ,表示没有控制终端。使用这个 “x” 选项,可以 看到我们所拥有的每个进程的信息。

因为系统中正运行着许多进程,所以 ps 命令的输出结果很长。为了方便查看,将ps的输出管道 到less中通常很有帮助。一些选项组合也会产生很长的输出结果,所以最大化 终端仿真器窗口可能也是一个好主意。

输出结果中,新添加了一栏,标题为 STAT 。STAT 是 “state” 的简写,它揭示了进程当前状态:

表11-1: 进程状态

状态 含义
R 运行中。这意味着,进程正在运行或准备运行。
S 正在睡眠。进程没有运行,而是,正在等待一个事件, 比如说,一个按键或者网络分组。
D 不可中断睡眠。进程正在等待 I/O,比方说,一个磁盘驱动器的 I/O。
T 已停止. 已经指示进程停止运行。稍后介绍更多。
Z 一个死进程或“僵尸”进程。这是一个已经终止的子进程,但是它的父进程还没有清空它。 (父进程没有把子进程从进程表中删除)
< 一个高优先级进程。这可能会授予一个进程更多重要的资源,给它更多的 CPU 时间。 进程的这种属性叫做 niceness。具有高优先级的进程据说是不好的(less nice), 因为它占用了比较多的 CPU 时间,这样就给其它进程留下很少时间。
N 低优先级进程。 一个低优先级进程(一个“nice”进程)只有当其它高优先级进程被服务了之后,才会得到处理器时间。

进程状态信息之后,可能还跟随其他的字符。这表示各种外来进程的特性。详细信息请看 ps 手册页。

另一个流行的选项组合是 “aux”(不带开头的”-“字符)。这会给我们更多信息:

[chenfy@entrobus32 ~]$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0 191056  4072 ?        Ss   8月13  16:45 /usr/l
root         2  0.0  0.0      0     0 ?        S    8月13   0:00 [kthre
root         3  0.0  0.0      0     0 ?        S    8月13   0:40 [ksoft
...
[chenfy@entrobus32 ~]$ 

这个选项组合,能够显示属于每个用户的进程信息。使用这个选项,可以唤醒 “BSD 风格” 的输出结果。 Linux 版本的 ps 命令,可以模拟几个不同 Unix 版本中的 ps 程序的行为。通过这些选项,我们得到 这些额外的列。

表11-2: BSD 风格的 ps 命令列标题

|标题|含义| |USER|用户 ID. 进程的所有者。| |%CPU|以百分比表示的 CPU 使用率| |%MEM|以百分比表示的内存使用率| |VSZ|虚拟内存大小| |RSS|进程占用的物理内存的大小,以千字节为单位。| |START|进程启动的时间。若它的值超过24小时,则用天表示。|

用 top 命令动态查看进程

虽然 ps 命令能够展示许多计算机运行状态的信息,但是它只是提供 ps 命令执行时刻的机器状态快照。 为了看到更多动态的信息,我们使用 top 命令:

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
....

其中系统概要包含许多有用信息。下表是对系统概要的说明:

表11-3: top 命令信息字段

行号 字段 意义
1 top 程序名。
14:59:20 当前时间。
up 6:30 这是正常运行时间。它是计算机从上次启动到现在所运行的时间。 在这个例子里,系统已经运行了六个半小时。
2 users 有两个用户登录系统。
load average: 加载平均值是指,等待运行的进程数目,也就是说,处于可以运行状态并共享 CPU 的进程个数。 这里展示了三个数值,每个数值对应不同的时间段。第一个是最后60秒的平均值, 下一个是前5分钟的平均值,最后一个是前15分钟的平均值。若平均值低于1.0,则指示计算机 工作不忙碌。
2 Tasks: 总结了进程数目和这些进程的各种状态。
3 Cpu(s): 这一行描述了 CPU 正在进行的活动的特性。
0.7%us 0.7% 的 CPU 被用于用户进程。这意味着进程在内核之外。
1.0%sy 1.0%的 CPU 时间被用于系统(内核)进程。
0.0%ni 0.0%的 CPU 时间被用于"nice"(低优先级)进程。
98.3%id 98.3%的 CPU 时间是空闲的。
0.0%wa 0.0%的 CPU 时间来等待 I/O。
4 Mem: 展示物理内存的使用情况。
5 Swap: 展示交换分区(虚拟内存)的使用情况。

top 程序接受一系列从键盘输入的命令。两个最有趣的命令是 h 和 q。h,显示程序的帮助屏幕,q, 退出 top 程序。

两个主要的桌面环境都提供了图形化应用程序,来显示与 top 程序相似的信息 (和 Windows 中的任务管理器差别不多),但是我觉得 top 程序要好于图形化的版本, 因为它运行速度快,并且消费很少的系统资源。毕竟,我们的系统监测程序不能成为 我们试图追踪的系统怠工的原因。

控制进程

现在我们可以看到和监测进程,让我们得到一些对它们的控制权。为了我们的实验,我们将使用 一个叫做 xlogo 的小程序,作为我们的实验品。这个 xlogo 程序是 X 窗口系统 (使图形界面显示在屏幕上的底层引擎)提供的示例程序,这个程序仅显示一个大小可调的 包含 X 标志的窗口。首先,我们需要知道测试的实验对象:

[me@linuxbox ~]$ xlogo

命令执行之后,一个包含 X 标志的小窗口应该出现在屏幕的某个位置上。在一些系统中,xlogo 命令 会打印一条警告信息,但是不用理会它。

小贴士:如果你的系统不包含 xlogo 程序,试着用 gedit 或者 kwrite 来代替。

通过调整它的窗口大小,我们能够证明 xlogo 程序正在运行。如果这个标志以新的尺寸被重画, 则这个程序正在运行。

注意,为什么我们的 shell 提示符还没有返回?这是因为 shell 正在等待这个程序结束, 就像到目前为止我们用过的其它所有程序一样。如果我们关闭 xlogo 窗口,shell 提示符就返回了。

中断一个进程

我们再运行 xlogo 程序一次,观察一下发生了什么事。首先,执行 xlogo 命令,并且 证实这个程序正在运行。下一步,回到终端窗口,按下 Ctrl-c。

[me@linuxbox ~]$ xlogo
[me@linuxbox ~]$

在一个终端中,输入 Ctrl-c,中断一个程序。这意味着,我们礼貌地要求终止这个程序。 输入 Ctrl-c 之后,xlogo 窗口关闭,shell 提示符返回。

通过这个技巧,许多(但不是全部)命令行程序可以被中断。

把一个进程放置到后台(执行)

假如说我们想让 shell 提示符返回,却不终止 xlogo 程序。我们可以把 这个程序放到后台(background)执行。把终端想象是一个有前台(包含在表层可见的事物,像 shell 提示符) 和后台(包含表层之下的隐藏的事物)(的设备)。为了启动一个程序并让它立即在后台 运行,我们在程序命令之后,加上”&”字符:

[me@linuxbox ~]$ xlogo &
[1] 28236
[me@linuxbox ~]$

执行命令之后,这个 xlogo 窗口出现,并且 shell 提示符返回,同时打印一些有趣的数字。 这条信息是 shell 特性的一部分,叫做任务控制 (job control)。通过这条信息,shell 告诉我们,已经启动了 任务号(job number)为1(“[1]”),PID 为28236的程序。如果我们运行 ps 命令,可以看到我们的进程:

[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 的任务控制功能给出了一种列出从我们终端中启动了的任务的方法。执行 jobs 命令,我们可以看到这个输出列表:

[me@linuxbox ~]$ jobs
[1]+ Running            xlogo &

进程返回到前台

一个在后台运行的进程对一切来自键盘的输入都免疫,也不能用 Ctrl-c 来中断它。 为了让一个进程返回前台 (foreground),这样使用 fg 命令:

[me@linuxbox ~]$ jobs
[1]+ Running        xlogo &
[me@linuxbox ~]$ fg %1
xlogo

fg 命令之后,跟随着一个百分号和任务序号(叫做 jobspec,如此处的%1)就可以了。如果我们只有一个后台任务,那么 jobspec(job specification) 是可有可无的。输入 Ctrl-c 来终止 xlogo 程序。

停止一个进程

有时候,我们想要停止一个进程,而不是终止它。我们这么做通常是为了允许前台进程被移动到后台。 输入 Ctrl-z,可以停止一个前台进程。让我们试一下。在命令提示符下,执行 xlogo 命令, 然后输入 Ctrl-z:

[me@linuxbox ~]$ xlogo
[1]+ Stopped                 xlogo
[me@linuxbox ~]$

停止 xlogo 程序之后,通过调整 xlogo 的窗口大小,我们可以证实这个程序已经停止了。 它看起来像死掉了一样。使用 fg 命令,可以恢复程序到前台运行,或者用 bg 命令把程序移到后台。

[me@linuxbox ~]$ bg %1
[1]+ xlogo &
[me@linuxbox ~]$

和 fg 命令一样,如果只有一个任务的话,jobspec 参数是可选的。

如果我们从命令行启动一个图形程序,但是忘了在命令后加字符 “&”, 将一个进程从前台移动到后台也是很方便的。

为什么要从命令行启动一个图形界面程序呢?有两个原因。第一个,你想要启动的程序,可能 没有在窗口管理器的菜单中列出来(比方说 xlogo)。第二个,从命令行启动一个程序, 你能够看到一些错误信息,如果从图形界面中运行程序的话,这些信息是不可见的。有时候, 一个程序不能从图形界面菜单中启动。通过从命令行中启动它,我们可能会看到 能揭示问题的错误信息。一些图形界面程序还有许多有意思并且有用的命令行选项。

Signals

kill 命令被用来“杀死”程序。这样我们就可以终止需要杀死的程序。这里有一个例子:

[me@linuxbox ~]$ xlogo &
[1] 28401
[me@linuxbox ~]$ kill 28401
[1]+ Terminated               xlogo

首先,我们在后台启动 xlogo 程序。shell 打印出这个后台进程的 jobspec 和 PID。下一步,我们使用 kill 命令,并且指定我们想要终止的进程 PID。也可以用 jobspec(例如,“%1”)来代替 PID。

虽然这个命令看上去很直白, 但是它的含义不止于此。这个 kill 命令不是真的“杀死”程序,而是给程序 发送信号。信号是操作系统与程序之间进行通信时所采用的几种方式中的一种。 在使用 Ctrl-c 和 Ctrl-z 的过程中我们已经看到信号的实际用法。当终端接受了其中一个按键组合后,它会给在前端运行 的程序发送一个信号。在使用 Ctrl-c 的情况下,会发送一个叫做 INT(Interrupt,中断)的信号;当使用 Ctrl-z 时,则发送一个叫做 TSTP(Terminal Stop,终端停止)的信号。程序,相应地,监听信号的到来,当程序 接到信号之后,则做出响应。一个程序能够监听和响应信号这件事允许一个程序做些事情, 比如,当程序接到一个终止信号时,它可以保存所做的工作。

通过 kill 命令给进程发送信号

kill 命令被用来给程序发送信号。它最常见的语法形式看起来像这样:

kill [-signal] PID...

如果在命令行中没有指定信号,那么默认情况下,发送 TERM(Terminate,终止)信号。kill 命令被经常 用来发送以下命令:

表 11-4: 常用信号

编号 名字 含义
1 HUP 挂起(Hangup)。这是美好往昔的残留部分,那时候终端机通过电话线和调制解调器连接到 远端的计算机。这个信号被用来告诉程序,控制的终端机已经“挂断”。 通过关闭一个终端会话,可以展示这个信号的作用。在当前终端运行的前台程序将会收到这个信号并终止。
许多守护进程也使用这个信号,来重新初始化。这意味着,当一个守护进程收到这个信号后, 这个进程会重新启动,并且重新读取它的配置文件。Apache 网络服务器守护进程就是一个例子。
2 INT 中断。实现和 Ctrl-c 一样的功能,由终端发送。通常,它会终止一个程序。
9 KILL 杀死。这个信号很特别。尽管程序可能会选择不同的方式来处理发送给它的 信号,其中也包含忽略信号,但是 KILL 信号从不被发送到目标程序。而是内核立即终止 这个进程。当一个进程以这种方式终止的时候,它没有机会去做些“清理”工作,或者是保存工作。 因为这个原因,把 KILL 信号看作最后一招,当其它终止信号失败后,再使用它。
15 TERM 终止。这是 kill 命令发送的默认信号。如果程序仍然“活着”,可以接受信号,那么 这个它会终止。
18 CONT 继续。在一个停止信号后,这个信号会恢复进程的运行。
19 STOP 停止。这个信号导致进程停止运行,而不是终止。像 KILL 信号,它不被 发送到目标进程,因此它不能被忽略。

让我们试一下 kill 命令:

[me@linuxbox ~]$ xlogo &
[1] 13546
[me@linuxbox ~]$ kill -1 13546
[1]+ Hangup         xlogo

在这个例子里,我们在后台启动 xlogo 程序,然后通过 kill 命令,发送给它一个 HUP 信号。 这个 xlogo 程序终止运行,并且 shell 指示这个后台进程已经接受了一个挂起信号。在看到这条 信息之前,你可能需要多按几次 enter 键。注意,信号既可以用号码,也可以用名字来指定, 包括在前面加上字母 “SIG” 的名字。

[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

重复上面的例子,试着使用其它的信号。记住,你也可以用 jobspecs 来代替 PID。

进程,和文件一样,拥有所有者,所以为了能够通过 kill 命令来给进程发送信号, 你必须是进程的所有者(或者是超级用户)。

除了上表列出的 kill 命令最常使用的信号之外,还有一些系统频繁使用的信号。以下是其它一些常用 信号列表:

表 11-5: 其它常用信号

编号 名字 含义
3 QUIT 退出
11 SEGV 段错误(Segmentation Violation)。如果一个程序非法使用内存,就会发送这个信号。也就是说, 程序试图写入内存,而这个内存空间是不允许此程序写入的。
20 TSTP 终端停止(Terminal Stop)。当按下 Ctrl-z 组合键后,终端发送这个信号。不像 STOP 信号, TSTP 信号由目标进程接收,且可能被忽略。
28 WINCH 改变窗口大小(Window Change)。当改变窗口大小时,系统会发送这个信号。 一些程序,像 top 和 less 程序会响应这个信号,按照新窗口的尺寸,刷新显示的内容。

为了满足读者的好奇心,通过下面的命令可以得到一个完整的信号列表:

[chenfy@entrobus32 ~]$ kill -l

通过 killall 命令给多个进程发送信号

也有可能通过 killall 命令,给匹配特定程序或用户名的多个进程发送信号。下面是 killall 命令的语法形式:

killall [-u user] [-signal] name...

为了说明情况,我们将启动一对 xlogo 程序的实例,然后再终止它们:

[me@linuxbox ~]$ xlogo &
[1] 18801
[me@linuxbox ~]$ xlogo &
[2] 18802
[me@linuxbox ~]$ killall xlogo
[1]- Terminated                xlogo
[2]+ Terminated                xlogo

记住,和 kill 命令一样,你必须拥有超级用户权限才能给不属于你的进程发送信号。

更多和进程相关的命令

因为监测进程是一个很重要的系统管理任务,所以有许多命令与它相关。玩玩下面几个命令:

表11-6: 其它与进程相关的命令

命令名 命令描述
pstree 输出一个树型结构的进程列表(processtree),这个列表展示了进程间父/子关系。
vmstat 输出一个系统资源使用快照,包括内存,交换分区和磁盘 I/O。 为了看到连续的显示结果,则在命令名后加上更新操作延时的时间(以秒为单位)。例如,“vmstat 5”。 ,按下 Ctrl-c 组合键, 终止输出。
xload 一个图形界面程序,可以画出系统负载随时间变化的图形。
tload terminal load与 xload 程序相似,但是在终端中画出图形。使用 Ctrl-c,来终止输出。

配置文件和 shell 环境

第十二章 : shell 环境

恰如我们之前所讲的,shell 在 shell 会话中保存着大量信息。这些信息被称为 (shell 的) 环境。 程序获取环境中的数据(即环境变量)来了解本机的配置。虽然大多数程序用配置文件来存储程序设置, 一些程序会根据环境变量来调整他们的行为。知道了这些,我们就可以用环境变量来自定制 shell 体验。

在这一章,我们将用到以下命令: - printenv - 打印部分或所有的环境变量 - set - 设置 shell 选项 - export — 导出环境变量,让随后执行的程序知道。 - alias - 创建命令别名

什么存储在环境变量中?

shell 在环境中存储了两种基本类型的数据,虽然 bash 几乎无法分辨这些数据的类型。 它们是环境变量和 shell 变量。Shell 变量是 bash 存放的少量数据。剩下的都是 环境变量。除了变量,shell 也存储了一些可编程的数据,即别名和 shell 函数。我们 已经在第六章讨论了别名,而 shell 函数(涉及到 shell 脚本)将会在本章第五部分叙述。

检查环境变量

我们可以用 bash 的内建命令 set,或者是 printenv 程序来查看环境变量。set 命令可以 显示 shell 或环境变量,而 printenv 只是显示环境变量。因为环境变量列表比较长,最好 把每个命令的输出通过管道传递给 less 来阅读:

[chenfy@entrobus32 ~]$ printenv | less

执行以上命令之后,我们应该能得到类似以下内容:

XDG_SESSION_ID=110904
HOSTNAME=entrobus32
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=10.100.1.78 54014 22
SSH_TTY=/dev/pts/3
USER=chenfy
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
PYSPARK_PYTHON=/home/hadoop/anaconda3/bin/python
COBBLER_SERVER=10.18.0.254
MAIL=/var/spool/mail/chenfy
PATH=/usr/bin/:/home/chenfy/anaconda3/bin:/home/hadoop/anaconda3/bin:/usr/local/scala-2.11.8/bin:/usr/local/jdk1.8.0_131/bin:/home/hadoop/anacon
:

我们所看到的是环境变量及其数值的列表。例如,我们看到一个叫做 USER 的变量,这个变量值是 “chenfy”。printenv 命令也能够列出特定变量的数值:

[chenfy@entrobus32 ~]$ printenv USER
chenfy
[chenfy@entrobus32 ~]$ 

当使用没有带选项和参数的 set 命令时,shell 变量,环境变量,和定义的 shell 函数 都会被显示。不同于 printenv 命令,set 命令的输出很友好地按照首字母顺序排列:

[chenfy@entrobus32 ~]$ set | less

也可以通过 echo 命令来查看一个变量的内容,像这样:

[chenfy@entrobus32 ~]$ echo $HOME
/home/chenfy
[chenfy@entrobus32 ~]$ 

别名无法通过使用 set 或 printenv 来查看。 用不带参数的 alias 来查看别名:

[chenfy@entrobus32 ~]$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias vi='vim'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
[chenfy@entrobus32 ~]$ 

一些有趣的环境变量

shell 环境中包含相当多的变量。虽然你的 shell 环境可能与这里的不同,你可能会看到 以下的环境变量:

表12-1: 环境变量

变量 内容
DISPLAY 如果你正在运行图形界面环境,那么这个变量就是你显示器的名字。通常,它是 ":0", 意思是由 X 产生的第一个显示器。
EDITOR 文本编辑器的名字。
SHELL shell 程序的名字。
HOME 用户家目录。
LANG 定义了字符集以及语言编码方式。
OLD_PWD 先前的工作目录。
PAGER 页输出程序的名字。这经常设置为/usr/bin/less。
PATH 由冒号分开的目录列表,当你输入可执行程序名后,会搜索这个目录列表。
PS1 Prompt String 1. 这个定义了你的 shell 提示符的内容。随后我们可以看到,这个变量 内容可以全面地定制。
PWD 当前工作目录。
TERM 终端类型名。类 Unix 的系统支持许多终端协议;这个变量设置你的终端仿真器所用的协议。
TZ 指定你所在的时区。大多数类 Unix 的系统按照协调时间时 (UTC) 来维护计算机内部的时钟 ,然后应用一个由这个变量指定的偏差来显示本地时间。
USER 你的用户名

如果缺失了一些变量,不要担心,这些变量会因发行版本的不同而不同。

如何建立 shell 环境?

当我们登录系统后, bash 程序启动,并且会读取一系列称为启动文件的配置脚本, 这些文件定义了默认的可供所有用户共享的 shell 环境。然后是读取更多位于我们自己家目录中 的启动文件,这些启动文件定义了用户个人的 shell 环境。确切的启动顺序依赖于要运行的 shell 会话 类型。有两种 shell 会话类型:一个是登录 shell 会话,另一个是非登录 shell 会话。

登录 shell 会话会在其中提示用户输入用户名和密码;例如,我们启动一个虚拟控制台会话。 非登录 shell 会话通常当我们在 GUI 下启动终端会话时出现。

登录 shell 会读取一个或多个启动文件,正如表12-2所示:

表12-2: 登录 shell 会话的启动文件

文件 内容
/etc/profile 应用于所有用户的全局配置脚本。
~/.bash_profile 用户个人的启动文件。可以用来扩展或重写全局配置脚本中的设置。
~/.bash_login 如果文件 ~/.bash_profile 没有找到,bash 会尝试读取这个脚本。
~/.profile 如果文件 ~/.bash_profile 或文件 ~/.bash_login 都没有找到,bash 会试图读取这个文件。 这是基于 Debian 发行版的默认设置,比方说 Ubuntu。

非登录 shell 会话会读取以下启动文件:

表12-3: 非登录 shell 会话的启动文件

文件 内容
/etc/bash.bashrc 应用于所有用户的全局配置文件。
~/.bashrc 用户个人的启动文件。可以用来扩展或重写全局配置脚本中的设置。

除了读取以上启动文件之外,非登录 shell 会话也会继承它们父进程的环境设置,通常是一个登录 shell。

浏览一下你的系统,看一看系统中有哪些启动文件。记住-因为上面列出的大多数文件名都以圆点开头 (意味着它们是隐藏文件),你需要使用带”-a”选项的 ls 命令。

在普通用户看来,文件 ~/.bashrc 可能是最重要的启动文件,因为它几乎总是被读取。非登录 shell 默认 会读取它,并且大多数登录 shell 的启动文件会以能读取 ~/.bashrc 文件的方式来书写。

一个启动文件的内容

如果我们看一下典型的 .bash_profile 文件(来自于 CentOS 4 系统),它看起来像这样:

# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH

以”#”开头的行是注释,shell 不会读取它们。它们在那里是为了方便人们阅读。第一件有趣的事情 发生在第四行,伴随着以下代码:

if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi

这叫做一个 if 复合命令,我们将会在第五部分详细地介绍它,现在我们对它翻译一下:

If the file ~/.bashrc exists, then
read the ~/.bashrc file.

我们可以看到这一小段代码就是一个登录 shell 得到 .bashrc 文件内容的方式。在我们启动文件中, 下一件有趣的事与 PATH 变量有关系。

是否曾经对 shell 怎样知道在哪里找到我们在命令行中输入的命令感到迷惑?例如,当我们输入 ls 后, shell 不会查找整个计算机系统来找到 /bin/ls(ls 命令的全路径名),相反,它查找一个目录列表, 这些目录包含在 PATH 变量中。

PATH 变量经常(但不总是,依赖于发行版)在 /etc/profile 启动文件中设置,通过这些代码:

PATH=$PATH:$HOME/bin

修改 PATH 变量,添加目录 $HOME/bin 到目录列表的末尾。这是一个参数展开的实例, 参数展开我们在第八章中提到过。为了说明这是怎样工作的,试试下面的例子:

[chenfy@entrobus32 ~]$ foo="This is some"
[chenfy@entrobus32 ~]$ echo $foo
This is some
[chenfy@entrobus32 ~]$ foo="$foo text."
[chenfy@entrobus32 ~]$ echo $foo
This is some text.
[chenfy@entrobus32 ~]$ 

使用这种技巧,我们可以把文本附加到一个变量值的末尾。通过添加字符串 $HOME/bin 到 PATH 变量值 的末尾,则目录 $HOME/bin 就添加到了命令搜索目录列表中。这意味着当我们想要在自己的家目录下, 创建一个目录来存储我们自己的私人程序时,shell 已经给我们准备好了。我们所要做的事就是 把创建的目录叫做 bin,赶快行动吧。

注意:很多发行版默认地提供了这个 PATH 设置。一些基于 Debian 的发行版,例如 Ubuntu,在登录 的时候,会检测目录 ~/bin 是否存在,若找到目录则把它动态地加到 PATH 变量中。

最后,有下面一行代码:

export PATH

这个 export 命令告诉 shell 让这个 shell 的子进程可以使用 PATH 变量的内容。

修改 shell 环境

既然我们知道了启动文件所在的位置和它们所包含的内容,我们就可以修改它们来定制自己的 shell 环境。

我们应该修改哪个文件?

按照通常的规则,添加目录到你的 PATH 变量或者是定义额外的环境变量,要把这些更改放置到 .bash_profile 文件中(或者其替代文件中,根据不同的发行版。例如,Ubuntu 使用 .profile 文件)。 对于其它的更改,要放到 .bashrc 文件中。除非你是系统管理员,需要为系统中的所有用户修改 默认设置,那么则限定你只能对自己家目录下的文件进行修改。当然,有可能会更改 /etc 目录中的 文件,比如说 profile 文件,而且在许多情况下,修改这些文件也是明智的,但是现在,我们要谨慎行事。

为了编辑(例如,修改)shell 的启动文件以及系统中大多数其它配置文件,我们使用一个叫做文本编辑器的程序。 文本编辑器是一个在某些方面类似于文字处理器的程序,允许你使用移动光标在屏幕上编辑文字。 文本编辑器不同于文字处理器之处在于它只能支持纯文本,并且经常包含为便于写程序而设计的特性。 文本编辑器是软件开发人员用来写代码,以及系统管理员用来管理控制系统的配置文件的重要工具。

Linux 系统有许多不同类型的文本编辑器可用;你的系统中可能已经安装了几个。为什么会有这么 多种呢?可能因为程序员喜欢编写它们,又因为程序员们会频繁地使用它们,所以程序员编写编辑器让 它们按照程序员自己的愿望工作。

文本编辑器分为两种基本类型:图形化的和基于文本的编辑器。GNOME 和 KDE 两者都包含一些流行的 图形化编辑器。GNOME 自带了一个叫做 gedit 的编辑器,这个编辑器通常在 GNOME 菜单中称为”文本编辑器”。 KDE 通常自带了三种编辑器,分别是(按照复杂度递增的顺序排列)kedit,kwrite,kate。

有许多基于文本的编辑器。你将会遇到一些流行的编辑器,它们是 nano、vi和 emacs。 nano 编辑器 是一个简单易用的编辑器,用于替代随 PINE 邮件套件提供的 pico 编辑器。vi 编辑器 (在大多数 Linux 系统中被 vim 替代,vim 是 “Vi IMproved”的简写)是类 Unix 操作系统的传统编辑器。 vim 是我们下一章节的讨论对象。emacs 编辑器最初由 Richard Stallman 写成。它是一个庞大、多用途的, 可做任何事情的编程环境。虽然 emacs 很容易获取,但是大多数 Linux 系统很少默认安装它。

使用文本编辑器

所有的文本编辑器都可以通过在命令行中输入编辑器的名字,加上你所想要编辑的文件来唤醒。如果所 输入的文件名不存在,编辑器则会假定你想要创建一个新文件。下面是一个使用 gedit 的例子:

[me@linuxbox ~]$ gedit some_file

这条命令将会启动 gedit 文本编辑器,同时加载名为 “some_file” 的文件,如果这个文件存在的话。

所有的图形文本编辑器很大程度上都是不需要解释的,所以我们在这里不会介绍它们。反之,我们将集中精力在 我们第一个基于文本的文本编辑器,nano。让我们启动 nano,并且编辑文件 .bashrc。但是在我们这样 做之前,先练习一些”安全计算”。当我们编辑一个重要的配置文件时,首先创建一个这个文件的备份 总是一个不错的主意。这样能避免我们在编辑文件时弄乱文件。创建文件 .bashrc 的备份文件,这样做:

[chenfy@entrobus32 ~]$ cp .bashrc .bashrc.bak
[chenfy@entrobus32 ~]$ 

备份文件的名字无关紧要,只要选择一个容易理解的文件名。扩展名 “.bak”、”.sav”、 “.old”和 “.orig” 都是用来指示备份文件的流行方法。哦,记住 cp 命令会默默地覆盖已经存在的同名文件。

现在我们有了一个备份文件,我们启动 nano 编辑器吧:

[me@linuxbox ~]$ nano .bashrc

注:公司32号机无nano。

一旦 nano 编辑器启动后,我们将会得到一个像下面一样的屏幕:

GNU nano 2.0.3
....

注意:如果你的系统中没有安装 nano 编辑器,你可以用一个图形化的编辑器代替。

这个屏幕由上面的标头,中间正在编辑的文件文本和下面的命令菜单组成。因为设计 nano 是为了 代替由电子邮件客户端提供的编辑器的,所以它相当缺乏编辑特性。在任一款编辑器中,你应该 学习的第一个命令是怎样退出程序。以 nano 为例,你输入 Ctrl-x 来退出 nano。在屏幕底层的菜单中 说明了这个命令。”^X” 表示法意思是 Ctrl-x。这是控制字符的常见表示法,许多程序都使用它。

第二个我们需要知道的命令是怎样保存我们的劳动成果。对于 nano 来说是 Ctrl-o。既然我们 已经获得了这些知识,接下来我们准备做些编辑工作。使用下箭头按键和 / 或下翻页按键,移动 鼠标到文件的最后一行,然后添加以下几行到文件 .bashrc 中:

umask 0002
export HISTCONTROL=ignoredups
export HISTSIZE=1000
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'

注意:你的发行版在这之前可能已经包含其中的一些行,出现重复的代码不会有其他影响。

下表是所添加行的意义:

表12-4:

文本行 含义
umask 0002 设置掩码来解决共享目录的问题。
export HISTCONTROL=ignoredups 使得 shell 的历史记录功能忽略一个命令,如果相同的命令已被记录。
export HISTSIZE=1000 增加命令历史的大小,从默认的 500 行扩大到 1000 行。
alias l.='ls -d .* --color=auto' 创建一个新命令,叫做'l.',这个命令会显示所有以点开头的目录项。
alias ll='ls -l --color=auto' 创建一个叫做'll'的命令,这个命令会显示长格式目录列表。

正如我们所看到的,我们添加的许多代码的意思直觉上并不是明显的,所以添加注释到我们的文件 .bashrc 中是 一个好主意,可以帮助人们理解。使用编辑器,更改我们添加的代码,让它们看起来像这样:

# Change umask to make directory sharing easier
umask 0002
# Ignore duplicates in command history and increase
# history size to 1000 lines
export HISTCONTROL=ignoredups
export HISTSIZE=1000
# Add some helpful aliases
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'

啊,看起来好多了! 当我们完成修改后,输入 Ctrl-o 来保存我们修改的 .bashrc 文件,输入 Ctrl-x 退出 nano。

Why Comments Are Important

为什么注释很重要?

Whenever you modify configuration files it’s a good idea to add some comments to document your changes. Sure, you will remember what you changed tomorrow, but what about six months from now? Do yourself a favor and add some comments. While you’re at it, it’s not a bad idea to keep a log of what changes you make.

不管什么时候你修改配置文件时,给你所做的更改加上注释都是一个好主意。的确,明天你会 记得你修改了的内容,但是六个月之后会怎样呢?帮自己一个忙,加上一些注释吧。当你意识 到这一点后,对你所做的修改做个日志是个不错的主意。

Shell scripts and bash startup files use a “#” symbol to begin a comment. Other configuration files may use other symbols. Most configuration files will have comments. Use them as a guide.

Shell 脚本和 bash 启动文件都使用 “#” 符号来开始注释。其它配置文件可能使用其它的符号。 大多数配置文件都有注释。把它们作为指南。

You will often see lines in configuration files that are commented out to prevent them from being used by the affected program. This is done to give the reader suggestions for possible configuration choices or examples of correct configuration syntax. For example, the .bashrc file of Ubuntu 8.04 contains these lines:

你会经常看到配置文件中的一些行被注释掉,以此防止它们被受影响的程序使用。这样做 是为了给读者在可能的配置选项方面一些建议,或者给出正确的配置语法实例。例如,Ubuntu 8.04 中的 .bashrc 文件包含这些行:

# some more ls aliases
#alias ll='ls -l'
#alias la='ls -A'
#alias l='ls -CF'

The last three lines are valid alias definitions that have been commented out. If you remove the leading “#” symbols from these three lines, a technique called uncommenting, you will activate the aliases. Conversely, if you add a “#” symbol to the beginning of a line, you can deactivate a configuration line while preserving the information it contains.

最后三行是有效的被注释掉的别名定义。如果你删除这三行开头的 “#” 符号,此技术程称为 uncommenting (取消注释),这样你就会激活这些别名。相反地,如果你在一行的开头加上 “#” 符号, 你可以注销掉这一行,但会保留它所包含的信息。

激活我们的修改

我们对于文件 .bashrc 的修改不会生效,直到我们关闭终端会话,再重新启动一个新的会话, 因为 .bashrc 文件只是在刚开始启动终端会话时读取。然而,我们可以强迫 bash 重新读取修改过的 .bashrc 文件,使用下面的命令:

[me@linuxbox ~]$ source .bashrc

运行上面命令之后,我们就应该能够看到所做修改的效果了。试试其中一个新的别名:

[me@linuxbox ~]$ ll

总结

在这一章中,我们学到了用文本编辑器来编辑配置文件的基本技巧。随着学习的继续,当我们 浏览命令的手册页时,可以记录下该命令所支持的环境变量。这样或许我们能够收获一到两个特别好用的宝贝命令。 在随后的章节里面,我们将会学习 shell 函数,一个很强大的特性,你可以把它包含在 bash 启动文件里面, 以此来添加你自定制的命令宝库。

拓展阅读

bash 手册页的 INVOCATION 部分非常详细地讨论了 bash 启动文件。

第十三章 : vi 简介

有一个古老的笑话,说是一个在纽约的游客向行人打听这座城市中著名古典音乐场馆的方向:

游客: 请问一下,我怎样去卡内基音乐大厅?

行人: 练习,练习,练习!

学习 Linux 命令行,就像要成为一名造诣很深的钢琴家一样,它不是我们一下午就能学会的技能。这需要 经历几年的勤苦练习。在这一章中,我们将介绍 vi(发音“vee eye”)文本编辑器,它是 Unix 传统中核心程序之一。 vi 因它难用的用户界面而有点声名狼藉,但是当我们看到一位大师坐在钢琴前开始演奏时,我们的确成了 伟大艺术的见证人。虽然我们在这里不能成为 vi 大师,但是当我们学完这一章后, 我们会知道怎样在 vi 中弹奏像“Chopsticks”那样的钢琴小品。

为什么我们应该学习 vi

在现在这个图形化编辑器和易于使用的基于文本编辑器的时代,比如说 nano,为什么我们还应该学习 vi 呢? 下面有三个充分的理由:

好吧,可能只有两个充分的理由。

一点儿背景介绍

第一版 vi 是在1976由 Bill Joy 写成的,当时他是加州大学伯克利分校的学生, 后来他共同创建了 Sun 微系统公司。vi 这个名字 来源于单词“visual”,因为它打算在带有可移动光标的视频终端上编辑文本。在发明可视化编辑器之前, 有一次只能操作一行文本的行编辑器。为了编辑,我们需要告诉行编辑器到一个特殊行并且 说明做什么修改,比方说添加或删除文本。视频终端(而不是基于打印机的终端,像电传打印机)的出现 ,使可视化编辑成为可能。vi 实际上整合了一个强大的行编辑器 ———— ex , 所以我们在使用 vi 时能运行行编辑命令。

大多数 Linux 发行版不包含真正的 vi;而是自带一款高级替代版本,叫做 vim(它是“vi improved”的简写)由 Bram Moolenaar 开发的。vim 相对于传统的 Unix vi 来说,取得了实质性进步。通常,vim 在 Linux 系统中是“vi”的符号链接(或别名)。 在随后的讨论中,我们将会假定我们有一个叫做“vi”的程序,但它其实是 vim。

启动和退出 vi

要想启动 vi,只要简单地输入以下命令:

[chenfy@entrobus32 ~]$ vi

一个像这样的屏幕应该出现:

~                          VIM - Vi IMproved                            
~                                                                       
~                            版本 7.4.1099                              
~                       维护人 Bram Moolenaar 等                        
~                     修改者 <bugzilla@redhat.com>                      
~                   Vim 是可自由分发的开放源代码软件                    
~                                                                       
~                        帮助乌干达的可怜儿童!                         
~            输入  :help iccf<Enter>       查看说明                     
~                                                                       
~            输入  :q<Enter>               退出                         
~            输入  :help<Enter>  或  <F1>  查看在线帮助                 
~            输入  :help version7<Enter>   查看版本信息 

正如我们之前操作 nano 时,首先要学的是怎样退出 vi。要退出 vi,输入下面的命令(注意冒号是命令的一部分):

:q

shell 提示符应该重新出现。如果由于某种原因,vi 不能退出(通常因为我们对文件做了修改,却没有保存文件)。 通过给命令加上叹号,我们可以告诉 vi 我们真要退出 vi。(注意感叹号是命令的一部分)

:q!

小贴示:如果你在 vi 中“迷失”了,试着按下 Esc 键两次来回到普通模式。

Compatibility Mode

兼容模式

In the example startup screen above (taken from Ubuntu 8.04), we see the text “Running in Vi compatible mode.” This means that vim will run in a mode that is closer to the normal behavior of vi rather than the enhanced behavior of vim. For purposes of this chapter, we will want to run vim with its enhanced behavior. To do this, you have a few options:

在上面的截屏中(来自于 Ubuntu 8.04),我们看到一行文字 “运行于 Vi 兼容模式。” 这意味着 vim 将以近似于 vi 的普通的模式 运行,而不是以 vim 的高级的模式运行。出于本章的教学目的,我们将使用 vim 和它的的高级模式。 要这样使用vim,可以通过如下方法:

Try running vim instead of vi.

用 vim 来代替 vi。

If that works, consider adding alias vi=’vim’ to your .bashrc file.

如果命令生效,考虑在你的.bashrc 文件中添加 alias vi=’vim’。

Alternately, use this command to add a line to your vim configuration file:

或者,使用以下命令在你的 vim 配置文件中添加一行:

echo “set nocp” >> ~/.vimrc

Different Linux distributions package vim in different ways. Some distributions install a minimal version of vim by default that only supports a limiting set of vim features. While preforming the lessons that follow, you may encounter missing features. If this is the case, install the full version of vim.

不同 Linux 发行版自带的 vim 软件包各不相同。一些发行版预装了 vim 的最简版, 其只支持很有限的 vim 特性。在随后练习里,你可能发现你的 vim 缺失一些特性。 若是如此,请安装 vim 的完整版。

编辑模式

再次启动 vi,这次传递给 vi 一个不存在的文件名。这也是用 vi 创建新文件的方法。

[chenfy@entrobus32 ~]$ rm -f foo.txt
[chenfy@entrobus32 ~]$ vi foo.txt

如果一切正常,我们应该获得一个像这样的屏幕:

....
"foo.txt" [New File]

每行开头的波浪号(”~”)表示那一行没有文本。这里我们有一个空文件。先别进行输入!

关于 vi ,第二重要的事是知晓vi 是一个模式编辑器。(第一件事是如何退出 vi )vi 启动后会直接进入 命令模式。这种模式下,几乎每个按键都是一个命令,所以如果我们直接输入文本,vi 会发疯,弄得一团糟。

插入模式

为了在文件中添加文本,我们需要先进入插入模式。按下”i”键进入插入模式。之后,我们应当 在屏幕底部看到如下的信息,如果 vi 运行在高级模式下( vi 在兼容模式下不会显示这行信息):

-- INSERT --

现在我们能输入一些文本了。试着输入这些文本:

The quick brown fox jumped over the lazy dog.

若要退出插入模式返回命令模式,按下 Esc 按键。

保存我们的工作

为了保存我们刚才对文件所做的修改,我们必须在命令模式下输入一个 ex 命令。 通过按下”:”键,这很容易完成。按下冒号键之后,一个冒号字符应该出现在屏幕的底部:

:

为了写入我们修改的文件,我们在冒号之后输入”w”字符,然后按下回车键:

:w

文件将会写入到硬盘,而且我们会在屏幕底部看到一行确认信息,就像这样:

"foo.txt" [New] 1L, 46C written

小贴示:如果你阅读 vim 的文档,你会发现命令模式被(令人困惑地)叫做普通模式,ex 命令 叫做命令模式。当心。

移动光标

当在 vi 命令模式下时,vi 提供了大量的移动命令,其中一些与 less 阅读器的相同。这里 列举了一些:

表13-1: 光标移动按键

按键 移动光标
l or 右箭头 向右移动一个字符
h or 左箭头 向左移动一个字符
j or 下箭头 向下移动一行
k or 上箭头 向上移动一行
0 (零按键) 移动到当前行的行首。
^ 移动到当前行的第一个非空字符。
$ 移动到当前行的末尾。
w 移动到下一个单词或标点符号的开头。
W 移动到下一个单词的开头,忽略标点符号。
b 移动到上一个单词或标点符号的开头。
B 移动到上一个单词的开头,忽略标点符号。
Ctrl-f or Page Down 向下翻一页
Ctrl-b or Page Up 向上翻一页
numberG 移动到第 number 行。例如,1G 移动到文件的第一行。
G 移动到文件末尾。

为什么 h,j,k,和 l 按键被用来移动光标呢?因为在开发 vi 之初,并不是所有的视频终端都有 箭头按键,熟练的打字员可以使用组合键来移动光标,他们的手指从不需要移开键盘。

vi 中的许多命令都可以在前面加上一个数字,比方说上面提到的”G”命令。在命令之前加上一个 数字,我们就可以指定命令执行的次数。例如,命令”5j”将光标下移5行。

基本编辑

大多数编辑工作由一些基本的操作组成,比如说插入文本,删除文本和通过剪切和粘贴来移动文本。 vi,当然,有它独特方式来实现所有的操作。vi 也提供了撤销功能,但有些限制。如果我们按下“u” 按键,当在命令模式下,vi 将会撤销你所做的最后一次修改。当我们试着执行一些基本的 编辑命令时,这会很方便。

追加文本

vi 有几种不同进入插入模式的方法。我们已经使用了 i 命令来插入文本。

让我们再次进入到我们的 foo.txt 文件:

The quick brown fox jumped over the lazy dog.

如果我们想要在这个句子的末尾添加一些文本,我们会发现 i 命令不能完成任务,因为我们不能把 光标移到行尾。vi 提供了追加文本的命令,明智地命名为”a”。如果我们把光标移动到行尾,输入”a”, 光标就会越过行尾,同时 vi 会进入插入模式。这让我们能添加文本到行末:

The quick brown fox jumped over the lazy dog. It was cool.

记得按 Esc 键来退出插入模式。

因为我们几乎总是想要在行尾添加文本,所以 vi 提供了一个快捷键。光标将移动到行尾,同时 vi 进入输入模式。 它是”A”命令。试着用一下它,向文件添加更多行。

首先,使用”0”(零)命令,将光标移动到行首。现在我们输入”A”,然后输入下面这些文本:

The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3
Line 4
Line 5

再一次,按下 Esc 键退出插入模式。

正如我们所看到的, “A” 命令非常有用,因为它在进入到插入模式前,先将光标移到了行尾。

打开一行

我们插入文本的另一种方式是“打开(open)”一行。这会在两行之间插入一个空白行,并且进入到插入模式。 这种方式有两个变体:

表13-2: 文本行打开按键

命令 打开行
o 当前行的下方打开一行。
O 当前行的上方打开一行。

我们可以演示一下:把光标移到”Line 3”上,再按下小 o 按键。

The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3

line 4
line 5

在第三行之下打开了新的一行,并且进入插入模式。按下 Esc,退出插入模式。按下 u 按键,撤销我们的修改。

按下大 O 按键在光标之上打开新的一行:

The quick brown fox jumped over the lazy dog. It was cool.
Line 2

Line 3
Line 4
Line 5

按下 Esc 按键,退出插入模式,并且按下 u 按键,撤销我们的更改。

删除文本

正如我们所愿,vi 提供了各种删除文本到的方法,而且只需一或两个按键。首先, x 按键会删除光标位置的一个字符。可以在 x 命令之前带上一个数字,来指明要删除的字符个数。 d 按键更通用一些。跟 x 命令一样,d 命令之前可以带上一个数字,来指定要执行的删除次数。另外, d 命令之后总是带上一个移动命令,用来控制删除的范围。这里有些实例:

表13-3: 文本删除命令

命令 删除的文本
x 当前字符
3x 当前字符及其后的两个字符。
dd 当前行。
5dd 当前行及随后的四行文本。
dW 从光标位置开始到下一个单词的开头。
d$ 从光标位置开始到当前行的行尾。
d0 从光标位置开始到当前行的行首。
d^ 从光标位置开始到文本行的第一个非空字符。
dG 从当前行到文件的末尾。
d20G 从当前行到文件的第20行。

把光标放到第一行单词“It”之上。重复按下 x 按键直到删除剩下的部分。下一步,重复按下 u 按键 直到恢复原貌。

注意:真正的 vi 只是支持单层面的 undo 命令。vim 则支持多个层面的。

我们再次执行删除命令,这次使用 d 命令。还是移动光标到单词”It”之上,按下的 dW 来删除单词:

The quick brown fox jumped over the lazy dog. was cool.
Line 2
Line 3
Line 4
Line 5

按下 d$删除从光标位置到行尾的文本:

The quick brown fox jumped over the lazy dog.
Line 2
Line 3
Line 4
Line 5

按下 dG 按键删除从当前行到文件末尾的所有行:

~
....

连续按下 u 按键三次,来恢复删除部分。

剪切,复制和粘贴文本

这个 d 命令不仅删除文本,它还“剪切”文本。每次我们使用 d 命令,删除的部分被复制到一个 粘贴缓冲区中(看作剪切板)。过后我们执行小 p 命令把剪切板中的文本粘贴到光标位置之后, 或者是大 P 命令把文本粘贴到光标之前。

y 命令用来“拉”(复制)文本,和 d 命令剪切文本的方式差不多。这里有些把 y 命令和各种移动命令 结合起来使用的实例:

表13-4: 复制命令

命令 复制的内容
yy 当前行。
5yy 当前行及随后的四行文本。
yW 从当前光标位置到下一个单词的开头。
y$ 从当前光标位置到当前行的末尾。
y0 从当前光标位置到行首。
y^ 从当前光标位置到文本行的第一个非空字符。
yG 从当前行到文件末尾。
y20G 从当前行到文件的第20行。

我们试着做些复制和粘贴工作。把光标放到文本第一行,输入 yy 来复制当前行。下一步,把光标移到 最后一行(G),输入小写的 p 把复制的一行粘贴到当前行的下面:

The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3
Line 4
Line 5
The quick brown fox jumped over the lazy dog. It was cool.

和以前一样,u 命令会撤销我们的修改。这时光标仍位于文件的最后一行,输入大写的 P 命令把 所复制的文本粘贴到当前行之上:

The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3
Line 4
The quick brown fox jumped over the lazy dog. It was cool.
Line 5

试着执行上表中其他的一些 y 命令,了解小写 p 和大写 P 命令的行为。当你完成练习之后,把文件 恢复原样。

连接行

vi 对于行的概念相当严格。通常,用户不可能通过删除“行尾结束符”(end-of-line character)来连接 当前行和它下面的一行。由于这个原因,vi 提供了一个特定的命令,大写的 J(不要与小写的 j 混淆了, j 是用来移动光标的)用于链接行与行。

如果我们把光标放到 line 3上,输入大写的 J 命令,看看发生什么情况:

The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3 Line 4
Line 5

查找和替换

vi 能把光标移到搜索到的匹配项上。vi 不仅能在搜索一特定行,还能进行全文搜索。 它也可以在有或没有用户确认的情况下实现文本替换。

查找一行

f 命令能搜索一特定行,并将光标移动到下一个匹配的字符上。例如,命令 fa 会把光标定位到同一行中 下一个出现的”a”字符上。在进行了一次行内搜索后,输入分号能重复这次搜索。

查找整个文件

移动光标到下一个出现的单词或短语上,使用 / 命令。这个命令和我们之前在 less 程序中学到 的一样。当你输入/命令后,一个”/”字符会出现在屏幕底部。接下来,输入要查找的单词或短语, 按下回车。光标就会移动到下一个包含所查找字符串的位置。通过 n 命令来重复先前的查找。 这里有个例子:

The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3
Line 4
Line 5

移动光标到文件的第一行。输入:

/Line

然后敲回车。光标会移动到第二行。然后输入 n,这时光标移动到第三行。重复键入 n 命令,光标会 继续向下移动直到遍历所有的匹配项。至此我们只是通过输入单词和短语进行搜索,但 vi 支持正则 表达式,一种用于表达复杂文本的方法。我们将会在之后的章节中详细讲解正则表达式。

全局查找和替代

vi 使用 ex 命令来执行查找和替代操作(vi 中叫做“替换”)。将整个文件中的单词“Line”更改为“line”, 输入以下命令:

:%s/Line/line/g

我们把这个命令分解为几个单独的部分,看一下每部分的含义:

条目 含义
: 冒号字符运行一个 ex 命令。
% 指定要操作的行数。% 是一个快捷方式,表示从第一行到最后一行。另外,操作范围也 可以用 1,5 来代替(因为我们的文件只有5行文本),或者用 1,$ 来代替,意思是 “ 从第一行到文件的最后一行。” 如果省略了文本行的范围,那么操作只对当前行生效。
s 指定操作。在这种情况下是,替换(查找与替代)。
/Line/line 查找类型与替代文本。
g 这是“全局”的意思,意味着对文本行中所有匹配的字符串执行查找和替换操作。如果省略 g,则 只替换每个文本行中第一个匹配的字符串。

执行完查找和替代命令之后,我们的文件看起来像这样:

The quick brown fox jumped over the lazy dog. It was cool.
line 2
line 3
line 4
line 5

我们也可以指定一个需要用户确认的替换命令。通过添加一个”c”字符到这个命令的末尾,来完成 这个替换命令。例如:

:%s/line/Line/gc

这个命令会把我们的文件恢复先前的模样;然而,在执行每个替换命令之前,vi 会停下来, 通过下面的信息,来要求我们确认这个替换:

replace with Line (y/n/a/q/l/^E/^Y)?

括号中的每个字符都是一个可能的选择,如下所示:

表13-5: 替换确认按键

按键 行为
y 执行替换操作
n 跳过这个匹配的实例
a 对这个及随后所有匹配的字符串执行替换操作。
q or esc 退出替换操作。
l 执行这次替换并退出。l 是 “last” 的简写。
Ctrl-e, Ctrl-y 分别是向下滚动和向上滚动。用于查看建议替换的上下文。

如果你输入 y,则执行这个替换,输入 n 则会导致 vi 跳过这个实例,而移到下一个匹配项上。

编辑多个文件

同时能够编辑多个文件是很有用的。你可能需要更改多个文件或者从一个文件复制内容到 另一个文件。通过 vi,我们可以打开多个文件来编辑,只要在命令行中指定要编辑的文件名。

vi file1 file2 file3...

我们先退出已经存在的 vi 会话,然后创建一个新文件来编辑。输入:wq 来退出 vi 并且保存了所做的修改。 下一步,我们将在家目录下创建一个额外的用来玩耍的文件。通过获取从 ls 命令的输出,来创建这个文件。

[chenfy@entrobus32 ~]$ ls -l /usr/bin > ls-output.txt

用 vi 来编辑我们的原文件和新创建的文件:

[chenfy@entrobus32 ~]$ vi foo.txt ls-output.txt

vi 启动,我们会看到第一个文件显示出来:

The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3
Line 4
Line 5

文件之间切换

从这个文件切换下一个文件,使用这个 ex 命令:

:n

回到先前的文件使用:

:N

当我们从一个文件移到另一个文件时,如果当前文件没有保存修改,vi 会阻止我们切换文件, 这是 vi 强制执行的政策。在命令之后添加感叹号,可以强迫 vi 放弃修改而转换文件。

另外,上面所描述的切换方法,vim(和一些版本的 vi)也提供了一些 ex 命令,这些命令使 多个文件更容易管理。我们可以查看正在编辑的文件列表,使用:buffers 命令。运行这个 命令后,屏幕顶部就会显示出一个文件列表:

:buffers
1 #     "foo.txt"                 line 1
2 %a    "ls-output.txt"           line 0
Press ENTER or type command to continue

要切换到另一个缓冲区(文件),输入 :buffer, 紧跟着你想要编辑的缓冲器编号。比如,要从包含文件 foo.txt 的1号缓冲区切换到包含文件 ls-output.txt 的2号缓冲区,我们会这样输入:

:buffer 2

我们的屏幕现在会显示第二个文件。

打开另一个文件并编辑

在我们的当前的编辑会话里也能添加别的文件。ex 命令 :e (编辑(edit) 的简写) 紧跟要打开的文件名将会打开 另外一个文件。 让我们结束当前的会话回到命令行。

重新启动vi并只打开一个文件

[chenfy@entrobus32 ~]$ vi foo.txt

要加入我们的第二个文件,输入:

:e ls-output.txt

它应该显示在屏幕上。 我们可以这样来确认第一个文件仍然存在:

:buffers
 1 # "foo.txt" line 1
 2 %a "ls-output.txt" line 0
Press ENTER or type command to continue 

注意:当文件由 :e 命令加载,你将无法用 :n 或 :N 命令来切换文件。 这时要使用 :buffer 命令加缓冲区号码,来切换文件。

跨文件复制黏贴

当我们编辑多个文件时,经常地要复制文件的一部分到另一个正在编辑的文件。使用之前我们学到的 拉(yank)和粘贴命令,这很容易完成。说明如下。以打开的两个文件为例,首先转换到缓冲区1(foo.txt) ,输入:

:buffer 1

我们应该得到如下输出:

The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3
Line 4
Line 5

下一步,把光标移到第一行,并且输入 yy 来复制这一行。

转换到第二个缓冲区,输入:

:buffer 2

现在屏幕会包含一些文件列表(这里只列出了一部分):

total 343700
-rwxr-xr-x 1 root root    31316  2007-12-05  08:58 [
....

移动光标到第一行,输入 p 命令把我们从前面文件中复制的一行粘贴到这个文件中:

total 343700
The quick brown fox jumped over the lazy dog. It was cool.
-rwxr-xr-x 1 root root    31316  2007-12-05  08:58 [
....

插入整个文件到另一个文件

我们也可以把整个文件插入到我们正在编辑的文件中。看一下实际操作,结束 vi 会话,重新 启动一个只打开一个文件的 vi 会话:

[me@linuxbox ~]$ vi ls-output.txt

再一次看到我们的文件列表:

total 343700
-rwxr-xr-x 1 root root    31316  2007-12-05  08:58 [

移动光标到第三行,然后输入以下 ex 命令:

:r foo.txt

这个:r 命令(是”read”的简称)把指定的文件插入到光标位置之前。现在屏幕应该看起来像这样:

total 343700
-rwxr-xr-x 1 root root     31316 2007-12-05  08:58 [
....
The quick brown fox jumped over the lazy dog. It was cool.
Line 2
Line 3
Line 4
Line 5
-rwxr-xr-x 1 root root     111276 2008-01-31  13:36 a2p
....

保存工作

像 vi 中的其它操作一样,有几种不同的方法来保存我们所修改的文件。我们已经研究了:w 这个 ex 命令, 但还有几种方法,可能我们也觉得有帮助。

在命令模式下,输入 ZZ 就会保存并退出当前文件。同样地,ex 命令:wq 把:w 和:q 命令结合到 一起,来完成保存和退出任务。

这个:w 命令也可以指定可选的文件名。这个的作用就如”Save As…“。例如,如果我们 正在编辑 foo.txt 文件,想要保存一个副本,叫做 foo1.txt,那么我们可以执行以下命令:

:w foo1.txt

注意:当上面的命令以一个新名字保存文件时,它并没有更改你正在编辑的文件的名字。 如果你继续编辑,你还是在编辑文件 foo.txt,而不是 foo1.txt。

拓展阅读

即使把这章所学的内容都加起来,我们也只是学了 vi 和 vim 的一点儿皮毛而已。这里 有一些在线资料,可以帮助你进一步掌握 vi。

(略)

第十四章 : 自定制 shell 提示符

在这一章中,我们将会看一下表面上看来很琐碎的细节-shell 提示符。但这会揭示一些 shell 和 终端仿真器的内部工作方式。

和 Linux 内的许多程序一样,shell 提示符是可高度配置的,虽然我们把它相当多地看作是理所当然的, 但是我们一旦学会了怎样控制它,shell 提示符是一个相当有用的工具。

解剖一个提示符

我们默认的提示符看起来像这样:

[me@linuxbox ~]$

注意它包含我们的用户名,主机名和当前工作目录,但是它又是怎样得到这些东西的呢? 结果证明非常简单。提示符是由一个环境变量定义的,叫做 PS1(是“prompt string one” 的简写)。我们可以通过 echo 命令来查看 PS1的内容。

[chenfy@entrobus32 ~]$ echo $PS1
[\u@\h \W]\$
[chenfy@entrobus32 ~]$ 

注意:如果你 shell 提示符的内容和上例不是一模一样,也不必担心。每个 Linux 发行版 定义的提示符稍微有点不同,其中一些相当异于寻常。

从输出结果中,我们看到那个 PS1 环境变量包含一些这样的字符,比方说中括号,@符号,和美元符号, 但是剩余部分就是个谜。我们中一些机敏的人会把这些看作是由反斜杠转义的特殊字符,就像我们 在第八章中看到的一样。这里是一部分字符列表,在提示符中 shell 会特殊对待这些字符:

表14-1: Shell 提示符中用到的转义字符

序列 显示值
\a 以 ASCII 格式编码的铃声 . 当遇到这个转义序列时,计算机会发出嗡嗡的响声。
\d 以日,月,天格式来表示当前日期。例如,“Mon May 26.”
\h 本地机的主机名,但不带末尾的域名。
\H 完整的主机名。
\j 运行在当前 shell 会话中的工作数。
\l 当前终端设备名。
\n 一个换行符。
\r 一个回车符。
\s shell 程序名。
\t 以24小时制,hours:minutes:seconds 的格式表示当前时间。
\T 以12小时制表示当前时间。
\@ 以12小时制,AM/PM 格式来表示当前时间。
\A 以24小时制,hours:minutes 格式表示当前时间。
\u 当前用户名。
\v shell 程序的版本号。
\V Version and release numbers of the shell.
\w 当前工作目录名。
\W 当前工作目录名的最后部分。
\! 当前命令的历史号。
\# 当前 shell 会话中的命令数。
\\$ 这会显示一个"$"字符,除非你拥有超级用户权限。在那种情况下, 它会显示一个"#"字符。
\[ 标志着一系列一个或多个非打印字符的开始。这被用来嵌入非打印 的控制字符,这些字符以某种方式来操作终端仿真器,比方说移动光标或者是更改文本颜色。
\] 标志着非打印字符序列结束。

试试一些可替代的提示符设计

参照这个特殊字符列表,我们可以更改提示符来看一下效果。首先, 我们把原来提示符字符串的内容备份一下,以备之后恢复原貌。为了完成备份, 我们把已有的字符串复制到另一个 shell 变量中,这个变量是我们自己创造的。

[chenfy@entrobus32 ~]$ ps1_old="$PS1"
[chenfy@entrobus32 ~]$ 

我们新创建了一个叫做 ps1_old 的变量,并把变量 PS1的值赋 ps1_old。通过 echo 命令可以证明 我们的确复制了 PS1的值。

[chenfy@entrobus32 ~]$ echo $ps1_old
[\u@\h \W]\$
[chenfy@entrobus32 ~]$ 

在终端会话中,我们能在任一时间复原提示符,只要简单地反向操作就可以了。

[chenfy@entrobus32 ~]$ PS1="$ps1_old"
[chenfy@entrobus32 ~]$ 

现在,我们准备开始,让我们看看如果有一个空的字符串会发生什么:

[chenfy@entrobus32 ~]$ PS1=

如果我们没有给提示字符串赋值,那么我们什么也得不到。根本没有提示字符串!提示符仍然在那里, 但是什么也不显示,正如我们所要求的那样。我们将用一个最小的提示符来代替它:

PS1="\$ "

这样要好一些。至少能看到我们在做什么。注意双引号中末尾的空格。当提示符显示的时候, 这个空格把美元符号和光标分离开。

在提示符中添加一个响铃:

$ PS1="\a\$ "

现在每次提示符显示的时候,我们应该能听到嗡嗡声。这会变得很烦人,但是它可能会 很有用,特别是当一个需要运行很长时间的命令执行完后,我们要得到通知。

下一步,让我们试着创建一个信息丰富的提示符,包含主机名和当天时间的信息。

$ PS1="\A \h \$ "
22:22 entrobus32 $

试试其他上表中列出的转义序列,看看你能否想出精彩的新提示符。

添加颜色

大多数终端仿真器程序支持一定的非打印字符序列来控制,比方说字符属性(像颜色,黑体和可怕的闪烁) 和光标位置。我们会更深入地讨论光标位置,但首先我们要看一下字体颜色。

Terminal Confusion

混乱的终端时代

Back in ancient times, when terminals were hooked to remote computers, there were many competing brands of terminals and they all worked differently. They had different keyboards and they all had different ways of interpreting control information. Unix and Unix-like systems have two rather complex subsystems to deal with the babel of terminal control (called termcap and terminfo). If you look in the deepest recesses of your terminal emulator settings you may find a setting for the type of terminal emulation.

回溯到终端连接到远端计算机的时代,有许多竞争的终端品牌,它们各自工作不同。 它们有着不同的键盘,以不同的方式来解释控制信息。Unix 和类 Unix 的系统有两个 相当复杂的子系统来处理终端控制领域的混乱局面(称为 termcap 和 terminfo)。如果你 查看一下终端仿真器最底层的属性设置,可能会找到一个关于终端仿真器类型的设置。

In an effort to make terminals speak some sort of common language, the American National Standards Institute (ANSI) developed a standard set of character sequences to control video terminals. Old time DOS users will remember the ANSI.SYS file that was used to enable interpretation of these codes.

为了努力使所有的终端都讲某种通用语言,美国国家标准委员会(ANSI)制定了 一套标准的字符序列集合来控制视频终端。原先 DOS 用户会记得 ANSI.SYS 文件, 这是一个用来使这些编码解释生效的文件。

字符颜色是由发送到终端仿真器的一个嵌入到了要显示的字符流中的 ANSI 转义编码来控制的。 这个控制编码不会“打印”到屏幕上,而是被终端解释为一个指令。正如我们在上表看到的字符序列, 这个 [ 和 ] 序列被用来封装这些非打印字符。一个 ANSI 转义编码以一个八进制033(这个编码是由 退出按键产生的)开头,其后跟着一个可选的字符属性,在之后是一个指令。例如,把文本颜色 设为正常(attribute = 0),黑色文本的编码如下:

\033[0;30m

表14-2: 用转义序列来设置文本颜色

序列 文本颜色 序列 文本颜色
\033[0;30m 黑色 \033[1;30m 深灰色
\033[0;31m 红色 \033[1;31m 浅红色
\033[0;32m 绿色 \033[1;32m 浅绿色
\033[0;33m 棕色 \033[1;33m 黄色
\033[0;34m 蓝色 \033[1;34m 浅蓝色
\033[0;35m 粉红 \033[1;35m 浅粉色
\033[0;36m 青色 \033[1;36m 浅青色
\033[0;37m 浅灰色 \033[1;37m 白色

让我们试着制作一个红色提示符。我们将在开头加入转义编码:

[<chenfy@entrobus32 ~>$PS1="$ps1_old"
[chenfy@entrobus32 ~]$ PS1='\[\033[0;31m\]<\u@\h \W>\$\[\033[0m\]'
<chenfy@entrobus32 ~>$

这看起来要好些!

也有可能要设置文本的背景颜色,使用下面列出的转义编码。这个背景颜色不支持黑体属性。

表14-3: 用转义序列来设置背景颜色

序列 背景颜色 序列 背景颜色
\033[0;40m 蓝色 \033[1;44m 黑色
\033[0;41m 红色 \033[1;45m 紫色
\033[0;42m 绿色 \033[1;46m 青色
\033[0;43m 棕色 \033[1;47m 浅灰色

我们可以创建一个带有红色背景的提示符,只是对第一个转义编码做个简单的修改。

<me@linuxbox ~>$ PS1='\[\033[0;41m\]<\u@\h \W>\$\[\033[0m\] '
<me@linuxbox ~>$

试试这些颜色编码,看看你能定制出怎样的提示符!

注意:除了正常的 (0) 和黑体 (1) 字符属性之外,文本也可以具有下划线 (4),闪烁 (5), 和反向 (7) 属性。然而,为了拥有好品味,许多终端仿真器拒绝使用这个闪烁属性。

移动光标

转义编码也可以用来定位光标。这些编码被普遍地用来,每次当提示符出现的时候,会在屏幕的不同位置 比如说上面一个角落,显示一个时钟或者其它一些信息。这里是一系列用来定位光标的转义编码:

表14-4: 光标移动转义序列

转义编码 行动
\033[l;cH 把光标移到第 l 行,第 c 列。
\033[nA 把光标向上移动 n 行。
\033[nB 把光标向下移动 n 行。
\033[nC 把光标向前移动 n 个字符。
\033[nD 把光标向后移动 n 个字符。
\033[2J 清空屏幕,把光标移到左上角(第零行,第零列)。
\033[K 清空从光标位置到当前行末的内容。
\033[s 存储当前光标位置。
\033[u 唤醒之前存储的光标位置。

使用上面的编码,我们将构建一个提示符,每次当这个提示符出现的时候,会在屏幕的上方画出一个 包含时钟(由黄色文本渲染)的红色长条。构建好的提示符的编码就是这串看起来令人敬畏的字符串:

PS1='\[\033[s\033[0;0H\033[0;41m\033[K\033[1;33m\t\033[0m\033[u\]
<\u@\h \W>\$ '

让我们分别看一下这个字符串的每一部分所表示的意思:

序列 行动
\[ 开始一个非打印字符序列。其真正的目的是为了让 bash 能够正确地计算提示符的大小。如果没有这个转义字符的话,命令行编辑 功能会弄错光标的位置。
\033[s 存储光标位置。这个用来使光标能回到原来提示符的位置, 当长条和时钟显示到屏幕上方之后。当心一些 终端仿真器不推崇这个编码。
\033[0;0H 把光标移到屏幕左上角,也就是第零行,第零列的位置。
\033[0;41m 把背景设置为红色。
\033[K 清空从当前光标位置到行末的内容。因为现在 背景颜色是红色,则被清空行背景成为红色,以此来创建长条。注意虽然一直清空到行末, 但是不改变光标位置,它仍然在屏幕左上角。
\033[1;33m 把文本颜色设为黄色。
\t 显示当前时间。虽然这是一个可“打印”的元素,但我们仍把它包含在提示符的非打印部分, 因为我们不想 bash 在计算可见提示符的真正大小时包括这个时钟在内。
\033[0m 关闭颜色设置。这对文本和背景都起作用。
\033[u 恢复到之前保存过的光标位置处。
\] 结束非打印字符序列。
<\u@\h \W>\$ 提示符字符串。

保存提示符

显然地,我们不想总是敲入那个怪物,所以我们将要把这个提示符存储在某个地方。通过把它 添加到我们的.bashrc 文件,可以使这个提示符永久存在。为了达到目的,把下面这两行添加到.bashrc 文件中。

PS1='\[\033[s\033[0;0H\033[0;41m\033[K\033[1;33m\t\033[0m\033[u\]<\u@\h \W>\$ '
export PS1

总结归纳

不管你信不信,如果加上我们在这里没有论及的 shell 函数和脚本,还有许多事情可以由提示符来完成。 但这是一个好的开始。并不是每个人都会花心思来更改提示符,因为通常默认的提示符就很让人满意。 但是对于我们这些喜欢思考的人们来说,shell 却提供了许多制造琐碎乐趣的机会。

拓展阅读(略)

注:我认为这章相对没那么重要。

常见任务和基本工具

第十五章 : 软件包管理

第十六章 : 存储媒介

第十七章 : 网络系统

第十八章 : 查找文件

第十九章 : 归档和备份

第二十章 : 正则表达式

第二十一章 : 文本处理

第二十二章 : 格式化输出

第二十三章 : 打印

第二十四章 : 编译程序

编写 Shell 脚本

第二十五章 : 编写第一个 Shell 脚本

第二十六章 : 启动一个项目

第二十七章 : 自顶向下设计

第二十八章 : 流程控制:if 分支结构

第二十九章 : 读取键盘输入

第三十章 : 流程控制:while/until 循环

第三十一章 : 疑难排解

第三十二章 : 流程控制:case 分支

第三十三章 : 位置参数

第三十四章 : 流程控制:for 循环

第三十五章 : 字符串和数字

第三十六章 : 数组

第三十七章 : 奇珍异宝

自己添加的琐碎内容

安装anaconda

[chenfy@entrobus32 anaconda]$ wget https://repo.anaconda.com/archive/Anaconda3-5.1.0-Linux-x86_64.sh
--2019-09-20 15:09:27--  https://repo.anaconda.com/archive/Anaconda3-5.1.0-Linux-x86_64.sh
正在解析主机 repo.anaconda.com (repo.anaconda.com)... 104.16.130.3, 104.16.131.3, 2606:4700::6810:8303, ...
正在连接 repo.anaconda.com (repo.anaconda.com)|104.16.130.3|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:577996269 (551M) [application/x-sh]
正在保存至: “Anaconda3-5.1.0-Linux-x86_64.sh”

100%[========================================================================================================================================================================>] 577,996,269 14.0MB/s 用时 6m 36s 

2019-09-20 15:16:04 (1.39 MB/s) - 已保存 “Anaconda3-5.1.0-Linux-x86_64.sh” [577996269/577996269])

[chenfy@entrobus32 anaconda]$ ls
Anaconda3-5.1.0-Linux-x86_64.sh
[chenfy@entrobus32 anaconda]$ bash Anaconda3-5.1.0-Linux-x86_64.sh
.
.
.

安装虚拟环境及虚拟环境安装包的命令是相同的。不同的是Xshell上不像本地主机一样有浏览器,其需要相应的设置,以使在本地的浏览器上可以打开jupyter页面。