接上篇
Chapter 7 重定向
本章介绍可能是命令行最酷的特性。它叫做 I/O 重定向。”I/O”代表输入、输出,通过这个工具,你可以重定向命令的输入输出,命令的输入来自文件,而输出也存到文件。也可以把多个命令连接起来组成一个强大的命令管道。为了展示这个工具,我们将叙述以下命令:
- cat - 连接文件
- sort - 排序文本行
- uniq - 报道或省略重复行
- grep - 打印匹配行
- wc - 打印文件中换行符,字,和字节个数
- head - 输出文件第一部分
- tail - 输出文件最后一部分
- tee - 从标准输入读取数据,并同时写到标准输出和文件
标准输入、输出和错误
到目前为止,我们用到的许多程序都会产生某种输出。这种输出,经常由两种类型组成。第一,程序运行结果;这是说,程序要完成的功能。第二,我们得到状态和错误信息,这些告诉我们程序进展。如果我们观察一个命令,像 ls,会看到它的运行结果和错误信息 显示在屏幕上。
与 Unix 主题“任何东西都是一个文件”保持一致,程序,比方说ls,实际上把他们的运行结果输送到一个叫做标准输出的特殊文件(经常用 stdout 表示),而它们的状态信息则送到另一个叫做标准错误的文件(stderr)。默认情况下,标准输出和标准错误都连接到屏幕,而不是保存到磁盘文件。除此之外,许多程序从一个叫做标准输入(stdin)的设备得到输入,默认情况下, 标准输入连接到键盘。
I/O 重定向允许我们更改输出地点和输入来源。一般地,输出送到屏幕,输入来自键盘, 但是通过 I/O 重定向,我们可以做出改变。
标准输出重定向
I/O 重定向允许我们来重定义标准输出的地点。我们使用 “>” 重定向符后接文件名将标准输出重定向到除屏幕以外的另一个文件。为什么我们要这样做呢?因为有时候把一个命令的运行结果存储到一个文件很有用处。例如,我们可以告诉 shell 把ls 命令的运行结果输送到文件ls-output.txt中去,由文件代替屏幕。
[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt
当我们使用 “>” 重定向符来重定向输出结果时,目标文件总是从开头被重写。因为我们ls命令没有产生运行结果,只有错误信息,重定向操作开始重写文件,然后由于错误而停止,导致文件内容清空。事实上,如果我们需要清空一个文件内容(或者创建一个新的空文件),可以使用这样的技巧:所以,怎样才能把重定向结果追加到文件内容后面,而不是从开头重写文件?为了这个目的, 我们使用”>>“重定向符,像这样:
[me@linuxbox ~]$ > ls-output.txt
简单地使用重定向符,没有命令在它之前,这会清空一个已存在文件的内容或是 创建一个新的空文件。
所以,怎样才能把重定向结果追加到文件内容后面,而不是从开头重写文件?为了这个目的, 我们使用”>>“重定向符,像这样:
[me@linuxbox ~]$ ls -l /usr/bin >> ls-output.txt使用”>>“操作符,将导致输出结果添加到文件内容之后。如果文件不存在,文件会 被创建,就如使用了”>”操作符。把它放到测试中:
shenbufandeAir:~ private$ ls -l ls-output.txt
-rw-r--r-- 1 private staff 63361 6 3 20:51 ls-output.txt
shenbufandeAir:~ private$ ls -l /usr/bin > ls-output.txt
shenbufandeAir:~ private$ ls -l ls-output.txt
-rw-r--r-- 1 private staff 63361 6 3 20:51 ls-output.txt
shenbufandeAir:~ private$ ls -l /usr/bin >> ls-output.txt
shenbufandeAir:~ private$ ls -l ls-output.txt
-rw-r--r-- 1 private staff 126722 6 3 20:52 ls-output.txt
重复使用>文件大小并没有改变,但是使用了>>以后,文件大小是之前的两倍。
标准错误重定向
标准错误重定向没有专用的重定向操作符。为了重定向标准错误,我们必须参考其文件描述符。一个程序可以在几个编号的文件流中的任一个上产生输出。虽然我们已经将这些文件流的前三个称作标准输入、输出和错误,shell 内部分别将其称为文件描述符0、1和2。shell使用文件描述符提供了一种表示法来重定向文件。因为标准错误和文件描述符2一样,我们用这种 表示法来重定向标准错误:
[me@linuxbox ~]$ ls -l /bin/usr 2> ls-error.txt
重定向标准输出和错误到同一个文件
可能有这种情况,我们希望捕捉一个命令的所有输出到一个文件。为了完成这个,我们必须同时重定向标准输出和标准错误。使用一个表示法 &> 来重定向标准输出和错误到同一个文件。
例如
[me@linuxbox ~]$ ls -l /bin/usr &> ls-output.txt
处理不需要的输出
有时候“沉默是金”,我们不想要一个命令的输出结果,只想把它们扔掉。这种情况尤其适用于错误和状态信息。系统通过重定向输出结果到一个叫做”/dev/null”的特殊文件,为我们提供了解决问题的方法。这个文件是系统设备,叫做位存储桶,它可以接受输入,并且对输入不做任何处理。为了隐瞒命令错误信息,我们这样做:[me@linuxbox ~]$ ls -l /bin/usr 2> /dev/null
标准输入重定向
cat - 连接文件
cat命令读取一个或多个文件,然后复制它们到标准输出:
cat [file]
cat 经常被用来显示简短的文本文件。因为cat可以接受不只一个文件作为参数,所以它也可以用来把文件连接在一起。比方说我们下载了一个大型文件,这个文件被分离成多个部分(USENET中的多媒体文件经常以这种方式分离), 我们想把它们连起来。如果文件命名为:
movie.mpeg.001 movie.mpeg.002 … movie.mpeg.099
我们能用这个命令把它们连接起来:
cat movie.mpeg.0* > movie.mpeg因为通配符总是以有序的方式展开,所以这些参数会以正确顺序安排。
没有文件名参数,cat 复制标准输入到标准输出,所以我们看到文本行重复出现。我们可以使用这种行为来创建简短的文本文件。比方说,我们想创建一个叫做”lazy_dog。txt”的文件,这个文件包含例子中的文本。我们这样做:
shenbufandeAir:~ private$ cat test.txt
shenbufandeAir:~ private$ cat > test.txt
this is a standard input test.shenbufandeAir:~ private$ cat test.txt
this is a standard input test.shenbufandeAir:~ private$
管道线
命令从标准输入读取数据并输送到标准输出的能力被一个称为管道线的 shell特性所利用。使用管道操作符”|”(竖杠),一个命令的标准输出可以通过管道送至另一个命令作为标准输入:
command1 | command2
为了全面地说明这个命令,我们需要一些命令。是否记得我们说过,我们已经知道有一个命令接受标准输入?它是 less 命令。我们用 less来一页一页地显示任何命令的输出,命令把它的运行结果输送到标准输出:
[me@linuxbox ~]$ ls -l /usr/bin | less
* 使用这项技术,我们可以方便地检测会产生标准输出的任一命令的运行结果。*
过滤器
管道线经常用来对数据完成复杂的操作。有可能会把几个命令放在一起组成一个管道线。通常,以这种方式使用的命令被称为过滤器。过滤器接受输入,以某种方式改变它,然后输出它。第一个我们想试验的过滤器是sort。我们想把目录/bin和/usr/bin中的可执行程序都联合在一起,再把它们排序,然后浏览执行结果:
[me@linuxbox ~]$ ls /bin /usr/bin | sort | less
uniq - 报道或忽略重复行
uniq命令经常和sort命令结合在一起使用。uniq从标准输入或单个文件名参数接受数据有序列表(详情查看 uniq 手册页),默认情况下,从数据列表中删除任何重复行。所以,为了确信我们的列表中不包含重复句子(这是说,出现在目录/bin 和/usr/bin 中重名的程序),我们添加 uniq 到我们的管道线中:
[me@linuxbox ~]
ls/bin/usr/bin|sort|uniq|less在这个例子中,我们使用uniq从sort命令的输出结果中,来删除任何重复行。如果我们想看到重复的数据列表,让uniq命令带上”−d”选项,就像这样:[me@linuxbox ]
l
s
/
b
i
n
/
u
s
r
/
b
i
n
|
s
o
r
t
|
u
n
i
q
|
l
e
s
s
在
这
个
例
子
中
,
我
们
使
用
u
n
i
q
从
s
o
r
t
命
令
的
输
出
结
果
中
,
来
删
除
任
何
重
复
行
。
如
果
我
们
想
看
到
重
复
的
数
据
列
表
,
让
u
n
i
q
命
令
带
上
”
−
d
”
选
项
,
就
像
这
样
:
[
m
e
@
l
i
n
u
x
b
o
x
]
ls /bin /usr/bin | sort | uniq -d | less
wc - 打印行数、字数和字节数
wc(字计数)命令是用来显示文件所包含的行数、字数和字节数。例如:
shenbufandeAir:~ private
wcls−output.txt975895663361ls−output.txt在这个例子中,wc打印出来三个数字:包含在文件ls−output。txt中的行数,单词数和字节数,正如我们先前的命令,如果wc不带命令行参数,它接受标准输入。”−l”选项限制命令输出只能报道行数。添加wc到管道线来统计数据,是个很便利的方法。查看我们的有序列表中程序个数,我们可以这样做:shenbufandeAir: private
w
c
l
s
−
o
u
t
p
u
t
.
t
x
t
975
8956
63361
l
s
−
o
u
t
p
u
t
.
t
x
t
在
这
个
例
子
中
,
w
c
打
印
出
来
三
个
数
字
:
包
含
在
文
件
l
s
−
o
u
t
p
u
t
。
t
x
t
中
的
行
数
,
单
词
数
和
字
节
数
,
正
如
我
们
先
前
的
命
令
,
如
果
w
c
不
带
命
令
行
参
数
,
它
接
受
标
准
输
入
。
”
−
l
”
选
项
限
制
命
令
输
出
只
能
报
道
行
数
。
添
加
w
c
到
管
道
线
来
统
计
数
据
,
是
个
很
便
利
的
方
法
。
查
看
我
们
的
有
序
列
表
中
程
序
个
数
,
我
们
可
以
这
样
做
:
s
h
e
n
b
u
f
a
n
d
e
A
i
r
:
p
r
i
v
a
t
e
ls -l /bin | wc -l
37
shenbufandeAir:~ private
ls−l/usr/bin|wc−l975shenbufandeAir: private
l
s
−
l
/
u
s
r
/
b
i
n
|
w
c
−
l
975
s
h
e
n
b
u
f
a
n
d
e
A
i
r
:
p
r
i
v
a
t
e
ls /bin /usr/bin | sort | uniq | wc -l
1013
grep - 打印匹配行
grep是一个很强大的程序,用来找到文件中的匹配文本。这样使用 grep 命令:
grep pattern [file…]
当 grep 遇到一个文件中的匹配”模式”,它会打印出包含这个类型的行。grep 能够匹配的模式可以很复杂,但是现在我们把注意力集中在简单文本匹配上面。在后面的章节中,我们将会研究高级模式,叫做正则表达式。
比如说,我们想在我们的程序列表中,找到文件名中包含单词”zip”的所有文件。这样一个搜索,可能让我们了解系统中的一些程序与文件压缩有关系。这样做:
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | grep zip
bunzip2
bzip2
gunzip
…
grep 有一些方便的选项:”-i”使得 grep 在执行搜索时忽略大小写(通常,搜索是大小写 敏感的),”-v”选项会告诉 grep 只打印不匹配的行。
head/tail - 打印文件开头部分/结尾部分
有时候你不需要一个命令的所有输出。可能你只想要前几行或者后几行的输出内容。head命令打印文件的前十行,而tail命令打印文件的后十行。默认情况下,两个命令都打印十行文本,但是可以通过”-[me@linuxbox ~]
head−n5ls−output.txttotal343496…[me@linuxbox ]
h
e
a
d
−
n
5
l
s
−
o
u
t
p
u
t
.
t
x
t
t
o
t
a
l
343496
…
[
m
e
@
l
i
n
u
x
b
o
x
]
tail -n 5 ls-output.txt
…n”选项来调整命令打印的行数。
它们也能用于管道线中
[me@linuxbox ~]$ ls /usr/bin | tail -n 5
znew
…
tail 有一个选项允许你实时地浏览文件。当观察日志文件的进展时,这很有用,因为它们同时在被写入。在以下的例子里,我们要查看目录
/var/log里面的信息文件。在一些Linux发行版中,要求有超级用户权限才能阅读这些文件,因为文件/var/log/messages 可能包含安全信息。
[me@linuxbox ~]$ tail -f /var/log/messages
Feb 8 13:40:05 twin4 dhclient: DHCPACK from 192.168.1.1
….
使用”-f”选项,tail 命令继续监测这个文件,当新的内容添加到文件后,它们会立即出现在屏幕上。这会一直继续下去直到你输入 Ctrl-c。
tee - 从Stdin读取数据,并同时输出到Stdout和文件
为了和我们的管道隐喻保持一致,Linux提供了一个叫做tee的命令,这个命令制造了一个”tee”,安装到我们的管道上。tee 程序从标准输入读入数据,并且同时复制数据到标准输出(允许数据继续随着管道线流动)和一个或多个文件。当在某个中间处理阶段来捕捉一个管道线的内容时,这很有帮助。这里,我们重复执行一个先前的例子, 这次包含 tee 命令,在 grep 过滤管道线的内容之前,来捕捉整个目录列表到文件 ls.txt:
[me@linuxbox ~]$ ls /usr/bin | tee ls.txt | grep zip
bunzip2
bzip2
….
本章小结
一如既往,查看这章学到的每一个命令的文档。我们已经知道了他们最基本的用法。它们还有很多有趣的选项。随着我们 Linux 经验的积累,我们会了解命令行重定向特性在解决特殊问题时非常有用处。有许多命令利用标准输入和输出,而几乎所有的命令行 程序都使用标准错误来显示它们的详细信息。
Linux 可以激发我们的想象
当我被要求解释 Windows 与 Linux 之间的差异时,我经常拿玩具来作比喻。
Windows 就像一个游戏机。你去商店,买了一个包装在盒子里面的全新的游戏机。你把它带回家,打开盒子,开始玩游戏。精美的画面,动人的声音。玩了一段时间之后,你厌倦了它自带的游戏,所以你返回商店,又买了另一个游戏机。这个过程反复重复。最后,你玩腻了游戏机自带的游戏,你回到商店,告诉售货员,“我想要一个这样的游戏!”但售货员告诉你没有这样的游戏存在,因为它没有“市场需求”。然后你说,“但是我只 需要修改一下这个游戏!“,售货员又告诉你不能修改它。所有游戏都被封装在它们的存储器中。到头来,你发现你的玩具只局限于别人为你规定好的游戏。
另一方面,Linux 就像一个全世界上最大的建造模型。你打开它,发现它只是一个巨大的部件集合。有许多钢支柱、螺钉、螺母、齿轮、滑轮、发动机和一些怎样来建造它的说明书。然后你开始摆弄它。你建造了一个又一个样板模型。过了一会儿,你发现你要建造自己的模型。你不必返回商店,因为你已经拥有了你需要的一切。建造模型以你构想的形状为模板,搭建 你想要的模型。
当然,选择哪一个玩具,是你的事情,那么你觉得哪个玩具更令人满意呢?
Chapter 8 从shell眼中看世界
在这一章我们将看到,当你按下enter键后,发生在命令行中的一些“魔法”。尽管我们会深入研究几个复杂而有趣的 shell 特性,但我们只需要使用一个新命令:
echo - 显示一行文本
(字符)展开
每当你输入一个命令并按下 enter 键,bash 会在执行你的命令之前对输入的字符完成几个步骤的处理。我们已经见过几个例子:例如一个简单的字符序列”*”,对shell来说有着多么丰富的涵义。这背后的的过程叫做(字符)展开。通过展开,你输入的字符,在shell对它起作用之前,会展开成为别的字符。为了说明这一点 ,让我们看一看echo命令。echo是一个shell内建命令,可以完成非常简单的任务。它将它的文本参数打印到标准输出中。
shenbufandeAir:~ private$ type echo
echo is a shell builtin
shenbufandeAir:~ private$ echo this is a test
this is a test
这个命令的作用相当简单明了。传递到 echo 命令的任一个参数都会在(屏幕上)显示出来。 让我们试试另一个例子:
shenbufandeAir:leetcode private$ echo 1*
100.py 101.py 104.py 107.py 108.py 110.py 111.py 112.py 118.py 119.py 121.py 122.py 125.py 136.py 141.py 142.py 155.py 160.py 167.py 168.py 169.py 171.py 172.py 189.py 190.py 191.py 198.py
那么刚才发生了什么事情呢? 为什么 echo 不打印”“呢?如果你回忆起我们所学过的 关于通配符的内容,这个”“字符意味着匹配文件名中的任意字符,但在原先的讨论 中我们并不知道shell 是怎样实现这个功能的。简单的答案就是 shell 在 echo 命 令被执行前把’‘展开成了另外的东西(在这里,就是在当前工作目录下的文件名字)。当回车键被按下时,shell在命令被执行前在命令行上自动展开任何符合条件的字符, 所以 echo 命令的实际参数并不是”“,而是它展开后的结果。知道了这个以后, 我们就能明白 echo 的行为符合预期。
路径名展开
通配符所依赖的工作机制叫做路径名展开。如果我们试一下在之前的章节中使用的技巧,我们会看到它们实际上是展开。查看home目录之外的目录:
[me@linuxbox ~]$ echo /usr/*/share
/usr/kerberos/share /usr/local/share
正如我们知道的,以圆点字符开头的文件名是隐藏文件。路径名展开也尊重这种 行为。像这样的展开:
echo * 不会显示隐藏文件
使用 echo .* 会显示隐藏文件,但是’.’,’..’也在结果中,它们是指当前工作目录和父目录.
shenbufandeAir:~ private$ echo .*
. .. .CFUserTextEncoding .DS_Store .Rapp.history .Trash .bash_history .bash_profile .bash_sessions .config .gitconfig .lesshst .matplotlib .mysql_history .profile .python_history .qd .sqlite_history .ssh .viminfo .yunpan
因此我们需要更精确的命令
ls -d .[!.]?*
这种模式展开成所有以圆点开头,第二个字符不包含圆点,再包含至少一个字符,并且这个字符之后紧接着任意多个字符的文件名。这个命令将正确列出大多数的隐藏文件(但仍不能包含以多个圆点开头的文件名)。带有 -A 选项(“几乎所有”)的 ls 命令能够提供一份正确的隐藏文件清单:
ls -A
波浪线展开
可能你从我们对 cd 命令的介绍中回想起来,波浪线字符(“~”)有特殊的含义。当它用在、一个单词的开头时,它会展开成指定用户的家目录名,如果没有指定用户名,则展开成当前用户的家目录:
shenbufandeAir:~ private$ echo ~
/Users/private
算术表达式展开
shell 在展开中执行算数表达式。这允许我们把 shell 提示当作计算器来使用:
[me@linuxbox ~]
echo
e
c
h
o
((2 + 2))
4
算术表达式
算术表达式展开使用这种格式:
$((expression))
算术表达式只支持整数(全部是数字,不带小数点)
花括号展开
可能最奇怪的展开是花括号展开。通过它,你可以从一个包含花括号的模式中创建多个文本字符串。这是一个例子:
[me@linuxbox ~]$ echo Front-{A,B,C}-Back
Front-A-Back Front-B-Back Front-C-Back
花括号展开模式可能包含一个开头部分叫做报头,一个结尾部分叫做附言。花括号表达式本身可能包含一个由逗号分开的字符串列表,或者一个整数区间,或者单个的字符的区间。这种模式不能嵌入空白字符。这个例子中使用了一个整数区间:
[me@linuxbox ~]$ echo Number_{1..5}
Number_1 Number_2 Number_3 Number_4 Number_5
倒序排列的字母区间:
[me@linuxbox ~]$ 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
花括号展开可以嵌套:
[me@linuxbox ~]$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b
那么这对什么有好处呢?最常见的应用是,创建一系列的文件或目录列表。例如,如果我们是摄影师,有大量的相片。我们想把这些相片按年月先后组织起来。首先,我们要创建一系列以数值”年-月”形式命名的目录。通过这种方式,可以使目录名按照年代顺序排列。我们可以手动键入整个目录列表,但是工作量太大了,并且易于出错。 反之,我们可以这样做:
shenbufandeAir:~ private$ mkdir Pics
shenbufandeAir:~ private$ cd Pics
shenbufandeAir:Pics private$ mkdir {2007..2009}-0{1..9} {2007..2009}-{10..12}
shenbufandeAir:Pics private$ ls
2007-01 2007-05 2007-09 2008-01 2008-05 2008-09 2009-01 2009-05 2009-09
2007-02 2007-06 2007-10 2008-02 2008-06 2008-10 2009-02 2009-06 2009-10
2007-03 2007-07 2007-11 2008-03 2008-07 2008-11 2009-03 2009-07 2009-11
2007-04 2007-08 2007-12 2008-04 2008-08 2008-12 2009-04 2009-08 2009-12
参数展开
在这一章我们将会简单介绍参数展开,但会在后续章节中进行详细讨论。这个特性在shell脚本中比直接在命令行中更有用。它的许多功能和系统存储小块数据,并给每块数据命名的能力有关系。许多像这样的小块数据, 更恰当的称呼应该是变量,可供你方便地检查它们。例如,叫做”USER”的变量包含你的用户名。可以这样做来调用参数,并查看 USER 中的内容:
shenbufandeAir:~ private
echo
e
c
h
o
USER
private
要查看有效的变量列表,可以试试这个:
[me@linuxbox ~]$ printenv | less
你可能注意到在其它展开类型中,如果你误输入一个模式,展开就不会发生。这时echo命令只简单地显示误键入的模式。但在参数展开中,如果你拼写错了一个变量名,展开仍然会进行,只是展开的结果是一个空字符串:
[me@linuxbox ~]
echo
e
c
h
o
SUER
[me@linuxbox ~]$
命令替换
命令替换允许我们把一个命令的输出作为一个展开模式来使用:
[me@linuxbox ~]
echo
e
c
h
o
(ls)
Desktop Documents ls-output.txt Music Pictures Public Templates
Videos
这里我们把 which cp 的执行结果作为一个参数传递给ls命令,因此可以在不知道cp命令完整路径名的情况下得到它的文件属性列表。
[me@linuxbox ~]
ls−l
l
s
−
l
(which cp)
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp
我们不只限制于简单命令。也可以使用整个管道线 (只展示部分输出):
[me@linuxbox ~]
file
f
i
l
e
(ls /usr/bin/* | grep zip)
/usr/bin/bunzip2: symbolic link to `bzip2’
….
在这个例子中,管道线的输出结果成为 file 命令的参数列表。这里file filename显示文件类型
如
shenbufandeAir:Desktop private$ file test.py
test.py: Python script text executable, ASCII text
引用
我们已经知道 shell 有许多方式可以完成展开,现在是时候学习怎样来控制展开了。 以下面例子来说明:
[me@linuxbox ~]$ echo this is a test
this is a test
[me@linuxbox ~]$ echo The total is $100.00
The total is 00.00
在第一个例子中,shell利用单词分割删除掉echo命令的参数列表中多余的空格。在第二个例子中,参数展开把 $1 的值替换为一个空字符串,因为1是没有定义的变量。shell提供了一种叫做引用的机制,来有选择地禁止不需要的展开。
双引号
我们将要看一下引用的第一种类型,双引号。如果你把文本放在双引号中,shell使用的特殊字符,都失去它们的特殊含义,被当作普通字符来看待。 有几个例外: $,\ (反斜杠),和`(倒引号)。这意味着单词分割、路径名展开、波浪线展开和花括号展开都将失效,然而参数展开、算术展开和命令替换仍然执行。使用双引号,我们可以处理包含空格的文件名。比方说我们是不幸的 名为 two words.txt文件的受害者。如果我们试图在命令行中使用这个文件,单词分割机制会导致这个文件名被看作两个独自的参数,而不是所期望的单个参数:
单引号
如果需要禁止所有的展开,我们要使用单引号。以下例子是无引用,双引号,和单引号的比较结果
[me@linuxbox ~]
echotext /∗.txta,b
e
c
h
o
t
e
x
t
/
∗
.
t
x
t
a
,
b
(echo foo)
((2+2))
(
(
2
+
2
)
)
USER
text /home/me/ls-output.txt a b foo 4 me
[me@linuxbox ~]
echo"text /∗.txta,b
e
c
h
o
"
t
e
x
t
/
∗
.
t
x
t
a
,
b
(echo foo)
((2+2))
(
(
2
+
2
)
)
USER”
text ~/*.txt {a,b} foo 4 me
[me@linuxbox ~]
echo′text /∗.txta,b
e
c
h
o
′
t
e
x
t
/
∗
.
t
x
t
a
,
b
(echo foo)
((2+2))
(
(
2
+
2
)
)
USER’
text ~/*.txt {a,b}
(echofoo)
(
e
c
h
o
f
o
o
)
((2+2)) $USER
转义字符
有时候我们只想引用单个字符。我们可以在字符之前加上一个反斜杠,在这里叫做转义字符。 经常在双引号中使用转义字符,来有选择地阻止展开。
为了允许反斜杠字符出现,输入”\“来转义。注意在单引号中,反斜杠失去它的特殊含义,它 被看作普通字符。
这种利用反斜杠的表示法背后的思想来源于 C 编程语言, 许多其它语言也采用了这种表示方法,包括 shell。
echo 命令带上 ‘-e’ 选项,能够解释转义序列。你可以把转义序列放在
″里面。以下例子中,我们可以使用sleep命令创建一个简单的倒数计数器(sleep是一个简单的程序,它会等待指定的秒数,然后退出):sleep10;echo−e“Time′sup\a”Wecouldalsodothis:我们也可以这样做:sleep10;echo“Time′sup”
″
里
面
。
以
下
例
子
中
,
我
们
可
以
使
用
s
l
e
e
p
命
令
创
建
一
个
简
单
的
倒
数
计
数
器
(
s
l
e
e
p
是
一
个
简
单
的
程
序
,
它
会
等
待
指
定
的
秒
数
,
然
后
退
出
)
:
s
l
e
e
p
10
;
e
c
h
o
−
e
“
T
i
m
e
′
s
u
p
\a
”
W
e
c
o
u
l
d
a
l
s
o
d
o
t
h
i
s
:
我
们
也
可
以
这
样
做
:
s
l
e
e
p
10
;
e
c
h
o
“
T
i
m
e
′
s
u
p
”
’\a’
本章小结
随着我们继续学习 shell,你会发现使用展开和引用的频率逐渐多起来,所以能够很好的理解它们的工作方式很有意义。事实上,可以这样说,它们是学习shell的最重要的主题。如果没有准确地理解展开模式,shell 总是神秘和混乱的源泉,并且 shell 潜在的能力也 浪费掉了。
Chapter 9 键盘高级操作技巧
开玩笑地说,我经常把 Unix 描述为“这个操作系统是为喜欢敲键盘的人们服务的。”当然,Unix甚至还有一个命令行这件事证明了我所说的话。但是命令行用户不喜欢敲入那么多字。要不什么如此多的命令会有这样简短的命令名,像cp、ls、mv和rm?事实上,命令行最为珍视的目标之一就是懒惰;用最少的击键次数来完成最多的工作。另一个目标是你的手指永远不必离开键盘,永不触摸鼠标。在这一章节,我们将看一下bash 特性 ,这些特性使键盘使用起来更加迅速,更加高效。
本章介绍以下命令:
- clear - 清空屏幕
- history - 显示历史列表内容
命令行编辑
Bash 使用了一个名为 Readline 的库(共享的例程集合,可以被不同的程序使用),来实现命令行编辑。我们已经看到一些例子。我们知道,例如,箭头按键可以移动光标,此外还有许多特性。想想这些额外的工具,我们可以在工作中使用。学会所有的特性 并不重要,但许多特性非常有帮助。选择自己需要的特性。
移动光标
下表列出了移动光标所使用的按键:
按键 行动
Ctrl-a 移动光标到行首。
Ctrl-e 移动光标到行尾。
Ctrl-f 光标前移一个字符;和右箭头作用一样。
Ctrl-b 光标后移一个字符;和左箭头作用一样。
Alt-f 光标前移一个字。
Alt-b 光标后移一个字。
Ctrl-l 清空屏幕,移动光标到左上角。clear 命令完成同样的工作。
修改文本
列出了键盘命令,这些命令用来在命令行中编辑字符。
按键 行动
Ctrl-d 删除光标位置的字符。
Ctrl-t 光标位置的字符和光标前面的字符互换位置。
Alt-t 光标位置的字和其前面的字互换位置。
Alt-l 把从光标位置到字尾的字符转换成小写字母。
Alt-u 把从光标位置到字尾的字符转换成大写字母。
剪切和粘贴文本
Readline 的文档使用术语killing和yanking来指我们平常所说的剪切和粘贴。剪切下来的本文被存储在一个叫做剪切环(kill-ring)的缓冲区中。
剪切和粘贴命令如下:
按键 行动
Ctrl-k 剪切从光标位置到行尾的文本。
Ctrl-u 剪切从光标位置到行首的文本。
Alt-d 剪切从光标位置到词尾的文本。
Alt-Backspace 剪切从光标位置到词头的文本。如果光标在一个单词的开头,剪切前一个单词。
Ctrl-y 把剪切环中的文本粘贴到光标位置。
自动补全
shell 能帮助你的另一种方式是通过一种叫做自动补全的机制。当你敲入一个命令时,按下tab键,自动补全就会发生。让我们看一下这是怎样工作的。给出一个看起来 像这样的home目录:
[me@linuxbox ~]
lsDesktopls−output.txtPicturesTemplatesVideos….试着输入下面的命令,但不要按下Enter键:[me@linuxbox ]
l
s
D
e
s
k
t
o
p
l
s
−
o
u
t
p
u
t
.
t
x
t
P
i
c
t
u
r
e
s
T
e
m
p
l
a
t
e
s
V
i
d
e
o
s
…
.
试
着
输
入
下
面
的
命
令
,
但
不
要
按
下
E
n
t
e
r
键
:
[
m
e
@
l
i
n
u
x
b
o
x
]
ls l
现在按下 tab 键:
[me@linuxbox ~]
lsls−output.txt这个实例展示了路径名自动补全,这是最常用的形式。自动补全也能对变量(如果字的开头是一个”
l
s
l
s
−
o
u
t
p
u
t
.
t
x
t
这
个
实
例
展
示
了
路
径
名
自
动
补
全
,
这
是
最
常
用
的
形
式
。
自
动
补
全
也
能
对
变
量
(
如
果
字
的
开
头
是
一
个
”
”)、用户名字(单词以”~”开始)、命令(如果单词是一行的第一个单词)和主机名(如果单词的开头是”@”)起作用。主机名自动补全只对包含在文件/etc/hosts 中的主机名有效。
有一系列的控制和元键序列与自动补全相关联:
按键 行动
Alt-? 显示可能的自动补全列表。在大多数系统中,你也可以完成这个通过按 两次 tab 键,这会更容易些。
Alt-* 插入所有可能的自动补全。当你想要使用多个可能的匹配项时,这个很有帮助。
利用历史命令
正如我们在第二章中讨论到的,bash维护着一个已经执行过的命令的历史列表。这个命令列表被保存在你家目录下,一个叫做 .bash_history 的文件里。这个history工具是个有用资源,因为它可以减少你敲键盘的次数,尤其当和命令行编辑联系起来时。
搜索历史命令
在任何时候,我们都可以浏览历史列表的内容,通过:
[me@linuxbox ~]$ history | less
在默认情况下,bash 会存储你所输入的最后 500个命令。在随后的章节里,我们会知道怎样调整这个数值。比方说我们想在自己曾经用过的命令中,找出和/usr/bin这一目录相关的。那么我们就可以这样做:
[me@linuxbox ~]$ history | grep /usr/bin
比方说在我们的搜索结果之中,我们得到一行,包含了有趣的命令,像这样;
367 cd Desktop/Python/leetcode
数字 “367” 是这个命令在历史列表中的行号。我们可以使用另一种叫做历史命令展开的方式,来调用“367”所代表的这一行命令:
shenbufandeAir:~ private$ !367
cd Desktop/Python/leetcode
shenbufandeAir:leetcode private$
bash 会把 “!367” 展开成为历史列表中367行的内容。还有其它的历史命令展开形式,我们一会儿讨论它们。bash也具有按递增顺序来搜索历史列表的能力。这意味着随着字符的输入,我们可以告诉bash去搜索历史列表,每一个附加字符都进一步提炼我们的搜索。启动递增搜索,输入
Ctrl-r,其后输入你要寻找的文本。当你找到它以后,你可以敲入 Enter 来执行命令,
或者输入Ctrl-j,从历史列表中复制这一行到当前命令行。
再次输入Ctrl-r,来找到下一个匹配项(向上移动历史列表)。
输入 Ctrl-g 或者 Ctrl-c,退出搜索。实际来体验一下:
下表列出了一些按键组合, 这些按键可以用来操作历史列表:
按键 行为
Ctrl-p 移动到上一个历史条目。类似于上箭头按键。
Ctrl-n 移动到下一个历史条目。类似于下箭头按键。
Alt-< 移动到历史列表开头。
Alt-> 移动到历史列表结尾,即当前命令行。
Ctrl-r 反向递增搜索。从当前命令行开始,向上递增搜索。
Alt-p 反向搜索,不是递增顺序。输入要查找的字符串,然后按下 Enter,执行搜索。
Alt-n 向前搜索,非递增顺序。
Ctrl-o 执行历史列表中的当前项,并移到下一个。如果你想要执行历史列表中一系列的命令,这很方便。
历史命令展开
通过使用 “!” 字符,shell为历史列表中的命令,提供了一个特殊的展开类型。我们已经知道一个感叹号 ,其后再加上一个数字,可以把来自历史列表中的命令插入到命令行中。这里还有一些其它的展开特性:
序列 行为
!! 重复最后一次执行的命令。可能按下上箭头按键和 enter 键更容易些。
!number 重复历史列表中第 number 行的命令。
!string 重复最近历史列表中,以这个字符串开头的命令。
!?string 重复最近历史列表中,包含这个字符串的命令。
应该小心谨慎地使用 “!string” 和 “!?string” 格式,除非你完全确信历史列表条目的内容。
本章小结
在这一章中,我们已经讨论了一些由shell提供的键盘操作技巧,这些技巧是来帮助打字员减少工作量的。随着时光流逝,你和命令行打交道越来越多,我猜想你会重新翻阅这一章的内容,学会更多的技巧。目前,你就认为它们是可选的,潜在地有帮助的。
Chapter 10 权限
Unix 传统中的操作系统不同于那些 MS-DOS传统中的系统,区别在于它们不仅是多任务系统,而且也是多用户系统。这到底意味着什么?它意味着多个用户可以在同一时间使用同一台计算机。然而一个典型的计算机可能只有一个键盘和一个监视器,但是它仍然可以被多个用户使用。例如,如果一台计算机连接到一个网络或者因特网,那么远程用户通过 ssh(安全 shell)可以登录并操纵这台电脑。事实上,远程用户也能运行图形界面应用程序,并且图形化的输出结果会出现在远端的显示器上。X窗口系统把这个作为基本设计理念的一部分,并支持这种功能。
Linux系统的多用户性能,不是最近的“创新”,而是一种特性,它深深地嵌入到了Linux操作系统的设计过程中。 想一下 Unix系统的诞生环境,这会很有意义。多年前,在个人电脑出现之前,计算机都是大型、昂贵的、集中化的。 例如一个典型的大学计算机系统,是由坐落在一座建筑中的一台大型中央计算机和许多散布在校园各处的终端机组成,每个终端都连接到这台大型中央计算机。 这台计算机可以同时支持很多用户。
为了使多用户特性付诸实践,那么必须发明一种方法来阻止用户彼此之间受到影响。毕竟,一个用户的行为不能导致计算机崩溃,也不能乱动属于另一个用户的文件。
在这一章中,将要介绍如下命令:
- id - 显示用户身份号
- chmod - 更改文件模式
- umask - 设置默认的文件权限
- su - 以另一个用户的身份来运行shell
- sudo - 以另一个用户的身份来执行命令
- chown - 更改文件所有者
- chgrp - 更改文件组所有权
- passwd - 更改用户密码
拥有者、组成员和其他人
在第四章中我们曾遇到过以下问题
shenbufandeAir:etc private$ file /etc/shadow
/etc/shadow: cannot open `/etc/shadow' (No such file or directory)
产生这种错误信息的原因是,作为一个普通用户,我们没有权限来读取这个文件。
在 Unix 安全模型中,一个用户可能拥有文件和目录。当一个用户拥有一个文件或目录时,用户对这个文件或目录的访问权限拥有控制权。用户反过来又属于一个由一个或多个用户组成的用户组,用户组成员由文件和目录的所有者授予对文件和目录的访问权限。除了对一个用户组授予权限之外,文件所有者可能会给每个人一些权限,在 Unix 术语中,每个人 是指整个世界。可以用 id 命令,来找到关于你自己身份的信息:
shenbufandeAir:etc private$ id
uid=501(private) gid=20(staff) groups=20(staff)...
让我们看一下输出结果。当用户创建帐户之后,系统会给用户分配一个号码,叫做用户ID或者uid,然后,为了符合人类的习惯,这个ID映射到一个用户名。系统又会给这个用户分配一个原始的组ID或者是gid,这个 gid 可能属于另外的组。
那么这些信息来源于哪里呢?像Linux系统中的许多东西一样,来自一系列的文本文件。用户帐户定义在/etc/passwd 文件里面,用户组定义在/etc/group文件里面。当用户帐户和用户组创建以后,这些文件随着文件/etc/shadow 的变动而修改,文件/etc/shadow 包含了关于用户密码的信息。对于每个用户帐号,文件/etc/passwd定义了用户(登录)名、uid、gid、帐号的真实姓名、家目录和登录shell。如果你查看一下文件/etc/passwd 和文件/etc/group的内容,你会注意到除了普通用户帐号之外,还有超级用户(uid0)帐号,和各种各样的系统用户.
在下一章中,当我们讨论进程时,你会知道这些其他的“用户”是谁,实际上,他们相当忙碌。
读取、写入和执行
对于文件和目录的访问权力是根据读访问、写访问和执行访问来定义的。如果我们看一下ls命令的输出结果,我们能得到一些线索,这是怎样实现的:
shenbufandeAir:~ private$ > foo.txt
shenbufandeAir:~ private$ ls -l foo.txt
-rw-r--r-- 1 private staff 0 6 10 17:11 foo.txt
首先使用重定向符‘>‘新建一个名为foo的.txt文件,然后使用ls -l 命令查看。其中列表的前十个字符是文件的属性。这十个字符的第一个字符表明文件类型。下表是你可能经常看到的文件类型:
属性 文件类型
- 一个普通文件
d 一个目录
l 一个符号链接。注意对于符号链接文件,剩余的文件属性总是”rwxrwxrwx”,而且都是虚拟值。真正的文件属性是指符号链接所指向的文件的属性。
c 一个字符设备文件。这种文件类型是指按照字节流来处理数据的设备。比如说终端机或者调制解调器
b 一个块设备文件。这种文件类型是指按照数据块来处理数据的设备,例如一个硬盘或者 CD-ROM 盘。
剩下的九个字符叫做文件模式,代表着文件所有者、文件组所有者和其他人的读、写和执行权限。
当设置文件模式后,r、w和x 模式属性对文件和目录会产生以下影响:
r
文件-允许打开并读取文件内容。
目录-允许列出目录中的内容,前提是目录必须设置了可执行属性(x)。
w
文件:允许写入文件内容或截断文件。但是不允许对文件进行重命名或删除,重命名或删除是由目录的属性决定的。
目录:允许在目录下新建、删除或重命名文件,前提是目录必须设置了可执行属性(x)。
x
文件:允许将文件作为程序来执行,使用脚本语言编写的程序必须设置为可读才能被执行。
目录:允许进入目录,例如:cd directory 。
下面是权限属性的一些例子:
文件属性 含义
-rwx—— 一个普通文件,对文件所有者来说可读、可写、可执行。其他人无法访问。
-rw——- 一个普通文件,对文件所有者来说可读可写。其他人无法访问。
-rw-r–r– 一个普通文件,对文件所有者来说可读可写,文件所有者的组成员可以读该文件,其他所有人都可以读该文件。
-rwxr-xr-x 一个普通文件,对文件所有者来说可读、可写、可执行。也可以被其他的所有人读取和执行。
-rw-rw—- 一个普通文件,对文件所有者以及文件所有者的组成员来说可读可写。
lrwxrwxrwx 一个符号链接,符号链接的权限都是虚拟的,真实的权限应该以符号链接指向的文件为准。
drwxrwx— 一个目录,文件所有者以及文件所有者的组成员可以访问该目录,并且可以在该目录下新建、重命名、删除文件。
drwxr-x— 一个目录,文件所有者可以访问该目录,并且可以在该目录下新建、重命名、删除文件,文件所有者的组成员可以访问该目录,但是不能新建、重命名、删除文件。
chmod - 更改文件模式
更改文件或目录的模式(权限),可以利用chmod命令。注意只有文件的所有者或者超级用户才能更改文件或目录的模式。chmod 命令支持两种不同的方法来改变文件模式:八进制数字表示法或符号表示法。首先我们讨论一下八进制数字表示法。
通过八进制表示法,我们使用八进制数字来设置所期望的权限模式。因为每个八进制数字代表了、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个八进制数字,我们能够设置文件所有者、用户组和其他人的权限:
通过传递参数 “600”,我们能够设置文件所有者的权限为读写权限,而删除用户组和其他人的所有权限。虽然八进制到二进制的映射看起来不方便,但通常只会用到一些常见的映射关系: 7 (rwx),6 (rw-),5 (r-x),4 (r–),和 0 (—)。
例子
-rw-r--r-- 1 private staff 0 6 10 19:50 foo.txt
shenbufandeAir:~ private$ chmod 600 foo.txt
shenbufandeAir:~ private$ ls -l foo.txt
-rw------- 1 private staff 0 6 10 19:50 foo.txt
chmod 命令修改权限的另一种方法-符号表示法,来指定文件模式。符号表示法分为三部分:更改会影响谁,要执行哪个操作,要设置哪种权限。通过字符 “u”、“g”、“o”和 “a” 的组合来指定 要影响的对象,如下所示:
表:chmod 命令符号表示法
u “user”的简写,意思是文件或目录的所有者。
g 用户组。
o “others”的简写,意思是其他所有的人。
a “all”的简写,是”u”, “g”和“o”三者的联合。
如果没有指定字符,则假定使用”all”。执行的操作可能是一个“+”字符,表示加上一个权限,一个“-”,表示删掉一个权限,或者是一个“=”,表示只有指定的权限可用,其它所有的权限被删除。权限由“r”、“w”和 “x” 来指定。这里是一些符号表示法的实例:
表: chmod 符号表示法实例
u+x 为文件所有者添加可执行权限。
u-x 删除文件所有者的可执行权限。
+x 为文件所有者,用户组,和其他所有人添加可执行权限。 等价于 a+x。
o-rw 除了文件所有者和用户组,删除其他人的读权限和写权限。
go=rw 给群组的主人和任意文件拥有者的人读写权限。如果群组的主人或全局之前已经有了执行的权限,他们将被移除。
u+x,go=rw 给文件拥有者执行权限并给组和其他人读和执行的权限。多种设定可以用逗号分开。
例子
shenbufandeAir:~ private$ ls -l foo.txt
-rw------- 1 private staff 0 6 10 19:50 foo.txt
shenbufandeAir:~ private$ chmod +x foo.txt
shenbufandeAir:~ private$ ls -l foo.txt
-rwx--x--x 1 private staff 0 6 10 19:50 foo.txt
一些人喜欢使用八进制表示法,而另一些人则非常喜欢符号表示法。符号表示法的优点是,允许你设置文件模式的某个属性,而不影响其他的属性。
umask - 设置默认权限
当创建一个文件时,umask 命令控制着文件的默认权限。umask命令使用八进制表示法来表达从文件模式属性中删除一个位掩码。大家看下面的例子:
shenbufandeAir:~ private$ rm foo.txt
shenbufandeAir:~ private$ umask
0022
shenbufandeAir:~ private$ > foo.txt
shenbufandeAir:~ private$ ls -l foo.txt
-rw-r--r-- 1 private staff 0 6 10 20:30 foo.txt
首先,删除文件 foo.txt,以此确定我们从新开始。下一步,运行不带参数的 umask命令,看一下当前的掩码值。响应的数值是0002(0022是另一个常用值),这个数值是掩码的八进制表示形式。下一步,我们创建文件 foo.txt,并且保留它的权限。
我们可以看到文件所有者和用户组都得到读权限和写权限,而其他人只是得到读权限。其他人没有得到写权限的原因是由掩码值决定的。重复我们的实验,这次自己设置掩码值:
shenbufandeAir:~ private$ rm foo.txt
shenbufandeAir:~ private$ umask 0000
shenbufandeAir:~ private$ > foo.txt
shenbufandeAir:~ private$ ls -l foo.txt
-rw-rw-rw- 1 private staff 0 6 10 20:32 foo.txt
当掩码设置为0000(实质上是关掉它)之后,我们看到其他人能够读写文件。为了弄明白这是怎么回事,我们需要看一下掩码的八进制形式。把掩码展开成二进制形式,然后与文件属性相比较,看看有什么区别.此刻先忽略掉开头的三个零(我们一会儿再讨论),注意掩码中若出现一个数字1,则删除文件模式中和这个1在相同位置的属性,在这是指其他人的写权限。这就是掩码要完成的任务。掩码的二进制形式中,出现数字1的位置,相应地关掉一个文件模式属性。看一下 掩码0022的作用:
Original file mode — rw- rw- rw-
Mask 000 000 010 010
Result — rw- r– r–
又一次,二进制中数字1出现的位置,相对应的属性被删除。再试一下其它的掩码值(一些带数字7的),习惯于掩码的工作原理。当你实验完成之后,要记得清理现场:
[me@linuxbox ~]$ rm foo.txt; umask 0002
大多数情况下,你不必修改掩码值,系统提供的默认掩码值就很好了。然而,在一些高安全级别下,你要能控制掩码值。
更改身份
在不同的时候,我们会发现很有必要具有另一个用户的身份。经常地,我们想要得到超级用户特权,来执行一些管理任务,但是也有可能”变为”另一个普通用户,比如说测试一个帐号。有三种方式,可以拥有多重身份:
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, 我们可以这样做:
[me@linuxbox ~]su -
Password:
[root@linuxbox ~]#
按下回车符之后,shell 提示我们输入超级用户的密码。如果密码输入正确,出现一个新的 shell 提示符, 这表明这个shell具有超级用户特权(提示符的末尾字符是”#”而不是”
su - Password: [root@linuxbox ~]# 按下回车符之后,shell 提示我们输入超级用户的密码。如果密码输入正确,出现一个新的 shell 提示符, 这表明这个shell具有超级用户特权(提示符的末尾字符是”#”而不是”
”),并且当前工作目录是超级用户的家目录 (通常是/root)。一旦进入一个新的shell,我们能执行超级用户所使用的命令。当工作完成后,输入”exit”,则返回到原来的 shell:
[root@linuxbox ~]# exit
[me@linuxbox ~]$
以这样的方式使用 su 命令,也可以只执行单个命令,而不是启动一个新的可交互的 shell:
su -c ‘command’
使用这种模式,命令传递到一个新shell中执行。把命令用单引号引起来很重要,因为我们不想命令在我们的shell 中展开,但需要在新 shell 中展开。
sudo - 以另一个用户身份执行命令
sudo 命令在很多方面都相似于 su 命令,但是 sudo 还有一些非常重要的功能。管理员能够配置sudo命令,从而允许一个普通用户以不同的身份(通常是超级用户),通过一种非常可控的方式来执行命令。尤其是,只有一个用户可以执行一个或多个特殊命令时,(更体现了 sudo 命令的方便性)。 另一个重要差异是 sudo 命令不要求超级用户的密码。使用 sudo 命令时,用户使用他/她自己的密码来认证。比如说,例如,sudo命令经过配置,允许我们运行一个虚构的备份程序,叫做”backup_script”,这个程序要求超级用户权限。通过 sudo 命令,这个程序会像这样运行:
按下回车键之后,shell提示我们输入我们的密码(不是超级用户的)。一旦认证完成,则执行具体的命令。su 和 sudo 之间的一个重要区别是 sudo 不会重新启动一个 shell,也不会加载另一个 用户的shell 运行环境。这意味者命令不必用单引号引起来。注意通过指定各种各样的选项,这种行为可以被推翻。详细信息,阅读 sudo 手册页。
想知道 sudo 命令可以授予哪些权限,使用”-l”选项,列出所有权限.
几年前,大多数的Linux发行版都依赖于su命令,来达到目的。su命令不需要sudo命令所要求的配置,su 命令拥有一个 root 帐号,是Unix中的传统。但这会引起问题。所有用户会企图以root用户帐号来操纵系统。事实上,一些用户专门以 root用户帐号来操作系统,因为这样做,的确消除了所有那些讨厌的“权限被拒绝”的消息。你这样做就会使得Linux系统的安全性能被降低到和Windows系统相同的级别。不是一个好主意。当引进Ubuntu的时候,它的创作者们采取了不同的策略。默认情况下,Ubuntu不允许用户登录到root 帐号(因为不能为 root 帐号设置密码),而是使用 sudo 命令授予普通用户超级用户权限。通过sudo 命令,最初的用户可以拥有超级用户权限,也可以授予随后的用户帐号相似的权力。
chown - 更改文件所有者和用户组
chown 命令被用来更改文件或目录的所有者和用户组。使用这个命令需要超级用户权限。chown命令的语法看起来像这样:
chown [owner][:[group]] file…
chown 可以根据这个命令的第一个参数更改文件所有者和/或文件用户组。这里有一些例子:
表: chown 参数实例
参数 结果
bob 把文件所有者从当前属主更改为用户 bob。
bob:users 把文件所有者改为用户 bob,文件用户组改为用户组 users。
:admins 把文件用户组改为组 admins,文件所有者不变。
bob: 文件所有者改为用户 bob,文件用户组改为用户 bob 登录系统时所属的用户组。
比方说,我们有两个用户,janet拥有超级用户访问权限,而tony没有。用户jant想要从她的家目录复制一个文件到用户tony的家目录。因为用户jant想要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。
chgrp - 更改用户组所有权
旧版unix系统中chown命令只能更改用户权限,所以有了chgrp命令。但目前chown可以完成。就不了解了。
练习使用权限
略。
更改用户密码
这一章最后一个话题,我们将讨论自己帐号的密码(和其他人的密码,如果你具有超级用户权限)。使用passwd 命令,来设置或更改用户密码。命令语法如下所示:
passwd [user]
例子
shenbufandeAir:~ root# passwd user1
Changing password for user1.
New password:
非root模式下,只要输入passwd命令,就能更改你的密码。shell会提示你输入你的旧密码和你的新密码:
shenbufandeAir:music user1$ passwd
Changing password for user1.
Old Password:
New Password:
Retype New Password:
Chapter 11 进程
通常,现在的操作系统都支持多任务,意味着操作系统通过在一个执行中的程序和另一个程序之间快速地切换造成了一种它同时能够做多件事情的假象。Linux 内核通过使用进程来管理多任务。进程,就是Linux 组织安排正在等待使用CPU的各种程序的方式。
有时候,计算机变得呆滞,运行缓慢,或者一个应用程序停止响应。 在这一章中,我们将看一些可用的命令行工具,这些工具帮助我们查看程序的执行状态,以及怎样终止行为不当的进程。
这一章将介绍以下命令:
- ps - 报告当前进程快照
- top - 显示任务
- jobs - 列出活跃的任务
- bg - 把一个任务放到后台执行
- fg - 把一个任务放到前台执行
- kill - 给一个进程发送信号
- killall - 杀死指定名字的进程
- shutdown - 关机或重启系统
进程是怎样工作的
当系统启动的时候,内核先把一些它自己的活动初始化为进程,然后运行一个叫做 init 的程序。 init, 依次地,再运行一系列的称为 init 脚本的 shell 脚本(位于/etc),它们可以启动所有的系统服务。其中许多系统服务以守护(daemon)程序的形式实现,守护程序仅在后台运行,没有任何用户界面。 这样,即使我们没有登录系统,至少系统也在忙于执行一些例行事务。
在进程方案中,一个程序可以发动另一个程序被表述为一个父进程可以产生一个子进程。
内核维护每个进程的信息,以此来保持事情有序。例如,系统分配给每个进程一个数字,这个数字叫做进程 ID 或 PID。 PID 号按升序分配,init进程的PID总是1。内核也对分配给每个进程的内存和就绪状态进行跟踪以便继续执行这个进程。 像文件一样,进程也有所有者和用户 ID,有效用户 ID,等等。
查看进程
查看进程,最常使用地命令(有几个命令)是 ps。 ps 程序有许多选项,它最简单地使用形式是这样的:
[me@linuxbox ~]$ ps
PID TTY TIME CMD
5198 pts/1 00:00:00 bash
10129 pts/1 00:00:00 ps
上例中,列出了两个进程,进程 5198 和进程 10129,各自代表命令 bash和ps。正如我们所看到的,默认情况下,ps不会显示很多进程信息,只是列出与当前终端会话相关的进程。为了得到更多信息,我们需要加上一些选项,但是在这样做之前,我们先看一下ps命令运行结果的其它字段。TTY是“Teletype”的简写,是指进程的控制终端。 这里,Unix 展示它的年龄。TIME字段表示进程所消耗的CPU时间数量。正如我们所看到的,这两个进程使计算机工作起来很轻松。
上例中,列出了两个进程,进程5198 和进程 10129,各自代表命令 bash 和 ps。正如我们所看到的, 默认情况下,ps 不会显示很多进程信息,只是列出与当前终端会话相关的进程。为了得到更多信息,我们需要加上一些选项,但是在这样做之前,我们先看一下 ps 命令运行结果的其它字段。 TTY 是“Teletype” 的简写,是指进程的控制终端。 这里,Unix 展示它的年龄。 TIME字段表示进程所消耗的CPU时间数量。 正如我们所看到的,这两个进程使计算机工作起来很轻松。
如果给 ps 命令加上选项,我们可以得到更多关于系统运行状态的信息:
[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” 的简写,它揭示了进程当前状态:
表: 进程状态
状态 含义
R 运行中。 这意味着,进程正在运行或准备运行。
S 正在睡眠。 进程没有运行,而是,正在等待一个事件, 比如说,一个按键或者网络分组。
D 不可中断睡眠。 进程正在等待 I/O,比方说,一个磁盘驱动器的 I/O。
T 已停止. 已经指示进程停止运行。 稍后介绍更多。
Z 一个死进程或“僵尸”进程。 这是一个已经终止的子进程,但是它的父进程还没有清空它。 (父进程没有把子进程从进程表中删除)
< 一个高优先级进程。 这可能会授予一个进程更多重要的资源,给它更多的 CPU 时间。进程的这种属性叫做 niceness。 具有高优先级的进程据说是不好的(less nice),因为它占用了比较多的 CPU 时间,这样就给其它进程留下很少时间。
N 低优先级进程。 一个低优先级进程(一个“好”进程)只有当其它高优先级进程被服务了之后,才会得到处理器时间。
进程状态信息之后,可能还跟随其他的字符。 这表示各种外来进程的特性。 详细信息请看 ps 手册页。
另一个流行的选项组合是 “aux”(不带开头的”-“字符)。 这会给我们更多信息:
[me@linuxbox ~]$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2136 644 ? Ss Mar05 0:31 init
root 2 0.0 0.0 0 0 ? S< Mar05 0:00 [kt]
and many more...
这个选项组合,能够显示属于每个用户的进程信息。 使用这个选项,可以唤醒 “BSD 风格” 的输出结果。Linux版本的ps命令,可以模拟几个不同Unix版本中的ps程序的行为。通过这些选项,我们得到这些额外的列。
表: BSD 风格的 ps 命令列标题
标题 含义
USER 用户 ID. 进程的所有者。
%CPU 以百分比表示的 CPU 使用率
%MEM 以百分比表示的内存使用率
VSZ 虚拟内存大小
RSS 进程占用的物理内存的大小,以千字节为单位。
START 进程启动的时间。 若它的值超过24小时,则用天表示。
用top命令动态查看进程
虽然 ps 命令能够展示许多计算机运行状态的信息,但是它只是提供ps命令执行时刻的机器状态快照。为了看到更多动态的信息,我们使用 top 命令:
top程序以进程活动顺序显示连续更新的系统进程列表。(默认情况下,每三秒钟更新一次),”top”这个名字 来源于 top 程序是用来查看系统中“顶端”进程的。top显示结果由两部分组成:最上面是系统概要,下面是进程列表,以 CPU 的使用率排序。
Processes: 388 total, 2 running, 386 sleeping, 1891 threads 22:33:38
Load Avg: 8.65, 8.38, 8.62 CPU usage: 30.14% user, 12.61% sys, 57.24% idle
SharedLibs: 200M resident, 47M data, 58M linkedit.
MemRegions: 210465 total, 2977M resident, 95M private, 445M shared.
PhysMem: 8142M used (1513M wired), 49M unused.
VM: 1725G vsize, 1088M framework vsize, 15758150(0) swapins, 16673787(0) swapouts.
Networks: packets: 26931677/34G in, 20075248/4750M out.
Disks: 9656463/177G read, 6613470/167G written.
PID COMMAND %CPU TIME #TH #WQ #PORTS MEM PURG CMPRS PGRP PPID STATE
47983 top 5.6 00:06.18 1/1 0 23 3736K 0B 0B 47983 47002 running
47949 mdworker 0.0 00:00.07 3 1 51 3132K 0B 0B 47949 1 sleeping
47945 mdworker 0.0 00:00.08 3 1 51 3068K 0B 0B 47945 1 sleeping
47912 quicklookd 0.0 00:00.11 5 2 77+ 3412K+ 32K 0B 47912 1 sleeping
47738 mdworker 0.0 00:00.08 3 1 51 3092K 0B 0B 47738 1 sleeping
47731 mdworker 0.0 00:00.08 3 1 52 3184K 0B 0B 47731 1 sleeping
47730 mdworker 0.0 00:00.07 3 1 52 3080K 0B 0B 47730 1 sleeping
47574 mdworker 0.0 00:00.10 3 1 51 3128K 0B 0B 47574 1 sleeping
47534 mdworker 0.0 00:00.09 3 1 54 3424K 0B 0B 47534 1 sleeping
47522 mdworker 0.0 00:00.07 3 1 51 3196K 0B 0B 47522 1 sleeping
47047 AGSService 0.0 00:00.06 2 1 37 1700K 0B 0B 47047 1 sleeping
47002 bash 0.0 00:00.05 1 0 19 888K 0B 0B 47002 47001 sleeping
47001 login 0.0 00:00.07 2 1 30 2016K 0B 0B 47001 1376 sleeping
46905 MTLCompilerS 0.0 00:00.05 2 2 19 12K 0B 5624K 46905 1 sleeping
46783 com.apple.We 0.0 00:00.54 6 1 167 5868K 0B 6208K 46783 1 sleeping
46782 com.apple.We 0.0 00:00.17 5 1 84 2016K 0B 1764K 46782 1 sleeping
46781 com.apple.Sa 0.0 00:00.08 2 1 42 960K 0B 1064K 46781 1 sleeping
46598 CoreServices 0.0 00:00.36 3 1 142 2344K 0B 1040K 46598 1 sleeping
46261 mdworker 0.0 00:00.09 3 1 42 808K 0B 2156K 46261 1 sleeping
46240 mdworker 0.0 00:00.97 4 1 56 12M 0B 5164K 46240 1 sleeping
...
其中系统概要包含许多有用信息。 下表是对系统概要的说明:
表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: 展示交换分区(虚拟内存)的使用情况。
ps:以上例子为MAC OXS,表中为linux。所以有出入。
top 程序接受一系列从键盘输入的命令。两个最有趣的命令是 h 和 q。h,显示程序的帮助屏幕,q, 退出 top 程序。
两个主要的桌面环境都提供了图形化应用程序,来显示与top程序相似的信息(和Windows中的任务管理器差别不多),但是我觉得 top 程序要好于图形化的版本,因为它运行速度快,并且消费很少的系统资源。毕竟,我们的系统监测程序不能成为 我们试图追踪的系统怠工的原因。
控制进程
现在我们可以看到和监测进程,让我们得到一些对它们的控制权。为了我们的实验,我们将使用一个叫做xlogo 的小程序,作为我们的实验品。这个 xlogo 程序是X窗口系统(使图形界面显示在屏幕上的底层引擎)提供的示例程序,这个程序仅显示一个大小可调的包含X标志的窗口。首先,我们需要知道测试的实验对象:
中断一个进程
Ctrl-c
通过这个技巧,许多(但不是全部)命令行程序可以被中断。
把一个进程放置到后台(执行)
假如说我们想让 shell 提示符返回,却不终止xlogo程序。我们可以把这个程序放到后台执行。把终端想象是一个有前台(包含在表层可见的事物,像shell提示符)和后台(包含表层之下的隐藏的事物)(的设备)。为了启动一个程序并让它立即在后台 运行,我们在程序命令之后,加上”&”字符:
shenbufandeMacBook-Air:~ private$ top &
[1] 50128
shenbufandeMacBook-Air:~ private$ jobs
[1]+ Stopped top
执行命令之后,这个 xlogo 窗口出现,并且 shell 提示符返回,同时打印一些有趣的数字。 这条信息是 shell 特性的一部分,叫做任务控制。通过这条信息,shell 告诉我们,已经启动了 任务号为1(“[1]”),PID 为50128的程序。
shell 的任务控制功能给出了一种列出从我们终端中启动了的任务的方法。执行jobs命令,我们可以看到这个输出列表:
[me@linuxbox ~]$ jobs
[1]+ Running xlogo &
结果显示我们有一个任务,编号为“1”,它正在运行,并且这个任务的命令是 xlogo &。
进程返回到前台
一个在后台运行的进程对一切来自键盘的输入都免疫,也不能用 Ctrl-c 来中断它。
为了让一个进程返回前端,这样使用 fg 命令:
[me@linuxbox ~]jobs
[1]+ Running xlogo &
[me@linuxbox ~]
jobs [1]+ Running xlogo & [me@linuxbox ~]
fg %1
xlogo
fg命令之后,跟随着一个百分号和任务序号(叫做jobspec)就可以了。如果我们只有一个后台任务,那么 jobspec 是可有可无的。输入 Ctrl-c 来终止 xlogo 程序。
停止一个进程
有时候,我们想要停止一个进程,而不是终止它。我们这么做通常是为了允许前台进程被移动到后台。 输入 Ctrl-z,可以停止一个前台进程。让我们试一下。在命令提示符下,执行 xlogo 命令, 然后输入 Ctrl-z:
[me@linuxbox ~]
xlogo[1]+Stoppedxlogo[me@linuxbox ]
x
l
o
g
o
[
1
]
+
S
t
o
p
p
e
d
x
l
o
g
o
[
m
e
@
l
i
n
u
x
b
o
x
]
使用 fg 命令,可以恢复程序到前台运行,或者用 bg 命令把程序移到后台。
[me@linuxbox ~]bg %1
[1]+ xlogo &
[me@linuxbox ~]
bg %1 [1]+ xlogo & [me@linuxbox ~]
和 fg 命令一样,如果只有一个任务的话,jobspec参数是可选的。如果我们从命令行启动一个图形程序,但是忘了在命令后加字符 “&”, 将一个进程从前台移动到后台也是很方便的。
为什么要从命令行启动一个图形界面程序呢?有两个原因。第一个,你想要启动的程序,可能没有在窗口管理器的菜单中列出来(比方说 xlogo)。第二个,从命令行启动一个程序,你能够看到一些错误信息,如果从图形界面中运行程序的话,这些信息是不可见的。有时候,一个程序不能从图形界面菜单中启动。通过从命令行中启动它,我们可能会看到能揭示问题的错误信息。一些图形界面程序还有许多有意思并且有用的命令行选项。
Signals
kill 命令被用来“杀死”程序。这样我们就可以终止需要杀死的程序。
虽然这个命令看上去很直白, 但是它的含义不止于此。这个 kill 命令不是真的“杀死”程序,而是给程序发送信号。信号是操作系统与程序之间进行通信时所采用的几种方式中的一种。 在使用 Ctrl-c 和 Ctrl-z 的过程中我们已经看到信号的实际用法。当终端接受了其中一个按键组合后,它会给在前端运行的程序发送一个信号。在使用 Ctrl-c 的情况下,会发送一个叫做 INT(中断)的信号;当使用 Ctrl-z 时,则发送一个叫做 TSTP(终端停止)的信号。程序,相应地,倾听信号的到来,当程序接到信号之后,则做出响应。一个程序能够倾听和响应信号这件事允许一个程序做些事情,比如,当程序接到一个终止信号时,它可以保存所做的工作。
通过kill命令给进程发送信号
kill [-signal] PID…
如果在命令行中没有指定信号,那么默认情况下,发送TERM(终止)信号。kill命令被经常用来发送以下命令:
表 : 常用信号
编号 名字 含义
1 HUP 挂起。这是美好往昔的残留部分,那时候终端机通过电话线和调制解调器连接到远端的计算机。这个信号被用来告诉程序,控制的终端机已经“挂起”。通过关闭一个终端会话,可以展示这个信号的作用。在当前终端运行的前台程序将会收到这个信号并终止。
许多守护进程也使用这个信号,来重新初始化。这意味着,当一个守护进程收到这个信号后,这个进程会重新启动,并且重新读取它的配置文件。Apache 网络服务器守护进程就是一个例子。
2 INT 中断。实现和 Ctrl-c 一样的功能,由终端发送。通常,它会终止一个程序。
9 KILL 杀死。这个信号很特别。尽管程序可能会选择不同的方式来处理发送给它的 信号,其中也包含忽略信号,但是 KILL 信号从不被发送到目标程序。而是内核立即终止 这个进程。当一个进程以这种方式终止的时候,它没有机会去做些“清理”工作,或者是保存工作。 因为这个原因,把 KILL 信号看作最后一招,当其它终止信号失败后,再使用它。
15 TERM 终止。这是 kill 命令发送的默认信号。如果程序仍然“活着”,可以接受信号,那么 这个它会终止。
18 CONT 继续。在一个停止信号后,这个信号会恢复进程的运行。
19 STOP 停止。这个信号导致进程停止运行,而不是终止。像 KILL 信号,它不被 发送到目标进程,因此它不能被忽略。
进程,和文件一样,拥有所有者,所以为了能够通过kill命令来给进程发送信号,你必须是进程的所有者(或者是超级用户)。
除了上表列出的 kill 命令最常使用的信号之外,还有一些系统频繁使用的信号。以下是其它一些常用 信号列表:
表: 其它常用信号
编号 名字 含义
3 QUIT 退出
11 SEGV 段错误。如果一个程序非法使用内存,就会发送这个信号。也就是说, 程序试图写入内存,而这个内存空间是不允许此程序写入的。
20 TSTP 终端停止。当按下 Ctrl-z 组合键后,终端发送这个信号。不像 STOP 信号, TSTP 信号由目标进程接收,且可能被忽略。
28 WINCH 改变窗口大小。当改变窗口大小时,系统会发送这个信号。 一些程序,像 top 和 less 程序会响应这个信号,按照新窗口的尺寸,刷新显示的内容。
为了满足读者的好奇心,通过下面的命令可以得到一个完整的信号列表:
kill -l
通过 killall 命令给多个进程发送信号
也有可能通过killall命令,给匹配特定程序或用户名的多个进程发送信号。下面是killall命令的语法形式:
为了说明情况,我们将启动一对 xlogo 程序的实例,然后再终止它们:
[me@linuxbox ~]xlogo &
[1] 18801
[me@linuxbox ~]
xlogo & [1] 18801 [me@linuxbox ~]
xlogo &
[2] 18802
[me@linuxbox ~]$ killall xlogo
[1]- Terminated xlogo
[2]+ Terminated xlogo
更多和进程相关的命令
因为监测进程是一个很重要的系统管理任务,所以有许多命令与它相关。玩玩下面几个命令:
表: 其它与进程相关的命令
命令名 命令描述
pstree 输出一个树型结构的进程列表,这个列表展示了进程间父/子关系。
vmstat 输出一个系统资源使用快照,包括内存,交换分区和磁盘 I/O。 为了看到连续的显示结果,则在命令名后加上更新操作延时的时间(以秒为单位)。例如,“vmstat 5”。 ,按下 Ctrl-c 组合键, 终止输出。
xload 一个图形界面程序,可以画出系统负载随时间变化的图形。
tload 与 xload 程序相似,但是在终端中画出图形。使用 Ctrl-c,来终止输出。