Linux---持续更新

3.4.2 遍历目录

cd 命令

cd destination
单个参数 destination,用以指定你想切换到的目录名。

如果没有为 cd命令指定目标路径,则会切换到你的用户主目录。
destination 参数可以用两种方式表示:一种是绝对路径,另一种是相对路径。
 

1. 绝对路径

确切位置,相当于目录的全名。
起始:正斜线(/),表示根目录。:

[christine@localhost ~]$ cd /usr/bin
[christine@localhost bin]$

波浪号(~)。在切换到另一个目录之后,这个波浪号被 bin 替代了。

CLI 提示符正是用它来帮助你跟踪当前所在虚拟目录结构中的位置。

波浪号表明 shell 会话当前位于你的主目录中。离开主目录之后,提示符中的目录也会随之发生变化(如果提示符已经进行了相关配置的话)。
注意 如果你的 shell CLI 提示符中没有显示 shell 会话的当前位置,那是因为没有进行相关的配
置。如果希望修改 CLI 提示符,第 6 章会告诉你如何进行配置。


pwd 命令

当前工作目录。

[christine@localhost bin]$ pwd
/usr/bin
[christine@localhost bin]$
2. 相对路径

相对路径允许你指定一个基于当前位置的目标路径。
开头:目录名(如果你准备切换到当前工作目录下的某个目录的话)或是一个特殊字符
现在位于 home 目录中,要切换到 Documents 子目录,那么可以使用 cd 命令配合相对路径:

[christine@localhost ~]$ pwd
/home/christine
[christine@localhost ~]$ cd Documents
[christine@localhost Documents]$ pwd
/home/christine/Documents
[christine@localhost Documents]$

可以在任何包含子目录的目录中使用带有相对路径的 cd 命令,也可以使用特殊字符来表示相对目录位置。
有两个特殊字符可用于相对路径中:

单点号(.),表示当前目录;

(意义不大)

双点号(..),表示当前目录的父目录。

当在目录层级中移动时,双点号非常便利。

[christine@localhost Documents]$ pwd
/home/christine/Documents
[christine@localhost Documents]$ cd ../Downloads
[christine@localhost Downloads]$ pwd
/home/christine/Downloads
[christine@localhost Downloads]$

3.5 列出文件和目录

3.5.1 显示基本列表

ls 命令最基本的形式会显示当前目录下的文件和目录:

$ ls
Desktop Downloads my_script Public test_file
Documents Music Pictures Templates Videos
$

按字母排序的(按列而不是按行排序)。

如果使用的终端仿真器支持色彩显示,那么 ls 命令还可以用不同的颜色来区分不同类型的文件。LS_COLORS 环境变量(第 6 章会介绍环境变量)控制着这个特性。不同的 Linux 发行版会根据各自终端仿真器的能力来设置该环境变量。
如果没有安装彩色终端仿真器,

可以使用 

ls -F
$ ls -F
Desktop/ Downloads/ my_script* Public/ test_file
Documents/ Music/ Pictures/ Templates/ Videos/
$

-F 选项会在目录名之后添加正斜线(/),以方便用户在输出中分辨。

它还会在可执行文件(比如上面的 my_script 文件)之后添加星号(*),找出可在系统中运行的文件。隐藏文件显示不了

隐藏文件

通常是文件名以点号(.)开始的文件。
这些文件并不会在 ls 命令的默认输出中出现。
要想显示隐藏文件,可以使用

ls -a
$ ls -a
.             .bash_profile Desktop   .ICEauthority my_script   Templates
..            .bashrc       Documents .local        Pictures    test_file
.bash_history .cache        Downloads .mozilla      .pki        Videos
.bash_logout  .config       .esd_auth Music         Public
$

有 4 个以.bash 起始的文件。它们是 bashshell 环境所使用的隐藏文件,第 6 章会详述。

ls -R

-R 是 ls 命令的另一个选项,称作递归选项,可以列出当前目录所包含的子目录中的文件。
如果子目录数量众多,则输出结果会很长。

2种方法输入:ls –F –R。可以将其合并:ls –FR。

3.5.2 显示长列表

ls -l

提供目录中各个文件的详细信息:

$ ls -l
total 8
drwxr-xr-x. 2 christine christine 6 Feb 20 14:23 Desktop
drwxr-xr-x. 2 christine christine 6 Feb 20 14:23 Documents
drwxr-xr-x. 2 christine christine 6 Feb 20 14:23 Downloads
drwxr-xr-x. 2 christine christine 28 Feb 29 15:42 Music
-rwxrw-r--. 1 christine christine 74 Feb 29 15:49 my_script
drwxr-xr-x. 2 christine christine 6 Feb 20 14:23 Pictures
drwxr-xr-x. 2 christine christine 6 Feb 20 14:23 Public
drwxr-xr-x. 2 christine christine 6 Feb 20 14:23 Templates
-rw-rw-r--. 1 christine christine 74 Feb 29 15:50 test_file
drwxr-xr-x. 2 christine christine 6 Feb 20 14:23 Videos
$

在长列表格式输出中,每一行会列出一个文件或目录。除了文件名,输出中还包括其他有用
信息。输出的第一行显示了为该目录中的文件所分配的总块数(8)。此后的每一行都包含了关于
文件(或目录)的下列信息。
 文件类型,比如目录(d)、文件(-)、链接文件(l)、字符设备(c)或块设备(b)
 文件的权限(参见第 7 章)
 文件的硬链接数(参见 3.6.4 节)
 文件属主
 文件属组
 文件大小(以字节为单位)
 文件的上次修改时间
 文件名或目录名
如果想查看单个文件的长列表,那么只需在 ls -l 命令之后跟上该文件名即可。但如果想查看目录的相关信息,而非目录所包含的内容,则除了-l 选项之外,还得添加-d 选项,即 ls -ld Directory-Name。
在进行文件管理时,ls 命令的很多选项能派上用场。如果在 shell 提示符中输入 man ls,可用来修改 ls 命令输出的选项足足有好几页。
别忘了可以将多个选项合并。你经常会发现有些参数组合不仅能够显示所需的内容,而且还容易记忆,比如 ls –alF。

3.5.3 过滤输出列表

格式:ls -l my_script

过滤后显示哪些文件或目录。ls 命令只会显示my_script的信息

$ ls
Desktop   Downloads fell full  my_file   my_script Public    test_file
Documents fall      fill Music my_scrapt Pictures  Templates Videos
$ ls -l my_script
-rwxrw-r--. 1 christine christine 74 Feb 29 16:12 my_script

有时你可能不知道要找的那个文件的确切名称。

ls 命令也能识别标准通配符(wildcard),并在过滤器中用其来进行
模式匹配:
 问号(?)代表任意单个字符;
 星号(*)代表零个或多个字符。
问号可以代表过滤器字符串中任意位置的单个字符。例如:

ls -l my_scr?pt
$ ls -l my_scr?pt
-rw-rw-r--. 1 christine christine 0 Feb 29 16:12 my_scrapt
-rwxrw-r--. 1 christine christine 74 Feb 29 16:12 my_script
$
ls -l my*
$ ls -l my*
-rw-rw-r--. 1 christine christine 0 Feb 29 16:12 my_file
-rw-rw-r--. 1 christine christine 0 Feb 29 16:12 my_scrapt
-rwxrw-r--. 1 christine christine 74 Feb 29 16:12 my_script
$
通配符匹配(globbing)

是指使用通配符进行模式匹配的过程。通配符正式的名称叫作元字符通配符(metacharacter wildcard)。除了星号和问号,还有更多的元字符通配符可做文件匹配之用。

方括号:ls -l my_scr[ay]pt
$ touch my_scrypt
$ ls -l my_scr[ay]pt
-rw-rw-r--. 1 christine christine 0 Feb 29 16:12 my_scrapt
-rw-rw-r--. 1 christine christine 0 Feb 29 16:18 my_scrypt
$

使用的是方括号以及在该位置上可能出现的两种字符:a 或 y。方括号代表单个字符位置并给出了该位置上的多个可能的选择。

指定字符范围,比如字母范围[a–i]:

$ ls f*ll
fall fell fill full
$ ls f[a-i]ll
fall fell fill
$

惊叹号(!):ls -l f[!a]ll

将不需要的内容排除在外:

$ ls -l f[!a]ll
-rw-rw-r--. 1 christine christine 0 Feb 29 16:12 fell
-rw-rw-r--. 1 christine christine 0 Feb 29 16:12 fill
-rw-rw-r--. 1 christine christine 0 Feb 29 16:12 full
$

搜索文件时,通配符匹配是一个功能强大的特性。它也可以用于 ls 以外的其他 shell 命令。

3.6 处理文件

3.6.1 创建文件

创建空文件。有时应用程序希望在执行写入操作之前日志文件就已经存在。使用 touch 命令

touch 创建空文件:
$ touch test_one
$ ls -l test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:24 test_one
$

touch 命令会创建好指定的文件并将你的用户名作为该文件的属主。

新文件的大小为 0,因为 touch 命令只是创建了一个空文件。
touch 命令还可用来改变文件的修改时间。该操作不会改变文件内容:

$ ls -l test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:24 test_one
$ touch test_one
$ ls -l test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:26 test_one
$

test_one 的修改时间从原先的 17:24 被更新为 17:26。
 

3.6.2 复制文件

cp 命令
格式1:cp source destination。

当参数 source 和 destination 都是文件名时,cp 命令会将源文件复制成一个新的目标文件,并以 destination 命名。新文件在形式上就像全新的文件一样,有新的修改时间:

$ ls -l test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:24 test_one
$ touch test_one
$ ls -l test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:26 test_one
$

新文件 test_two 和文件 test_one 的修改时间并不一样。如果目标文件已经存在,则 cp 命令
可能并不会提醒你这一点。最好加上-i 选项,强制 shell 询问是否需要覆盖已有文件:

格式2:cp -i test_one test_two
$ ls -l test_one test_two
-rw-rw-r--. 1 christine christine 0 Feb 29 17:26 test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:46 test_two
$
$ cp -i test_one test_two
cp: overwrite 'test_two'? n
$

n:停止

y:继续

格式3:cp -i test_one /home/christine/Documents/

将文件复制到现有目录中(绝对路径

$ cp -i test_one /home/christine/Documents/
$
$ ls -l /home/christine/Documents/
total 0
-rw-rw-r--. 1 christine christine 0 Feb 29 17:48 test_one
$

tips:先前的例子在目标目录名尾部加上了一个正斜线(/)。这表明 Documents 是一个目录而
非文件。这有助于表明目的,而且在复制单个文件时非常重要。如果没有使用正斜线,同时子目录/home/christine/Documents 又不存在,就会产生麻烦。在这种情况下,试图将一个文件复制到 Documents 子目录反而会创建名为 Documents 的文件,更是连错误消息都不会有。因此,记得在目标目录名尾部加上正斜线。

也可以很方便地使用相对路径

$ cp -i test_two Documents/
$ ls -l Documents/
total 0
-rw-rw-r--. 1 christine christine 0 Feb 29 17:48 test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:51 test_two
$

一些特殊符号可以用在相对路径中,其中的单点号(.)就很适合用于 cp 命令。
记住,单点号表示当前工作目录。如果需要将源文件名很长的文件复制到当前工作目录中,那么单点号能省不少事:

格式4:cp /etc/NetworkManager/NetworkManager.conf .
$ cp /etc/NetworkManager/NetworkManager.conf .
$ ls *.conf
NetworkManager.conf
$
格式5:cp -R Documents/ NewDocuments/  (递归地复制整个目录)
$ ls -l Documents/
total 0
-rw-rw-r--. 1 christine christine 0 Feb 29 17:48 test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:51 test_two
$ cp -R Documents/ NewDocuments/
$ ls -l NewDocuments/
total 0
-rw-rw-r--. 1 christine christine 0 Feb 29 17:55 test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:55 test_two
$

3.6.3 使用命令行补全

制表键

这正是命令行补全(制表键)挺身而出的时候。制表键补全允许你在输入文件名或目录名的时候,按一下制表键,让 shell 帮你将内容补充完整:

$ touch really_ridiculously_long_file_name
$
$ cp really_ridiculously_long_file_name NewDocuments/
$ ls NewDocuments/
my_file my_script really_ridiculously_long_file_name test_two
my_scrapt my_scrypt test_one
$

输入了命令 cp really,然后按制表键,剩下的文件名自动补充完整了

有另一个文件名也是以 really 开头,那么就算按了制表键,也无法自动补全。这时候你会听到嘟的一声。要是再按一下制表键,shell 会将所有以 really 开头的文件名都列出来

3.6.4 链接文件

链接文件是 Linux 文件系统的一个优势。如果需要在系统中维护同一文件的两个或多个副本,可以使用单个物理副本加多个虚拟副本(链接)的方法代替创建多个物理副本。链接是目录中指向文件真实位置的占位符。在 Linux 中有两种类型的文件链接。

符号链接(也称为软链接)ln -s test_file slink_test_file

是一个实实在在的文件,该文件指向存放在虚拟目录结构中某个地方的另一个文件。这两个以符号方式链接在一起的文件彼此的内容并不相同。原始文件必须事先存在。

$ ls -l test_file
-rw-rw-r--. 1 christine christine 74 Feb 29 15:50 test_file
$
$ ln -s test_file slink_test_file
$
$ ls -l *test_file
lrwxrwxrwx. 1 christine christine 9 Mar 4 09:46 slink_test_file -> test_file
-rw-rw-r--. 1 christine christine 74 Feb 29 15:50 test_file
$

①->  符号表明该文件是链接到文件 test_file 的一个符号链接。

②文件大小。符号链接文件 slink_test_file 只有 9个字节,而 test_file 有 74 个字节。

 slink_test_file 仅仅只是指向 test_file 而已。它们的内容并不相同,是两个完全不同的文件。

查看 inode 编号  ls -i *test_file

证明链接文件是一个独立文件的方法。文件或目录的 inode 编号是内核分配给文件系统中的每一个对象的唯一标识。

$ ls -i *test_file
1415020 slink_test_file 1415523 test_file
$

test_file 文件的 inode 编号是 1415523,而 slink_test_file 的 inode 编号则是 1415020。
所以说两者是不同的文件。


硬链接 ln test_one hlink_test_one

创建的是一个独立的虚拟文件,其中包含了原始文件的信息以及位置。本质是同一个文件

原始文件也必须事先存在,只不过这次使用 ln 命令时不需要再加入额外的选项了(-s):

$ ls -l *test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:26 test_one
$
$ ln test_one hlink_test_one
$
$ ls -li *test_one
1415016 -rw-rw-r--. 2 christine christine 0 Feb 29 17:26 hlink_test_one
1415016 -rw-rw-r--. 2 christine christine 0 Feb 29 17:26 test_one
$

共享同一个 inode 编号。文件大小也一模一样。(用来文件重名)

注意 只能对处于同一存储设备的文件创建硬链接。要想在位于不同存储设备的文件之间创建
链接,只能使用符号链接。

3.6.5 文件重命名

在 Linux 中,重命名文件称为移动(moving),理解本质重命名相当于移动了。

mv 命令
重名:
$ ls -li f?ll
1414976 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 fall
1415004 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 fell
1415005 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 fill
1415011 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 full
$
$ mv fall fzll
$
$ ls -li f?ll
1415004 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 fell
1415005 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 fill
1415011 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 full
1414976 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 fzll
$

移动文件会将文件名从 fall 更改为 fzll,但 inode 编号和时间戳保持不变。这是因为
mv 只影响文件名

移动:

将文件 fzll 从/home/christine 移动到了/home/christine/NewDocuments

$ ls -li /home/christine/fzll
1414976 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 /home/christine/fzll
$
$ ls -li /home/christine/NewDocuments/fzll
ls: cannot access '/home/christine/NewDocuments/fzll': No such file or directory
$
$ mv /home/christine/fzll /home/christine/NewDocuments/
$
$ ls -li /home/christine/NewDocuments/fzll
1414976 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12
/home/christine/NewDocuments/fzll
$
$ ls -li /home/christine/fzll
ls: cannot access '/home/christine/fzll': No such file or directory
$

与 cp 命令类似,可以在 mv 命令中使用-i 选项。这样在 mv 试图覆盖已有的文件时会发出询问。

在移动文件的同时进行重命名:
$ ls -li NewDocuments/fzll
1414976 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 NewDocuments/fzll
$
$ mv /home/christine/NewDocuments/fzll /home/christine/fall
$
$ ls -li /home/christine/fall
1414976 -rw-rw-r--. 1 christine christine 0 Feb 29 16:12 /home/christine/fall
$
$ ls -li /home/christine/NewDocuments/fzll
ls: cannot access '/home/christine/NewDocuments/fzll': No such file or directory
$
移动整个目录及其内容:
$ ls NewDocuments
my_file   my_script really_ridiculously_long_file_name test_two
my_scrapt my_scrypt test_one
$
$ mv NewDocuments OldDocuments
$
$ ls NewDocuments
ls: cannot access 'NewDocuments': No such file or directory
$
$ ls OldDocuments
my_file   my_script really_ridiculously_long_file_name test_two
my_scrapt my_scrypt test_one
$

3.6.6 删除文件

删除(deleting)叫作移除(removing)。bash shell 中用于删除文件的命令是 rm。

rm 格式:rm -i fall
$rm -i fall
rm: remove regular empty file 'fall'? y
$ ls fall
ls: cannot access 'fall': No such file or directory
$

-i 选项会询问你是否真的要删除该文件。shell 没有回收站或者垃圾箱这样的东西,
文件一旦被删除,就再也找不回来了。所以在使用 rm 命令时,要养成总是加入-i 选项的好习惯。
也可以使用通配符元字符删除一组文件。别忘了使用-i 选项:

$ rm -i f?ll
rm: remove regular empty file 'fell'? y
rm: remove regular empty file 'fill'? y
rm: remove regular empty file 'full'? y
$ ls f?ll
ls: cannot access 'f?ll': No such file or directory
$

rm 命令的另一个特性是,如果你要删除很多文件,又不想被命令提示干扰,可以用-f 选项来强制删除。

3.7 管理目录

有些命令(比如 cp 命令)对文件和目录都有效,有些命令则只对目录有效。
使用特定命令来创建新目录以及删除。

3.7.1 创建目录

mkdir 命令:mkdir New_Dir
$ mkdir New_Dir
$ ls -ld New_Dir
drwxrwxr-x. 2 christine christine 6 Mar 6 14:40 New_Dir
$

长列表输出中,目录以 d 开头。这表示New_Dir 并不是文件,而是一个目录。

批量地创建目录和子目录。

mkdir -p New_Dir/SubDir/UnderDir
$ mkdir -p New_Dir/SubDir/UnderDir
$ ls -R New_Dir
New_Dir:
SubDir

New_Dir/SubDir:
UnderDir

New_Dir/SubDir/UnderDir:
$

mkdir 命令的-p 选项可以根据需要创建缺失的父目录。父目录是包含目录树中下一级目录的目录。

3.7.2 删除目录

删除目录是件棘手的事情,这是有原因的。删除目录时,很有可能会出岔子。shell 会尽可能
地防止我们出大错。

rmdir命令:mkdir Wrong_Dir
$ mkdir Wrong_Dir
$ touch Wrong_Dir/newfile
$
$ rmdir Wrong_Dir/
rmdir: failed to remove 'Wrong_Dir/': Directory not empty
$

在默认情况下,rmdir 命令只删除空目录。因为我们在 Wrong _ Dir 目录下创建了一个文件
newfile,所以 rmdir 命令会拒绝删除该目录。
要想删除这个目录,需要先把目录中的文件删掉,然后才能在空目录中使用 rmdir 命令:

$ rm -i Wrong_Dir/newfile
rm: remove regular empty file 'Wrong_Dir/newfile'? y
$ rmdir Wrong_Dir/
$ ls Wrong_Dir
ls: cannot access 'Wrong_Dir': No such file or directory
$

rmdir 并没有-i 选项可以用来询问是否要删除目录。这也是为什么 rmdir 只能删除空目录

在整个非空目录中使用 rm 命令。

-r 选项使得 rm 命令可以向下进入(descend into)目录,删除其中的文件,再删除目录本身:

$ mkdir TestDir
$ touch TestDir/fileone TestDir/filetwo
$ ls TestDir
fileone filetwo
$ rm -ir TestDir
rm: descend into directory 'TestDir'? y
rm: remove regular empty file 'TestDir/fileone'? y
rm: remove regular empty file 'TestDir/filetwo'? y
rm: remove directory 'TestDir'? y
$ ls TestDir
ls: cannot access 'TestDir': No such file or directory
$

这种方法同样可以向下进入多个子目录,当需要删除大量的目录和文件时,这一点尤为管用:

$ touch New_Dir/testfile
$ ls -FR New_Dir
New_Dir:
SubDir/ testfile

New_Dir/SubDir:
UnderDir/

New_Dir/SubDir/UnderDir:
$
$ rm -iR New_Dir
rm: descend into directory 'New_Dir'? y
rm: descend into directory 'New_Dir/SubDir'? y
rm: remove directory 'New_Dir/SubDir/UnderDir'? y
rm: remove directory 'New_Dir/SubDir'? y
rm: remove regular empty file 'New_Dir/testfile'? y
rm: remove directory 'New_Dir'? y
$


虽然这种方法可行,但不太好用。你依然要确认每个文件是否要被删除。如果该目录中有很
多个文件和子目录,则会非常琐碎。
注意 对于 rm 命令,-r 选项和-R 选项的效果是一样的,都可以递归地删除目录中的文件。
shell 命令很少会对相同的功能使用大小写不同的选项。

rm -rf 命令

一口气删除目录树的最终解决方案是使用 rm -rf 命令。该命令不声不响,能够直接删除指定目录及其所有内容。当然,这肯定是一个非常危险的命令。


3.8 查看文件内容

有几个命令可以直接查看文件的内容,不需要调用其他文本编辑器(第 10 章)。
现在展示其中部分命令。

3.8.1 查看文件类型

在显示文件内容之前,应该先了解文件类型。
file 命令是一个方便的小工具,能够探测文件的内部并

判断文件类型:
$ file .bashrc
.bashrc: ASCII text
$

上例中是一个 ASCII text 类型的文件。file 命令不仅能够确定文件中包含的是文本信息,
还能确定该文本文件的字符编码是 ASCII。
下面中的文件就是一个目录。

区分目录的方法:
$ file Documents
Documents/: directory
$
符号链接文件
$ file slink_test_file
slink_test_file: symbolic link to test_file
$
对于脚本文件的返回结果

尽管这个文件是 ASCII text,但因为是脚本文件,所以可以在系统中执行(运行):

$ file my_script
my_script: Bourne-Again shell script, ASCII text executable
$
二进制可执行程序

file 命令能够确定该程序编译时所面向的平台以及需要何种类型的库。如果有从未知来源处获得的二进制文件,那么这会是一个非常有用的特性:

$ file /usr/bin/ls
/usr/bin/ls: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
for GNU/Linux 3.2.0,[...]
$

3.8.2 查看整个文件

有一个很大的文本文件,看看里面是什么。Linux 有 3 个不同的命令可以完成这个任务。

1. cat 命令

cat 命令是显示文本文件中所有数据的得力工具:

cat test_file
$ cat test_file
Hello World
Hello World again
Hello World a third time
How are you World?
$

还有一些可以和 cat 命令配合使用的选项,

cat -n test_file

-n 选项会给所有的行加上行号

$ cat -n test_file
1 Hello World
2 Hello World again
3 Hello World a third time
4 How are you World?
5
$
cat -b test_file

这个功能在检查脚本时会很方便。如果只想给有文本的行加上行号,可以用-b 选项:

$ cat -b test_file
1 Hello World
2 Hello World again
3 Hello World a third time
4 How are you World?
$

对大文件来说,cat 命令多少有些烦人。文件内容会在屏幕上一闪而过。好在有一种简单的
方法可以解决这个问题。

2. more 命令

cat 命令的主要缺点是其开始运行之后你无法控制后续操作。为了解决这个问题,开发人员编写了 more 命令。more 命令会显示文本文件的内容,但会在显示每页数据之后暂停下来
more 命令是一个分页工具。当你使用 man 命令时,分页工具会显示指定的bash 手册页。和在手册页中浏览方法一样

空格键向前翻页,或是使用 Enter 键逐行向前查看。

结束之后,按 q 键退出。
more 命令只支持文本文件中基本的移动。

如果想要更多的高级特性,可以试试 less 命令。

3. less 命令

尽管名字不如 more 命令那样高级,实为 more 命令的升级版本。less 命令提供了多个非常实用的特性,能够实现在文本文件中前后翻动,还有一些高级搜索功能。
less 命令还可以在完成整个文件的读取之前显示文件的内容。cat 命令和 more 命令则无法做到这一点。
less 命令的操作和 more 命令基本一样,一次显示一屏的文件文本。除了支持和 more 命令相同的命令集,它还包括更多的选项。

输入 man less 可以查看 less 命令的所有可用选项,也可以使用相同的方法查看 more命令的各种选项。

less 命令能够识别上下箭头键以及上下翻页键(假设你的终端配置正确)。在查看文件内容时,这赋予了你全面的控制权。

3.8.3 查看部分文件

1. tail 命令(末尾)

默认情况下,它会显示文件的末尾 10 行。非默认

tail -n log_file(末尾n行)

使用 cat 命令显示该文件的全部内容,再用tail

$ cat log_file
line1
line2
line3
line4
Hello World - line5
line6
line7
line8
line9
Hello again World - line10
line11
line12
line13
line14
Last Line - line15
$ tail log_file
line6
line7
line8
line9
Hello again World - line10
line11
line12
line13
line14
Last Line - line15$
2. head 命令(头部)

在默认情况下,它会显示文件前 10行的文本:

$ head log_file
line1
line2
line3
line4
Hello World - line5
line6
line7
line8
line9
Hello again World - line10
$

与 tail 命令类似,head 命令也支持-n 选项,以便指定想要显示的内容。这两个命令也允许简单地在连字符后面直接输入想要显示的行数:

$ head -3 log_file
line1
line2
line3
$

文件的开头部分通常不会改变,因此 head 命令并没有 tail 命令那样的-f 选项。head 命令是一种查看文件起始部分内容的便捷方法。 

bash shell 命令

4.1 监测程序

跟踪运行在系统中的程序,有几个命令行工具可以提供帮助。介绍在 Linux 系统中管理程序的一些基础工具。

4.1.1 探查进程

程序:进程(process)。

要想监测这些进程,必须熟悉 ps 命令的用法。

ps 命令(时间点的具体信息)

但是有稳健性和复杂性:数不清的选项或许让 ps 命令成了最难掌握的命令。大多数系统管理员在找到一组能够提供所需信息的选项之后,会一直坚持只使用这些选项。
在默认情况下,ps 命令并没有提供太多的信息:

$ ps
  PID TTY TIME CMD

  3081 pts/0  00:00:00 bash
  3209 pts/0  00:00:00 ps
$

ps 命令默认只显示运行在当前终端中属于当前用户的那些进程。

当前只有 bash shell 在运行(记住,shell 只是运行在系统中的另一个程序而已),当然 ps命令本身也在运行。
ps 命令的基本输出显示了程序的进程 ID(process ID,PID)、进程运行在哪个终端(TTY)
及其占用的 CPU 时间,进程名

Linux 系统中使用的 GNU ps 命令支持以下 3 种类型的命令行选项:
 Unix 风格选项,选项前加单连字符;
 BSD 风格选项,选项前不加连字符;
 GNU 长选项,选项前加双连字符。
下面将进一步解析这 3 种选项类型,并举例演示其用法。

1. Unix 风格选项
Unix 风格选项源自贝尔实验室开发的 AT&T Unix 系统中的 ps 命令

-A                 显示所有进程
-N                 显示与指定参数不符的所有进程
-a                 显示除控制进程(session leader )和无终端进程外的所有进程
-d                 显示除控制进程外的所有进程
-e                 显示所有进程
-C cmdlist     显示包含在 cmdlist 列表中的进程
-G grplist     显示组 ID 在 grplist 列表中的进程
-U userlist    显示属主的用户 ID 在 userlist 列表中的进程
-g grplist       显示会话或组 ID 在 grplist 列表中的进程
-p pidlist       显示 PID 在 pidlist 列表中的进程
-s sesslist    显示会话 ID 在 sesslist 列表中的进程
-t ttylist         显示终端 ID 在 ttylist 列表中的进程
-u userlist     显示有效用户 ID 在 userlist 列表中的进程
-F                 显示更多的额外输出(相对-f 选项而言)
-O format      显示默认的输出列以及 format 列表指定的特定列

-M                 显示进程的安全信息
-c                 显示进程的额外的调度器信息
-f                  显示完整格式的输出
-j                  显示作业信息
-l                  显示长列表
-o format         仅显示由 format 指定的列
-y                  不显示进程标志
-Z                 显示安全上下文信息
-H                 以层级格式显示进程(显示父进程)
-n namelist   定义要在 WCHAN 输出列中显示的值
-w                采用宽输出格式,不限宽度显示
-L                 显示进程中的线程
-V                 显示 ps 命令的版本号

查看系统中运行的所有进程,可以使用-ef 选项组合(ps 命令允许像这样把选项合并在一起)。

$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 12:14 ? 00:00:02 /sbin/init splash
root 2 0 0 12:14 ? 00:00:00 [kthreadd]
root 3 2 0 12:14 ? 00:00:00 [rcu_gp]
root 4 2 0 12:14 ? 00:00:00 [rcu_par_gp]
root 5 2 0 12:14 ? 00:00:00
[kworker/0:0-events]
root 6 2 0 12:14 ? 00:00:00
[kworker/0:0H-kblockd]
root 7 2 0 12:14 ? 00:00:00
[kworker/0:1-events]
...
rich 2209 1438 0 12:17 ? 00:00:01 /usr/libexec/
gnome-terminal-
rich 2221 2209 0 12:17 pts/0 00:00:00 bash
rich 2325 2221 0 12:20 pts/0 00:00:00 ps -ef
$

Linux 系统中运行着大量的进程。
这个例子用了两个选项:

-e 选项指定显示系统中运行的所有进程;-f 选项则扩充输出内容以显示一些有用的信息列。
 UID:启动该进程的用户。
 PID:进程 ID。

 PPID:父进程的 PID(如果该进程是由另一个进程启动的)。
 C:进程生命期中的 CPU 利用率。
 STIME:进程启动时的系统时间。
 TTY:进程是从哪个终端设备启动的。
 TIME:运行进程的累计 CPU 时间。
 CMD:启动的程序名称。
由此得到了合理的信息量,这也正是很多系统管理员乐于看到的。
还想获得更多的信息,可以使用-l 选项,产生长格式输出。

$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 500 3081 3080 0 80 0 - 1173 do_wai pts/0 00:00:00
bash
0 R 500 4463 3081 1 80 0 - 1116 - pts/0 00:00:00 ps
$

 F:内核分配给进程的系统标志。
 S:进程的状态(O 代表正在运行;S 代表在休眠;R 代表可运行,正等待运行;Z 代表僵化,已终止但找不到其父进程;T 代表停止)。
 PRI:进程的优先级(数字越大,优先级越低)。
 NI:谦让度(nice),用于决定优先级。
 ADDR:进程的内存地址。
 SZ:进程被换出时所需交换空间的大致大小。
 WCHAN:进程休眠的内核函数地址。

2. BSD 风格选项

BSD 版的 ps 命令选项如下

T         显示与当前终端关联的所有进程
a         显示与任意终端关联的所有进程
g         显示包括控制进程在内的所有进程
r         仅显示运行中的进程
x         显示所有进程,包括未分配任何终端的进程
U         userlist 显示属于 userlist 列表中某个用户 ID 所有的进程
p         pidlist 显示 PID 在 pidlist 列表中的进程

t         ttylist 显示与 ttylist 列表中的某个终端关联的进程
O         format 除了标准列,还输出由 format 指定的列
X         以寄存器格式显示数据
Z         在输出中包含安全信息
j         显示作业信息
l         采用长格式显示
o format         仅显示由 format 指定的列
s         采用信号格式显示
u         采用基于用户的格式显示
v         采用虚拟内存格式显示
N namelist         定义要在 WCHAN 输出列中显示的值
O order             定义信息列的显示顺序
S         将子进程的数值统计信息(比如 CPU 和内存使用情况)汇总到父进程中
c         显示真实的命令名称(用以启动该进程的程序名称)
e         显示命令使用的环境变量
f         用层级格式来显示进程,显示哪些进程启动了哪些进程
h         不显示头信息
k sort          指定用于排序输出的列
n         使用数值显示用户 ID、组 ID 以及 WCHAN 信息
w         为更宽的终端屏幕生成宽输出
H         将线程显示为进程
m         在进程之后显示线程
L         列出所有的格式说明符
V         显示 ps 命令的版本

在使用 BSD 风格的选项时,ps 命令会自动改变输出以模仿 BSD 格式。
下面是使用 l 选项的输出。

$ ps l
$ ps l
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY
TIME COMMAND
4 1000 1491 1415 20 0 163992 6580 poll_s Ssl+ tty2
0:00 /usr/li
4 1000 1496 1491 20 0 225176 58712 ep_pol Sl+ tty2
0:05 /usr/li
0 1000 1538 1491 20 0 192844 15768 poll_s Sl+ tty2
0:00 /usr/li
0 1000 2221 2209 20 0 10608 4740 do_wai Ss
pts/0 0:00 bash
0 1000 2410 2221 20 0 11396 1156 - R+
pts/0 0:00 ps l
$

上述很多输出列跟使用 Unix 风格选项时是一样的,但还是有一些不同之处。
 VSZ:进程占用的虚拟内存大小(以 KB 为单位)。
 RSS:进程在未被交换出时占用的物理内存大小。
 STAT:代表当前进程状态的多字符状态码。
        很多系统管理员喜欢 BSD 风格的 l 选项,因为能输出更详细的进程状态码(STAT 列)。多
字符状态码能比 Unix 风格输出的单字符状态码更清楚地表明进程的当前状态。
        第一个字符采用了与 Unix 风格的 S 输出列相同的值,表明进程是在休眠、运行还是等待。
第二个字符进一步说明了进程的状态。
 <:该进程以高优先级运行。
 N:该进程以低优先级运行。
 L:该进程有锁定在内存中的页面。
 s:该进程是控制进程。
 l:该进程拥有多线程。
 +:该进程在前台运行。
从先前展示的简单例子中可以看出,bash 命令处于休眠状态,但同时它也是一个控制进程
(会话中的主进程),而 ps 命令则运行在系统前台。

3. GNU 长选项

GNU 开发人员在经过改进的新 ps 命令中加入了另外一些选项,其中一些 GNU 长选项复制
了现有的 Unix 或 BSD 风格选项的效果,而另外一些则提供了新功能。

--deselect                 显示除命令行中列出的进程之外的其他进程
--Group grplist         显示组 ID 在 grplist 列表中的进程
--User userlist         显示用户 ID 在 userlist 列表中的进程
--group grplist         显示有效组 ID 在 grplist 列表中的进程
--user userlist         显示有效用户 ID 在 userlist 列表中的进程
--pid pidlist         显示 pid 在 pidlist 列表中的进程
--ppid pidlist         显示父 pid 在 pidlist 列表中的进程
--sid sidlist         显示会话 ID 在 sidlist 列表中的进程

--tty ttylist                 显示终端设备 ID 在 ttylist 列表中的进程
--format format         仅显示由 format 指定的列
--context                 显示额外的安全信息
--cols n                 将屏幕宽度设置为 n 列
--columns n         将屏幕宽度设置为 n 列
--cumulative         包含已停止的子进程的信息
--forest                 用层级结构显示出进程和父进程之间的关系
--headers                 在每页输出中都显示列名
--no-headers         不显示列名
--lines n                 将屏幕高度设置为 n 行
--rows n                 将屏幕高度设置为 n 行
--sort order         指定用于排序输出的列
--width n                 将屏幕宽度设置为 n 列
--help                 显示帮助信息
--info                 显示调试信息
--version                 显示 ps 命令的版本号

可以混用 GNU 长选项和 Unix 或 BSD 风格的选项来定制输出。作为一个 GNU 长选项,
--forest 选项能够使用 ASCII 字符来绘制可爱的图表以显示进程的层级信息:

1981 ? 00:00:00 sshd
3078 ? 00:00:00 \_ sshd
3080 ? 00:00:00 \_ sshd
3081 pts/0 00:00:00 \_ bash
16676 pts/0 00:00:00 \_ ps

这种格式可以轻而易举地跟踪子进程和父进程。

4.1.2 实时监测进程

top 命令(实时)

ps 命令(时间点)与 ps 命令相似,top 命令也可以显示进程信息,但采用的是实时方式。

详细列表,有些列跟 ps 命令的输出类似。
 PID:进程的 PID。
 USER:进程属主的用户名。
 PR:进程的优先级。 

 NI:进程的谦让度。
 VIRT:进程占用的虚拟内存总量。
 RES:进程占用的物理内存总量。
 SHR:进程和其他进程共享的内存总量。
 S:进程的状态(D 代表可中断的休眠,R 代表运行,S 代表休眠,T 代表被跟踪或停止,
Z 代表僵化 )。
 %CPU:进程使用的 CPU 时间比例。
 %MEM:进程使用的可用物理内存比例。
 TIME+:自进程启动到目前为止所占用的 CPU 时间总量。
 COMMAND:进程所对应的命令行名称,也就是启动的程序名。
        在默认情况下,top 命令在启动时会按照%CPU 值来对进程进行排序,你可以在 top 命令
运行时使用多种交互式命令来重新排序。每个交互式命令都是单字符,在 top 命令运行时键入
可改变 top 的行为。

键入 f 允许你选择用于对输出进行排序的字段,键入 d 允许你修改轮询间隔(polling interval),键入 q 可以退出 top。用户对 top 命令输出有很大的控制权。利用该工具,能找出占用系统大量资源的进程。

4.1.3 结束进程

有时候,进程会被挂起,此时只需动动手让进程重新运行或结束就行了。

有时候,进程会霸占着 CPU 且拒绝让出。

在这两种情景下,都需要能够控制进程的命令。

Linux 沿用了 Unix 的进程间通信方法。在 Linux 中,进程之间通过信号来通信。进程的信号是预定义好的一个消息,进程能识别该消息并决定忽略还是做出反应。进程如何处理信号是由开发人员通过编程来决定的。大多数编写完善的应用程序能接收和处理标准 Unix 进程信号。这些信号如表下:

信 号                        名 称                        描 述
1                                 HUP                         挂起
2                                 INT                         中断
3                                 QUIT                        结束运行
9                                 KILL                         无条件终止
11                                 SEGV                         段错误
15                                 TERM                         尽可能终止
17                                 STOP                         无条件停止运行,但不终止
18                                TSTP                         停止或暂停,但继续在后台运行
19                                 CONT                         在 STOP 或 TSTP 之后恢复执行

可以向运行中的进程发出进程信号:kill 和 pkill。

1. kill 命令

kill 命令可以通过 PID 向进程发送信号。在默认情况下,kill 命令会向命令行中列出的所有 PID 发送 TERM 信号。只能使用进程的 PID 而不能使用其对应的程序名
要发送进程信号,必须是进程的属主root 用户:

$ kill 3940
-bash: kill: (3940) - Operation not permitted
$

TERM 信号会告诉进程终止运行。但不服管教的进程通常会忽略这个请求。如果要强制终止,
则-s 选项支持指定其他信号(用信号名或信号值)。
从下例可以看到,kill 命令不会有任何输出:

# kill -s HUP 3940
#

要检查 kill 命令是否生效,可以再次执行 ps 命令或 top 命令,看看那些进程是否已经停止运行。


2. pkill 命令

pkill 命令可以使用程序名代替 PID 来终止进程,这就方便多了。除此之外,pkill 命令也允许使用通配符,当系统出问题时,这是一个非常有用的工具:

# pkill http*
#

该命令将“杀死”所有名称以 http 起始的进程,比如 Apahce Web Server 的 httpd 服务。
tips: 以 root 身份使用 pkill 命令时要格外小心。命令中的通配符很容易意外地将系统的重要进程终止。这可能会导致文件系统损坏。

4.2 监测磁盘空间

有几个命令行命令可以帮助你管理 Linux 系统中的存储设备。


4.2.1 挂载存储设备

Linux 文件系统会将所有的磁盘都并入单个虚拟目录。

在使用新的存储设备之前,需要将其放在虚拟目录中。这项工作称为挂载(mounting)。
在今天的图形化桌面环境中,大多数 Linux 发行版能自动挂载特定类型的可移动存储设备。
所谓可移动存储设备(显然)指的是那种可以从 PC 中轻易移除的媒介,比如 DVD 和 U 盘。

如果你使用的发行版不支持自动挂载和卸载可移动存储设备,则只能手动操作了。

本节将介绍一些可以帮你管理可移动存储设备的 Linux 命令行命令。

1. mount 命令

用于挂载存储设备的命令。在默认情况下,mount 命令会输出当前系统已挂载的设备列表

但是,除了标准存储设备,较新版本的内核还会挂载大量用作管理目的的虚拟文件系统。这使得 mount 命令的默认输出非常杂乱。如果知道设备分区使用的文件系统类型,可以这样过滤输出。

$ mount -t ext4
/dev/sda5 on / type ext4 (rw,relatime,errors=remount-ro)
$ mount -t vfat
/dev/sda2 on /boot/efi type vfat
(rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso88591,
shortname=mixed,errors=remount-ro)
/dev/sdb1 on /media/rich/54A1-7D7D type vfat
(rw,nosuid,nodev,relatime,uid=1000,gid=1000,fmask=0022,dmask=0022,
codepage=437,
iocharset=iso8859-1,shortname=mixed,showexec,utf8,flush,
errors=remountro,uhelper=udisks2)
$

mount 命令提供了 4 部分信息。
 设备文件名
 设备在虚拟目录中的挂载点
 文件系统类型
 已挂载设备的访问状态
在上面例子的最后一行输出中,U 盘被 GNOME 桌面自动挂载到了挂载点/media/rich/54A1-
7D7D。vfat 文件系统类型说明它是在 Microsoft Windows PC 中格式化的。
要手动在虚拟目录中挂载设备,需要以 root 用户身份登录,或是以 root 用户身份运行 sudo
命令。下面是手动挂载设备的基本命令:

mount -t type device directory

其中,type 参数指定了磁盘格式化所使用的文件系统类型。Linux 可以识别多种文件系统类型。
如果与 Windows PC 共用移动存储设备,那么通常需要使用下列文件系统类型。
 vfat:Windows FAT32 文件系统,支持长文件名。
 ntfs:Windows NT 及后续操作系统中广泛使用的高级文件系统。
 exfat:专门为可移动存储设备优化的 Windows 文件系统。
 iso9660:标准 CD-ROM 和 DVD 文件系统。
大多数 U 盘会使用 vfat 文件系统格式化。如果需要挂载数据 CD 或 DVD,则必须使用 iso9660文件系统类型。
        后面两个参数指定了该存储设备的设备文件位置以及挂载点在虚拟目录中的位置。例如,手
动将 U 盘/dev/sdb1 挂载到/media/disk,可以使用下列命令:

mount -t vfat /dev/sdb1 /media/disk

一旦存储设备被挂载到虚拟目录,root 用户就拥有了对该设备的所有访问权限,而其他用户
的访问则会被限制。可以通过目录权限(参见第 7 章)指定用户对设备的访问权限。
        如果需要使用 mount 命令的一些高级特性,可以参见表 4-5 中列出的相关选项。
 mount 命令选项
选 项                                                 描 述
-a                                                         挂载/etc/fstab 文件中指定的所有文件系统
-f                                                         模拟挂载设备,但并不真正挂载
-F                                                         和-a 选项一起使用时,同时挂载所有文件系统
-v                                                         详细模式,显示挂载设备的每一步操作
-i                                                         不使用/sbin/mount.filesystem 下的任何文件系统协助文件
-l                                                         自动给 ext2、ext3、ext4 或 XFS 文件系统添加文件系统标签
-n                                                         挂载设备,但不在/etc/mtab 已挂载设备文件中注册
-p num                         进行加密挂载时从文件描述符 num 中获得口令
-s                                 忽略该文件系统不支持的挂载选项
-r                                 将设备挂载为只读
-w                                 将设备挂载为可读写(默认选项)
-L label                         将设备按指定的 label 挂载
-U uuid                         将设备按指定的 uuid 挂载
-O                                 和-a 选项一起使用,限制其所作用的文件系统
-o                                 给文件系统添加特定的选项


-o 选项允许在挂载文件系统时添加一系列以逗号分隔的额外选项。常用选项如下。
 ro:以只读形式挂载。
 rw:以读写形式挂载。
 user:允许普通用户挂载该文件系统。
 check=none:挂载文件系统时不执行完整性校验。
 loop:挂载文件。

2. umount 命令

移除可移动设备时,不能直接将设备拔下,应该先卸载。
提示: Linux 不允许直接弹出已挂载的 CD 或 DVD。如果在从光驱中移除 CD 或 DVD 时遇到麻烦,那么最大的可能是它还在虚拟目录中挂载着。应该先卸载,然后再尝试弹出。

卸载设备的命令是 umount(是的,你没看错,命令名中并没有字母“n”,不是“unmount”,这一点有时候很让人困惑)。umount 命令的格式非常简单:

umount [directory | device ]

umount 命令支持通过设备文件或者挂载点来指定要卸载的设备。如果有任何程序正在使用
设备上的文件,则系统将不允许卸载该设备。

# umount /home/rich/mnt
umount: /home/rich/mnt: device is busy
umount: /home/rich/mnt: device is busy
# cd /home/rich
# umount /home/rich/mnt
# ls -l mnt
total 0
#

       在本例中,因为命令行提示符仍然位于已挂载设备的文件系统中,所以 umount 命令无法卸
载该镜像文件。一旦命令提示符移出其镜像文件系统,umount 命令就能成功卸载镜像文件了

4.2.2 使用 df 命令

查看所有已挂载磁盘的使用情况:

$ df -t ext4 -t vfat
Filesystem     1K-blocks      Used         Available     Use%     Mounted on
/dev/sda5       19475088     7326256         11136508     40%     /
/dev/sda2         524272         4             524268     1%      /boot/efi
/dev/sdb1         983552     247264             736288    26%     /media/
rich/54A1-7D7D
$

df 命令会逐个显示已挂载的文件系统。与 mount 命令类似,df 命令会输出内核挂载的所有虚拟文件系统,因此可以使用-t 选项来指定文件系统类型,进而过滤输出结果。该命令的输出如下。
 设备文件位置
 包含多少以 1024 字节为单位的块
 使用了多少以 1024 字节为单位的块
 还有多少以 1024 字节为单位的块可用
 已用空间所占的百分比
 设备挂载点

df 命令的大部分选项你根本不会用到。

常用选项之一是-h,该选项会以人类易读(human-readable)的形式显示磁盘空间,通常用 M 来替代兆字节,用 G 来替代吉字节:

$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda5 19G 7.0G 11G 40% /
/dev/sda2 512M 4.0K 512M 1% /boot/efi
/dev/sdb1 961M 242M 720M 26% /media/rich/54A1-7D7D
$

df 命令在排查系统磁盘空间问题时非常有价值。
注意 记住,Linux 系统后台一直有进程在处理文件。df 命令的输出值反映的是 Linux 系统认为的当前值。正在运行的进程有可能创建或删除了某个文件,但尚未释放该文件。这个值是不会被计算进闲置空间的。

4.2.3 使用 du 命令

通过 df 命令,发现哪个磁盘存储空间不足。
du 命令可以显示某个特定目录(默认情况下是当前目录)的磁盘使用情况。这有助于你快速判断系统中是否存在磁盘占用“大户”。
在默认情况下,du 命令会显示当前目录下所有的文件、目录和子目录的磁盘使用情况,并以磁盘块为单位来表明每个文件或目录占用了多大存储空间。对标准大小的目录来说,输出内容可不少。下面是 du 命令的部分输出:
 

$ du
484       ./.gstreamer-0.10
8         ./Templates
8         ./Download
8         ./.ccache/7/0
24        ./.ccache/7
368       ./.ccache/a/d
384       ./.ccache/a
424       ./.ccache
8         ./Public
8         ./.gphpedit/plugins
32        ./.gphpedit
72        ./.gconfd
128       ./.nautilus/metafiles
384       ./.nautilus
8         ./Videos
8         ./Music
16        ./.config/gtk-2.0
40        ./.config
8         ./Documents

每行最左侧的数字是每个文件或目录所占用的磁盘块数。注意,这个列表是从目录层级的最
底部开始,然后沿着其中包含的文件和子目录逐级向上的。
单纯的 du 命令作用并不大。我们更想知道每个文件和目录各占用了多大的磁盘空间
下面这些选项能让 du 命令的输出更加清晰易读。
 -c:显示所有已列出文件的总大小。
 -h:按人类易读格式输出大小,分别用 K 表示千字节、M 表示兆字节、G 表示吉字节。
 -s:输出每个参数的汇总信息。系统管理员的下一步任务是使用一些文件处理命令来操作大量数据。这正是下一节的主题。

4.3 处理数据文件

        当有大量数据时,处理这些数据并从中提取有用信息通常不是件容易事。通过上一节的 du
命令可知,系统命令很容易输出让人难以招架的过量信息。
        Linux 系统提供了一些可以帮助你管理大量数据的命令行工具。下面是基本命令

4.3.1 数据排序

sort命令:对数据进行排序

在默认情况下,sort 命令会依据会话所指定的默认语言的排序规则来对文本文件中的数据
行进行排序:

sort file2
$ cat file1
one
two
three
four
five
$ sort file1
five
four
one
three
two
$

非常简单。但事情往往并不像看起来那么简单:

$ cat file2
1
2
100
453
10
145
75
$ sort file2
1
10
100
145
2
3
45
75
$

在默认情况下,sort 命令会将数字视为字符并执行标准的字符排序,

sort -n file2

可以使用-n 选项来解决这个问题,该选项会将数字按值排序

$ sort -n file2
1
2
3
10
45
75
100
145
$

另一个常用的选项是-M,该选择可以将数字按月排序。Linux 的日志文件经常在每行的起始位置有一个时间戳,以表明事件是什么时候发生的:

Apr 13 07:10:09 testbox smartd[2718]: Device: /dev/sda, opened

将含有时间戳日期的文件按默认的排序方法来排序:

$ sort file3
Apr
Aug
Dec
Feb
Jan
Jul
Jun
Mar
May
Nov
Oct
Sep
$
sort -M file3

加入-M 选项,那么 sort 命令就能识别三字符的月份名并正确排序。

$ sort -M file3
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
$
sort 命令其他选项

短 选 项       长 选 项                                          描 述
-b                --ignore-leading-blanks     排序时忽略起始的空白字符
-C               --check=quiet                   不排序,如果数据无序也不要报告
-c                 --check                           不排序,但检查输入数据是否有序,无序的话就报告
-d                 --dictionary-order           仅考虑空白字符和字母数字字符,不考虑特殊字符
-f                   --ignore-case                大写字母默认先出现,该选项会忽略大小写
-g                  --general-numeric-sort     使用一般数值进行排序
-i                    --ignore-nonprinting     在排序时忽略不可打印字符
-k             --key=POS1[,POS2]      排序键从 POS1 位置开始POS2 位置结束(如果指定POS2)
-M                 --month-sort                                    用三字符的月份名按月份排序
-m                   --merge                         合并两个已排序数据文件
-n                 --numeric-sort                 将字符串按数值意义排序
-o                 --output=file                         将排序结果写入指定文件
-R                 --random-sort                         根据随机哈希排序
                        --random-source=FILE         指定-R 选项用到的随机字节文件
-r                 --reverse                                 逆序排序(升序变成降序)
-S                 --buffer-size=SIZE                 指定使用的内存大小
-s                 --stable                                         禁止 last-resort 比较,实现稳定排序
-T                 --temporary-directory=DIR         指定用于保存临时工作文件的目录
-t                 --field-separator=SEP                 指定字段分隔符
-u                 --unique                      和-c 选项合用时,检查严格排序;不和-c 选项合用时,相同行
                                                        仅输出一次
-z                 --zero-terminated                 在行尾使用 NULL 字符代替换行符

在对按字段分隔的数据(比如/etc/passwd 文件)进行排序时,-k 选项和-t 选项非常方便。
先使用-t 选项指定字段分隔符,然后使用-k 选项指定排序字段。

例如,要根据用户 ID 对/etc/passwd 按数值排序,可以这么做:

$ sort -t ':' -k 3 -n /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
news:x:9:13:news:/etc/news:
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

现在数据已经按第三个字段(用户 ID 的数值)排序妥当了。
-n 选项适合于排序数值型输出,比如 du 命令的输出:

$ du -sh * | sort -nr
1008k mrtg-2.9.29.tar.gz
972k bldg1
888k fbs2.pdf
760k Printtest
680k rsync-2.6.6.tar.gz
660k code
516k fig1001.tiff
496k test
496k php-common-4.0.4pl1-6mdk.i586.rpm
448k MesaGLUT-6.5.1.tar.gz
400k plp

-r 选项对数值按照降序排列,这样便能轻而易举地看出目录中的哪些文件占用磁盘空间最多。

4.3.2 数据搜索

查找位于文件中间部分某处的数据行。使用 grep 命令来帮助查找。

grep [options] pattern [file]

grep 命令会在输入或指定文件中逐行搜索匹配指定模式的文本。

$ grep three file1
three
$ grep t file1
two
three
$

第一个例子在文件 file1 中搜索能匹配模式 three 的文本。grep 命令输出了匹配该模式的

第二个例子在文件 file1 中t 的文本,其中,file1 中有两行匹配指定模式,所以均被输出。

grep 命令非常流行,它在其生命周期中经历过大量的更新,加入了很多特性。
如果要进行反向搜索(输出不匹配指定模式的行),可以使用-v 选项:

$ grep -v t file1
one
four
five
$
行的行号,可以使用-n 选项:
$ grep -n t file1
2:two
3:three
$
行数,可以使用-c 选项:
$ grep -c t file1
2
$

多个匹配模式,可以使用-e 选项来逐个指定:
$ grep -e t -e f file1
two
three
four
five
$

这个例子输出了包含字符串 t 或字符串 f 的所有行。

正则表达式

在默认情况下,grep 命令使用基本的 Unix 风格正则表达式来匹配模式。Unix 风格正则表
达式使用特殊字符来定义如何查找匹配模式。正则表达式的更多细节,参见第 20 章。
下面是在 grep 中使用正则表达式的一个简单例子:

$ grep [tf] file1
two
three
four
five
$

正则表达式中的方括号表明 grep 应该搜索包含 t 字符或者 f 字符的匹配。

如果不用正则表达式,则 grep 搜索的是匹配字符串 tf 的文本。
egrep 命令是 grep 的一个衍生,支持 POSIX 扩展正则表达式,其中包含更多可用于指定匹配模式的字符(第 20 章)。fgrep 则是另外一个版本,支持将匹配模式指定为以换行符分
隔的一系列固定长度的字符串。这样就可以将这些字符串放入一个文件中,然后在 fgrep 命令中使用其搜索大文件中的字符串。

4.3.3 数据压缩

 Microsoft Windows中 zip 文件非常流行,zip 可以轻松地将大文件(文本文件和可执行文件)压缩成占用空间更少的小件文。
Linux 包含多种文件压缩工具。虽然听上去不错,但实际上这经常会在用户下载文件时造成
混淆。下面 列出了可用的 Linux 文件压缩工具。

工具        文件扩展名        描述采用

bzip2         .bz2                Burrows-Wheeler块排序文本压缩算法和霍夫曼编码

gzip           .gz                   GNU 压缩工具,用Lempel-Zivwelch 编码

xz              .xz                   日渐流行的通用压缩工具

zip             .zip                  Windows中PKZIP 工具的Unix实现
gzip 是 Linux 中最流行的压缩工具。
gzip 软件包是 GNU 项目的产物,旨在编写一个能够替代原先 Unix 中 compress 工具的免费版本。这个软件包包括以下文件。
 gzip:用于压缩文件。
 gzcat:用于查看压缩过的文本文件的内容。
 gunzip:用于解压文件。

这些工具基本上跟 bzip2 一样:

gzip:用于压缩文件
$ gzip myprog
$ ls -l my*
-rwxrwxr-x 1 rich rich 2197 2007-09-13 11:29 myprog.gz
$
gzip:用通配符来一次性压缩多个文件:
$ gzip my*
$ ls -l my*
-rwxr--r-- 1 rich rich 103 Sep 6 13:43 myprog.c.gz
-rwxr-xr-x 1 rich rich 5178 Sep 6 13:43 myprog.gz
-rwxr--r-- 1 rich rich 59 Sep 6 13:46 myscript.gz
-rwxr--r-- 1 rich rich 60 Sep 6 13:44 myscript2.gz
$

gzip 命令会压缩该目录中匹配通配符的每个文件。

4.3.4 数据归档

zip 它并不是 Unix 和 Linux 中的标准归档工具。

目前,Unix 和 Linux 中最流行的归档工具是 tar 命令

tar 命令最开始是用于将文件写入磁带设备以作归档,但它也可以将输出写入文件,这种用法成了在 Linux 中归档数据的普遍做法。

tar 命令的格式:tar function [options] object1 object2 ...

function 定义了 tar 命令要执行的操作,

操 作         长 选 项              描 述
-A         --concatenate         将一个 tar 归档文件追加到另一个 tar 归档文件末尾
-c         --create                  创建新的 tar 归档文件
-d         --diff                      检查归档文件和文件系统的不同之处
              --delete                从 tar 归档文件中删除文件
-r         --append                将文件追加到 tar 归档文件末尾
-t         --list                        列出 tar 归档文件的内容
-u         --update                将比 tar 归档文件中已有的同名文件更新的文件追加到该归档文件
-x         --extract                 从 tar 归档文件中提取文件

option(选项)来定义针对 tar 归档文件的具体行为

选 项                 描 述
-C dir                 切换到指定目录
-f file                 将结果输出到文件(或设备)
-j                         将输出传给 bzip2 命令进行压缩

-J                         将输出传给 xz 命令进行压缩
-p                         保留文件的所有权限
-v                         在处理文件时显示文件名
-z                         将输出传给 gzip 命令进行压缩
-Z                         将输出传给 compress 命令进行压缩

创建了一个名为 test.tar 的归档文件,包含目录 test 和 test2 的内容

tar -cvf test.tar test/ test2/

该命令列出了(但不提取)tar 文件 test.tar 的内容。

tar -tf test.tar

该命令从 tar 文件 test.tar 中提取内容。如果创建的时候 tar 文件含有目录结构,则在当前目
录中重建该目录的整个结构。

tar -xvf test.tar

tar 命令可以轻松地为整个目录结构创建归档文件。这是在 Linux 中分发开源程序源代码文件所采用的普遍方法。

.tgz

提示 在下载开源软件时经常会看到文件名以.tgz 结尾,这是经 gzip 压缩过的 tar 文件,可以用
命令 tar -zxvf filename.tgz 来提取其中的内容。

 5.1 shell 的类型

登录系统时,系统启动什么样的 shell 程序取决于你的个人用户配置。

在/etc/passwd 文件中,用户记录的第 7 个字段中列出了该用户的默认 shell 程序。只要用户登录某个虚拟控制台终端或是在 GUI 中启动终端仿真器,默认的 shell 程序就会启动。

在下面的例子中,用户 christine 使用 GNU bash shell 作为自己的默认 shell 程序:

$ cat /etc/passwd
[...]
christine:x:1001:1001::/home/christine:/bin/bash
$
bash shell

在现代 Linux 系统中,bash shell 程序(bash)通常位于/usr/bin 目录。也有可能位于/bin 目录。which bash 命令可以帮助找出 bash shell 的位置:

$ which bash
/usr/bin/bash
$

长列表中文件名尾部的星号(*)表明 bash 文件(bash shell)是一个可执行程序。

$ ls -lF /usr/bin/bash
-rwxr-xr-x. 1 root root 1219248 Nov 8 11:30 /usr/bin/bash*
$

注意 在现代 Linux 系统中,/bin 目录通常是/usr/bin/目录的符号链接,这就是为什么用户christine 的默认 shell 程序是/bin/bash,但 bash shell 程序实际位于/usr/bin/目录。涉及到有关符号链接(软链接)的相关内容。

tcsh

该 Linux 系统中还存在其他的 shell 程序,其中就有 tcsh,其源自最初的 C shell:

$ which tcsh
/usr/bin/tcsh
$ ls -lF /usr/bin/tcsh
-rwxr-xr-x. 1 root root 465200 May 14 2019 /usr/bin/tcsh*
$
zsh

这是 bash shell 另一个更复杂的版本,兼具了 tcsh 的一些特性和其他元素。

$ which zsh
/usr/bin/zsh
$ ls -lF /usr/bin/zsh
-rwxr-xr-x. 1 root root 879872 May 11 2019 /usr/bin/zsh*
$

提示 如果你在自己的 Linux 系统中没有找到这些 shell,可以自行安装,具体方法第 9 章。

C shell

C shell是指向 tcsh shell 的软链接(参见第 3 章):

$ which csh
/usr/bin/csh
$ ls -lF /usr/bin/csh
lrwxrwxrwx. 1 root root 4 May 14 2019 /usr/bin/csh -> tcsh*
$

在基于 Debian 的 Linux 系统(比如 Ubuntu)中经常会碰到 dash,这是 Ash shell 的另一个
版本。

$ which dash
/usr/bin/dash
$ ls -lF /usr/bin/dash
-rwxr-xr-x 1 root root 129816 Jul 18 2019 /usr/bin/dash*
$

在大多数 Linux 系统中,/etc/shells 文件中列出了各种已安装的 shell,这些 shell 可以作为用
户的默认 shell。

$ cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
/bin/csh
/bin/tcsh
/usr/bin/csh
/usr/bin/tcsh
/usr/bin/zsh
/bin/zsh
$

在很多 Linux 发行版中, shell 文件似乎存在于两个位置:/bin 和/usr/bin。/bin 是指向/usr/bin 的符号链接。

由于 bash shell 的广为流行,作为默认的交互式 shell(default interactive shell)也称登录 shell(login shell),只要用户登录某个虚拟控制台终端或是在 GUI 中启动终端仿真器,该 shell 就会启动。
作为默认的系统 shell(default system shell),sh(/bin/sh)用于那些需要在启动时使用的系
统 shell 脚本。
你经常会看到某些发行版使用软链接将默认的系统 shell 指向 bash shell,比如 CentOS 发行版:

$ which sh
/usr/bin/sh
$ ls -l /usr/bin/sh
lrwxrwxrwx. 1 root root 4 Nov 8 11:30 /usr/bin/sh -> bash
$

但要注意,在有些发行版中,默认的系统 shell 并不指向 bash shell,比如 Ubuntu 发行版:

$ which sh
/usr/bin/sh
$ ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 4 Mar 10 18:43 /usr/bin/sh -> dash
$

在这里,默认的系统是 dash shell。
提示 对 bash shell 脚本来说,这两种 shell(默认的交互 shell 和默认的系统 shell)可能会导致
问题。一定要阅读第 11 章中有关 bash shell 脚本首行的语法要求,以避免这些麻烦。

并不是非得使用默认的交互式 shell。可以启动任意一种已安装的 shell,只需输入其名称即可。但屏幕上不会有任何提示或消息表明你当前使用的是哪种 shell。$0 变量可以助你

命令 echo $0

会显示当前 shell 的名称,提供必要的参考。
注意 使用 echo $0 显示当前所用 shell 的做法仅限在 shell 命令行中使用。如果在 shell 脚本中
使用,则显示的是该脚本的名称。如需了解详情,请参见第 14 章。
输入命令 dash,启动 dash shell,通过echo $0 显示新的 shell。

$ echo $0
-bash
$
$ dash
$
$ echo $0
dash
$

注意 在上面的例子中,注意第一个 echo $0 命令的输出:bash 之前有一个连字符(-)。这
表明该 shell 是用户的登录 shell
$是 dash shell 的 CLI 提示符。输入命令 exit 就可以退出 dash shell 程序(对于 bash shell 也
是如此):
$ echo $0
dash
$ exit
$ echo $0
-bash
$
虽然在各种 shell 之间来回切换看起来并不难,但幕后发生的事情可没那么简单。为了理解
这个过程,下一节将探究登录 shell新启动的 shell 之间的关系。

5.2 shell 的父子关系

用户登录时所启动的默认的交互式 shell(登录 shell)是一个父 shell。到目前为止,都是由父 shell 提供 CLI 提示符并等待命令输入。
当你在 CLI 提示符处输入 bash 命令(或是其他 shell 程序名)时,会创建新的 shell 程序。
这是一个子 shell。子 shell 也拥有 CLI 提示符,同样会等待命令输入。
在输入 bash 并生成子 shell 时,屏幕上不会显示任何相关信息,要想搞清楚来龙去脉,需
要用到第 4 章中讲过的 ps 命令。在生成子 shell 的前后配合-f 选项来使用:

$ ps -f
UID PID PPID C STIME TTY TIME CMD
christi+ 6160 6156 0 11:01 pts/1 00:00:00 -bash
christi+ 7141 6160 0 12:51 pts/1 00:00:00 ps -f
$
$ bash
$
$ ps -f
UID PID PPID C STIME TTY TIME CMD
christi+ 6160 6156 0 11:01 pts/1 00:00:00 -bash
christi+ 7142 6160 0 12:52 pts/1 00:00:00 bash
christi+ 7164 7142 0 12:52 pts/1 00:00:00 ps -f
$

进程就是正在运行的程序。bash shell 是一个程序,当它运行的时候,就成了进程。一个运行着的 shell 同样是进程。因此,在说到运行 bash shell 的时候,经常会看到“shell”和“进程”这两个词交换使用。

输入命令 bash 之后,就创建了一个子 shell。第二个 ps -f 是在子 shell 中执行的。有两个 bash shell 程序在运行。一个是父 shell 进程,其 PID 为 6160。另一个是子 shell 进程,其 PID 为 7142。注意,子 shell 的父进程 ID(PPID)是 6160,表明这个进程就是该子 shell 的父进程。图                                

在生成子 shell 进程时,只有部分父进程的环境被复制到了子 shell 环境中。这会对包括变量在内的一些东西造成影响 

子 shell 既可以从父 shell 中创建,也可以从另一个子 shell 中创建:

$ ps -f
UID PID PPID C STIME TTY TIME CMD
christi+ 7650 7649 0 16:01 pts/0 00:00:00 -bash
christi+ 7686 7650 0 16:02 pts/0 00:00:00 ps -f
$
$ bash
$ bash
$ bash
$
$ ps --forest
PID TTY TIME CMD
7650 pts/0 00:00:00 bash
7687 pts/0 00:00:00 \_ bash
7709 pts/0 00:00:00     \_ bash
7731 pts/0 00:00:00         \_ bash
7753 pts/0 00:00:00             \_ ps
$

 ps --forest命令展示了这些子 shell 间的嵌套结构

$ ps -f
UID PID PPID C STIME TTY TIME CMD
christi+ 7650 7649 0 16:01 pts/0 00:00:00 -bash
christi+ 7687 7650 0 16:02 pts/0 00:00:00 bash
christi+ 7709 7687 0 16:02 pts/0 00:00:00 bash
christi+ 7731 7709 0 16:02 pts/0 00:00:00 bash
christi+ 7781 7731 0 16:04 pts/0 00:00:00 ps -f
$
修改 shell 的启动方式

选 项           说明
-c string         从 string 中读取命令并进行处理
-i                 启动一个能够接收用户输入的交互式 shell
-l                 作为登录 shell 启动
-r                 启动一个受限 shell,将用户限制在默认目录中
-s                 从标准输入中读取命令

可以输入 man bash 获得关于 bash 命令的更多帮助信息,了解更多的命令行选项。

bash--help 命令也会提供一些额外的协助。
提示 如果想查看 bash shell 的版本号,在命令行中输出 bash --version 即可。该命令不会
创建子 shell,只会显示系统中 GNU bash shell 程序的当前版本。
可以使用 exit 命令有条不紊地退出子 shell:

$ ps -f
UID PID PPID C STIME TTY TIME CMD
christi+ 7650 7649 0 16:01 pts/0 00:00:00 -bash
christi+ 7687 7650 0 16:02 pts/0 00:00:00 bash
christi+ 7709 7687 0 16:02 pts/0 00:00:00 bash
christi+ 7731 7709 0 16:02 pts/0 00:00:00 bash
christi+ 8080 7731 0 16:35 pts/0 00:00:00 ps -f
$
$ exit
exit
$
$ ps --forest
PID TTY TIME CMD
7650 pts/0 00:00:00 bash
7687 pts/0 00:00:00 \_ bash
7709 pts/0 00:00:00 \_ bash
8081 pts/0 00:00:00 \_ ps
$
$ exit
exit
$ exit
exit
$
$ ps --forest
PID TTY TIME CMD
7650 pts/0 00:00:00 bash
8082 pts/0 00:00:00 \_ ps
$

exit 命令不仅能够退出子 shell,还可以注销(log out)当前的虚拟控制台终端或终端仿真器软件。只需在父 shell 中输入 exit,就能退出 CLI 。

5.2.1 查看进程列表

可以在单行中指定要依次运行的一系列命令。以分号(;)分隔即可:

$ pwd ; ls test* ; cd /etc ; pwd ; cd ; pwd ; ls my*
/home/christine
test_file test_one test_two
/etc
/home/christine
my_file my_scrapt my_script my_scrypt
$

在上面的例子中,所有命令依次执行,没有任何问题。不过这并不是进程列表。要想成为进
程列表,命令列表必须将命令放入圆括号内:

$ (pwd ; ls test* ; cd /etc ; pwd ; cd ; pwd ; ls my*)
/home/christine
test_file test_one test_two
/etc
/home/christine
my_file my_scrapt my_script my_scrypt
$

圆括号的加入使命令列表变成了进程列表,生成了一个子 shell 来执行这些命令。
注意 进程列表是命令分组(command grouping)的一种。另一种命令分组是将命令放入花括号
内,并在命令列表尾部以分号(;)作结。语法为:{ command; }。
使用花括号进行命令分组并不会像进程列表那样创建子 shell。
要想知道是否生成了子 shell,需要使用命令输出一个环境变量(参见第 6 章)的值。这个命
令就是 echo $BASH_SUBSHELL。如果该命令返回 0,那么表明没有子 shell。如果该命令返回 1
或者其他更大的数字,则表明存在子 shell。
下面这个例子先后使用了命令列表和 echo $BASH_SUBSHELL:

$ pwd ; ls test* ; cd /etc ; pwd ; cd ; pwd ; ls my* ; echo $BASH_SUBSHELL
/home/christine
test_file test_one test_two
/etc
/home/christine
my_file my_scrapt my_script my_scrypt
0
$

改用进程列表,则结果就不一样了。在列表最后加入 echo $BASH_SUBSHELL:

$ (pwd ; ls test* ; cd /etc ; pwd ; cd ; pwd ; ls my* ; echo $BASH_SUBSHELL)
/home/christine
test_file test_one test_two
/etc
/home/christine
my_file my_scrapt my_script my_scrypt
1
$

这次在输出结果的最后是数字 1。这表明的确创建了子 shell 来执行这些命令。
因此,进程列表就是使用圆括号包围起来的一组命令,它能够创建子 shell 来执行这些命令。

甚至可以在进程列表中嵌套圆括号来创建子 shell 的子 shell:

$ (pwd ; echo $BASH_SUBSHELL)
/home/Christine
1
$ (pwd ; (echo $BASH_SUBSHELL))
/home/Christine
2
$

注意,在第一个进程列表中,数字 1 表明有一个子 shell,这个结果和预期一样。但是在第
二个进程列表中,在命令 echo $BASH_SUBSHELL 之外又多出了一对圆括号。这对圆括号在子
shell 中产生了另一个子 shell 来执行该命令。数字 2 表示的就是这个子 shell。

子 shell 在 shell 脚本中经常用于多进程处理。但是,创建子 shell 的成本不菲(意思是要消耗
更多的资源,比如内存和处理能力),会明显拖慢任务进度。在交互式 CLI shell 会话中,子 shell
同样存在问题,它并非真正的多进程处理,原因在于终端与子 shell 的 I/O 绑定在了一起

5.2.2 子 shell 用法

在交互式 shell CLI 中,还有很多更富有成效的子 shell 用法。进程列表、协程和管道(参见
第 11 章)都用到了子 shell,各自都可以有效运用于交互式 shell。
在交互式 shell 中,一种高效的子 shell 用法是后台模式。在讨论如何配合使用后台模式和子
shell 之前,需要先搞明白什么是后台模式。

1. 后台模式

在后台模式中运行命令可以在处理命令的同时让出 CLI,以供他用。演示后台模式的一个典
型命令是 sleep。
sleep 命令会接受一个参数作为希望进程等待(睡眠)的秒数。该命令在 shell 脚本中常用
于引入一段暂停时间。命令 sleep 10 会将会话暂停 10 秒,然后返回 shell CLI 提示符

$ sleep 10
$

要想将命令置入后台模式,可以在命令末尾加上字符&。把 sleep 命令置入后台模式可以让
我们利用 ps 命令小窥一番:

$ sleep 3000&
[1] 2542
$
$ ps -f
UID       PID     PPID  C  STIME  TTY     TIME          CMD
christi+  2356   2352  0  13:27    pts/0    00:00:00    -bash
christi+  2542   2356  0  13:44    pts/0    00:00:00     sleep   3000
christi+  2543   2356  0  13:44    pts/0    00:00:00     ps        -f
$

sleep 命令会在后台(&)睡眠 3000 秒(50 分钟)。当其被置入后台时,在 shell CLI 提示
符返回之前,屏幕上会出现两条信息。

第一条信息是方括号中的后台作业号(1)。

第二条信息是后台作业的进程 ID(2542)。
ps 命令可以显示各种进程。注意进程列表中的 sleep 3000 命令。在其第二列显示的 PID
和该命令进入后台时所显示的 PID 是一样的,都是 2542。

jobs 命令

能够显示当前运行在后台模式中属于你的所有进程(作业):

$ jobs
[1]+ Running sleep 3000 &
$

jobs 命令会在方括号中显示作业号(1)。除此之外,还有作业的当前状态(Running)以
及对应的命令(sleep 3000 &)。

jobs -l

除了默认信息,-l选项还会显示命令的 PID。

$ jobs -l
[1]+ 2542 Running sleep 3000 &
$

如果运行多个后台进程,则还有一些额外信息可以显示哪个后台作业是最近启动的。

最近启动的作业在其作业号之后会有一个加号(+),在它之前启动的进程(the second newest process)则以减号(-)表示。
一旦后台作业完成,就会显示出结束状态:

$
[1]+ Done sleep 3000
$

后台模式用起来非常方便,为我们提供了一种在 CLI 中创建实用子 shell 的方法。

2. 将进程列表置入后台

通过将进程列表置入后台,可以在子 shell 中进行大量的多进程处理。由此带来的一个好处是终端不再和子 shell 的 I/O 绑定在一起。
之前说过,进程列表是子 shell 中运行的一系列命令。在进程列表中加入 sleep 命令并显示
BASH_SUBSHELL 变量,结果不出所料:

$ (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)
1
$

在上面的例子中,出现了 2 秒的暂停,显示的数字 1 表明只有一个子 shell,在返回提示符
之前又经历了另一个 2 秒的暂停。
将同样的进程列表置入后台会产生些许不同的命令输出:

$ (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)&
[1] 2553
$ 1
[1]+ Done ( sleep 2; echo $BASH_SUBSHELL; sleep 2 )
$

将进程列表置入后台会产生一个作业号和进程 ID,然后会返回提示符。不过,奇怪的是表
明一级子 shell(single-level subshell)的数字 1 竟然出现在了提示符的右边。别慌,按一下 Enter键(第3行),就会得到另一个提示符了。
在后台使用进程列表可谓是在 CLI 中运用子 shell 的一种创造性方法。可以用更少的键盘输
入换来更高的效率。

sleep 命令和 echo 命令组成的进程列表只是作为示例而已。使用 tar(参见第 4 章)
创建备份文件是有效利用后台进程列表的一个更实用的例子:

$ (tar -cf Doc.tar Documents ; tar -cf Music.tar Music)&
[1] 2567
$
$ ls *.tar
Doc.tar Music.tar
[1]+ Done ( tar -cf Doc.tar Documents; tar -cf Music.tar Music )
$

将进程列表置入后台并不是子 shell 在 CLI 中仅有的创造性用法,还有一种方法是协程。

3. 协程 coproc

协程同时做两件事:一是在后台生成一个子 shell,二是在该子 shell 中执行命令。
要进行协程处理,可以结合使用 coproc 命令以及要在子 shell 中执行的命令:

$ coproc sleep 10
[1] 2689
$

除了会创建子 shell,协程基本上就是将命令置入后台。当输入 coproc 命令及其参数之后,
你会发现后台启用了一个作业。屏幕上会显示该后台作业号(1)以及进程 ID(2689)。
jobs 命令可以显示协程的状态:

$ jobs
[1]+ Running coproc COPROC sleep 10 &
$

从上面的例子中可以看到,在子 shell 中执行的后台命令是 coproc COPROC sleep 10
COPROC 是 coproc 命令给进程起的名字。可以使用命令的扩展语法来自己设置这个名字:

$ coproc My_Job { sleep 10; }
[1] 2706
$
$ jobs
[1]+ Running coproc My_Job { sleep 10; } &
$

使用扩展语法,协程名被设置成了 My_Job。
在左花括号({)和内部命令名之间有一个空格。还必须保证内部命令以分号(;)结
尾。另外,分号和右花括号(})之间也得有一个空格。
注意 协程能够让你尽情地发挥想象力,发送或接收来自子 shell 中进程的信息。只有在拥有多
个协程时才需要对协程进行命名,因为你要和它们进行通信。否则的话,让 coproc 命令
将其设置成默认名称 COPROC 即可。

协程与进程列表结合

将协程与进程列表结合起来创建嵌套子 shell。只需将命令 coproc 放在进程列表之前即可:

$ coproc ( sleep 10; sleep 2 )
[1] 2750
$
$ jobs
[1]+ Running     coproc COPROC ( sleep 10; sleep 2 ) &
$
$ ps --forest
    PID  TTY       TIME CMD
    2367 pts/0 00:00:00 bash
    2750 pts/0 00:00:00   \_ bash
    2751 pts/0 00:00:00 |     \_ sleep
    2752 pts/0 00:00:00 \_ ps
$

生成子 shell 的成本可不低,而且速度很慢。创建嵌套子 shell 更是离谱
子 shell 提供了灵活性和便利性。要想获得这些优势,重要的是要理解子 shell 的行为方式。对于命令也是如此。下面将研究内建命令与外部命令之间的行为差异。

5.3 理解外部命令和内建命令

5.3.1 外部命令

外部命令(有时也称为文件系统命令)是存在于 bash shell 之外的程序。它并不
属于 shell 程序的一部分。外部命令程序通常位于/bin、/usr/bin、/sbin 或/usr/sbin 目录中。
ps 命令就是一个外部命令。可以使用 which 命令和 type 命令找到其对应的文件名:

$ which ps
/usr/bin/ps
$
$ type ps
ps is /usr/bin/ps
$
$ ls -l /usr/bin/ps
-rwxr-xr-x. 1 root root 142216 May 11 2019 /usr/bin/ps
$
衍生(forking)

每当执行外部命令时,就会创建一个子进程。

外部命令 ps会显示其父进程以及自己所对应的衍生子进程:

$ ps -f     
UID       PID PPID C STIME TTY       TIME CMD
christi+ 2367 2363 0 10:47 pts/0 00:00:00 -bash
christi+ 4242 2367 0 13:48 pts/0 00:00:00 ps -f
$

作为外部命令,ps 命令在执行时会产生一个子进程。在这里,ps 命令的 PID 是 4242,
父 PID 是 2367。作为父进程的 bash shell 的 PID 是 2367。下图 展示了外部命令执行时的衍生过程。

无论是衍生出子进程还是创建了子 shell,都仍然可以通过信号与其互通,这一点无论是
在使用命令行还是在编写脚本时都极其有用。进程间以发送信号的方式彼此通信。第 16 章将介绍信号和信号发送。 

5.3.2 内建命令

与外部命令不同,内建命令无须使用子进程来执行。
cd 命令和 exit 命令都内建于 bash shell。可以使用 type 命令来判断某个命令是否为内建:

$ type cd
cd is a shell builtin
$
$ type exit
exit is a shell builtin
$

内建命令既不需要通过衍生出子进程来执行,也不用打开程序文件,所以执行速度更快,
效率也更高。附录 A 给出了 GNU bash shell 的内建命令清单。
注意,有些命令有多种实现。例如,echo 和 pwd 既有内建命令也有外部命令。两种实现略
有差异。要查看命令的不同实现,可以使用 type 命令的-a 选项:

$ type -a echo
echo is a shell builtin
echo is /usr/bin/echo
$
$ which echo
/usr/bin/echo
$
$ type -a pwd
pwd is a shell builtin
pwd is /usr/bin/pwd
$
$ which pwd
/usr/bin/pwd
$

type -a 命令显示出了每个命令的两种实现(内建和外部)。

which 命令只显示外部命令文件。

提示 对于有多种实现的命令,如果想使用其外部命令实现,直接指明对应的文件即可。例
如,要使用外部命令 pwd,可以输入/usr/bin/pwd。

1. 使用 history 命令

history是一个实用的内建命令,能帮助你管理先前执行过的命令。
要查看最近用过的命令列表,可以使用不带任何选项的 history 命令:

$ history
1 ps -f
2 pwd
3 ls
[...]
$

可以设置保存在 bash 历史记录中的命令数量。为此,需要修改名为 HISTSIZE 的环境变量

输入!!,然后按 Enter 键,唤回并重用最近那条命令:

$ ps --forest
PID TTY TIME CMD
2367 pts/0 00:00:00 bash
5240 pts/0 00:00:00 \_ ps
$
$ !!
ps --forest
PID TTY TIME CMD
2367 pts/0 00:00:00 bash
5241 pts/0 00:00:00 \_ ps
$

当输入!!时,bash 会先显示从 shell 的历史记录中唤回的命令,然后再执行该命令。
命令历史记录被保存在位于用户主目录的隐藏文件.bash_history 之中:

$ pwd
/home/christine
$
$ ls .bash_history
.bash_history
$

在 CLI 会话期间,bash 命令的历史记录被保存在内存中。当 shell 退出时才被写入历史文件

history 命令运行时所显示的最近命令与.bash_history 文件中最后的命令并不一致。
有 6 个已经执行过的命令并没有被记录在历史文件中。
可以在不退出 shell 的情况下强制将命令历史记录写入.bash_history 文件。为此,需要使用

history -a
history -a

如果打开了多个终端会话,则仍然可以使用 history -a 命令在每个打开的会话中向.bash_history 文件添加记录。但是历史记录并不会在其他打开的终端会话中自动更新。
这是因为.bash_history 文件只在首次启动终端会话的时候才会被读取。要想强制重新读
取.bash_history 文件,更新内存中的终端会话历史记录,可以使用

history -n 

可以唤回历史记录中的任意命令。

!42
$ history
1 ps -f
2 pwd
[...]
39 history
40 cat .bash_history
41 ps --forest
42 pwd
43 ps -f
44 history
45 cat .bash_history
46 history -a
47 history
48 cat .bash_history
49 history
$
$ !42
pwd
/home/christine
$

bash shell 会先显示从历史记录中唤回的命令,然后再执行该命令。

history -c

清除命令历史,很简单,输入 history -c即可。

接下来再输入 history-a,清除 .bash_history 文件。

最后可以通过 man history 来查看 history 命令的 bash 手册页

2. 使用命令别名
alias 命令

另一个实用的 shell 内建命令。命令别名允许为常用命令及其参数创建另一个名称,从而将输入量减少到最低。
你所使用的 Linux 发行版很有可能已经为你设置好了一些常用命令的别名。

alias -p 查看当前可用的别名:
$ alias -p
[...]
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
$

注意,在该 Ubuntu Linux 发行版中,有一个别名取代了标准命令 ls。该别名中加入了
--color=auto 选项,以便在终端支持彩色显示的情况下,ls 命令可以使用色彩编码(比如,使用蓝色表示目录)。LS_COLORS 环境变量(环境变量相关内容见第 6 章)控制着所用的色彩编码。

提示 如果经常跳转于不同的发行版,那么在使用色彩编码来分辨某个名称究竟是目录还是
文件时,一定要小心。因为色彩编码并未实现标准化,所以最好使用 ls -F 来判断文件类型。

alias li='ls -i 创建自己的别名:
$ alias li='ls -i
$
$ li
34665652 Desktop 1415018 NetworkManager.conf
1414976 Doc.tar 50350618 OldDocuments
34665653 Documents 1414981 Pictures
51693739 Downloads 16789591 Public
1415016 hlink_test_one 1415019 really_ridiculously_long_file_name
1415021 log_file 1415020 slink_test_file
51693757 Music 1415551 Templates
1414978 Music.tar 1415523 test_file
1415525 my_file 1415016 test_one
1415524 my_scrapt 1415017 test_two
1415519 my_script 16789592 Videos
1415015 my_scrypt
$

定义好别名之后,就可以随时在 shell 或者 shell 脚本中使用了。要注意,因为命令别名属于
内建命令,所以别名仅在其被定义的 shell 进程中才有效。

$ alias li='ls -i'
$
$ bash
$ li
bash: li: command not found...
$
$ exit
exit
$
$ li
34665652 Desktop 1415018 NetworkManager.conf
1414976 Doc.tar 50350618 OldDocuments
[...]
1415524 my_scrapt 1415017 test_two
1415519 my_script 16789592 Videos
1415015 my_scrypt
$

可以在命令行中输入 unalias alias-name 删除指定的别名。如果被删除的别名不是你设置的,那么等下次重新登录系统的时候,该别名就会再次出现。可以通过修改环境文件永久地删除某个别名。

w

e

e

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值