Linux中Load average的理解

Load average的定义
系统平均负载被定义为在特定时间间隔内运行队列中的平均进程树。如果一个进程满足以下条件则其就会位于运行队列中:
- 它没有在等待I/O操作的结果
- 它没有主动进入等待状态(也就是没有调用’wait’)
- 没有被停止(例如:等待终止)

load average如何计算
为了使内核可以高效计算load average,采用了fixed-point arithmetic。fixed-point arithmetic是一种非常快速的模拟浮点运算的方法,特别是在没有FPU(float point unit)部件的处理器上,非常有用。

计算公式:load(t) = load(t-1) e^(-5/60) + n (1 - e^(-5/60)),迭代计算,其中n为run-queue length。

由Exponential Smoothing方程有,Y(t)= Y(t-1) + a*[X(t) - Y(t-1)],whereX(t) is the input raw data, Y(t - 1) is the value due to the previoussmoothing iteration and Y(t) is the new smoothed value.令a=1-b,b为e^(-5/60),就可以得到load average的计算公式。

采用此公式的好处:局部的load抖动不会对load average造成重大影响,使其平滑。

load average释疑
一般来说只要每个CPU的当前活动进程数不大于2那么系统的性能就是良好的,如果每个CPU的任务数大于5,那么就表示这台机器的性能有严重问题。

假设系统有两个CPU,那么其每个CPU的当前任务数为:Load average/2。这时候取得的数值可以参照上述2-5的标准来进行判断了。

Filled Under: 未分类

数据传输命令scp or rsync

rsync -apur –partial -e “ssh -p 22 -c arcfour” SRC DEST
scp -P 22 -c arcfour SRC DEST

Filled Under: 未分类

shell 批量 替换

grep something_old -rl ./ | xargs sed -i “s/something_old/something_new/g”

Filled Under: 未分类

cassandra初体验

http://incubator.apache.org/cassandra/media/img/cassandra_logo.png

A highly scalable, eventually consistent, distributed, structured key-value store.

一个高度可扩展、最終一致、分布式和结构化key-value儲存方案。

下载

http://incubator.apache.org/cassandra/download/ 选择二进制包下载,包名类似 apache-cassandra-incubating-x.y.z-bin.tar.gz 。

设置和运行

为了方便,下面所有命令以 root身份执行。

以一个独立节点运行

tar -zxvf cassandra-$VERSION.tar.gz
mv cassandra-$VERSION /opt/cassandra

sudo mkdir -p /var/log/cassandra
sudo mkdir -p /var/lib/cassandra

echo 'alias PATH=$PATH:/opt/cassandra/bin/' >> ~/.bashrc && source ~/.bashrc

创建start- cassandra.sh

/opt/cassandra/bin/cassand

创建stop- cassandra.sh

kill `ps aux |  fgrep  $USER | grep cassandra | grep -v 'grep' | awk '{print $2}'`

创建show- cassandra.sh

#/bin/bash
echo "cassandra PID: `ps aux |  fgrep  $USER | grep cassandra | grep -v 'grep' | awk '{print $2}'`"

以后台方式运行节点:

./start-cassandra.sh

或以前台方式运行:

cassandra -f

测试

cassandra-cli --host localhost --port 9160

如果成功会显示:

  Connected to localhost/9160
  Welcome to cassandra CLI.

  Type 'help' or '?' for help. Type 'quit' or 'exit' to quit.
  cassandra>

尝试写和读键:

set Keyspace1.Standard1['shugelee']['first'] = 'Lee'
set Keyspace1.Standard1['shugelee']['last'] = 'Li'
set Keyspace1.Standard1['shugelee']['age'] = '21'

get Keyspace1.Standard1['shugelee']

結果类似:

  (column=last, value=li; timestamp=1263050140529)
  (column=first, value=lee; timestamp=1263050117638)
  (column=age, value=18; timestamp=1263050155638)
Returned 3 rows.

以一个簇(集群)运行

假定两台debian均按 上面安装并初步设置了cassandra环境:

  • A 192.168.1.101 [development server]
  • B 192.168.1.105 [developer Lee Li]

将A 192.168.1.101 [development server]作为主server运行。

development server的设置

    <Seed>127.0.0.1</Seed>

改为:

    <Seed>192.168.1.101</Seed>

    <ListenAddress>localhost</ListenAddress>

改为:

    <ListenAddress>192.168.1.105</ListenAddress>

    <ThriftAddress>localhost</ThriftAddress>

改为:

    <ThriftAddress>0.0.0.0</ThriftAddress>

developer Lee Li的设置

    <Seed>127.0.0.1</Seed>

改为:

    <Seed>192.168.1.101</Seed>
    <Seed>192.168.1.105</Seed>

    <ListenAddress>localhost</ListenAddress>

改为:

    <ListenAddress>192.168.1.101</ListenAddress>

    <ThriftAddress>localhost</ThriftAddress>

改为:

    <ThriftAddress>0.0.0.0</ThriftAddress>

分别运行A和B上的 cassandra:

canssandra -f

分别在A和B上测试:

nodeprobe -host 192.168.1.101 ring

如果成功,結果类似:

DEBUG - Loading settings from /opt/cassandra/bin/../conf/storage-conf.xml
DEBUG - Syncing log with a period of 1000
Starting Token                                 Ending Token                                 Size Address        Ring
132617574668126261121070408499066554197        127319937893509951017249225297128612859         1 192.168.1.101  |<--|
127319937893509951017249225297128612859        132617574668126261121070408499066554197         1 192.168.1.105  |-->|

分别在A和B上测试链接到一个 node:

cassandra-cli  --host 192.168.1.105 --port 9160

如果成功,結果类似:

# cassandra-cli  --host 192.168.1.105 --port 9160
Connected to 192.168.1.105/9160
Welcome to cassandra CLI.

Type 'help' or '?' for help. Type 'quit' or 'exit' to quit.
cassandra>

测试清单:

  • A连接到自己的cassandra
  • A连接到B的cassandra
  • B连接到自己的cassandra
  • B连接到A的cassandra
  • A连接到自己的cassandra,写入并读取键值
  • A连接到B的cassandra,写入并读取键值
  • B连接到自己的cassandra,写入并读取键值
  • B连接到A的cassandra,写入并读取键值

测试清单二:

  • B 停止Cassandra服务,B连接到A并写入数据,重启B的Cassandra服务,B连接到自身的Casssandra,查看刚刚在A写入的数据(以 -f方式运行观察,B重启时,立即自动与A同步!非常好!)

参考链接

- http://wiki.apache.org/cassandra/GettingStarted

Filled Under: it技术

UML类图关系大全


1、关联

双向关联:
C1-C2:指双方都知道对方的存在,都可以调用对方的公共属性和方法。

在 GOF的设计模式书上是这样描述的:虽然在分析阶段这种关系是适用的,但我们觉得它对于描述设计模式内的类关系来说显得太抽象了,因为在设计阶段关联关系 必须被映射为对象引用或指针。对象引用本身就是有向的,更适合表达我们所讨论的那种关系。所以这种关系在设计的时候比较少用到,关联一般都是有向的。

使用ROSE 生成的代码是这样的:

class C1
…{
public:
C2* theC2;

};

class C2
…{
public:
C1* theC1;

};

双向关联在代码的表现为双方都拥有对方的一个指针,当然也可以是引用或者是值。

单向关联:
C3->C4:表示相识关系,指C3知道C4,C3可以调用C4的公共属性和方法。没有生命期的依赖。一般是表示为一种引用。

生成代码如下:

class C3
…{
public:
C4* theC4;

};

class C4
…{

};

单向关联的代码就表现为C3有C4的指针,而C4对C3一无所知。

自身关联(反身关联):
自己引用自己,带着一个自己的引用。

代码如下:

class C14
…{
public:
C14* theC14;

};

就是在自己的内部有着一个自身的引用。

2、聚合/组合

当类之间有整体-部分关系的时候,我们就可以使用组合或者聚合。

聚合:表示C9聚合C10,但是C10可以离开C9而独立存在(独立存在的意思是在某个应用的问题域中这个类的存在有意义。这句话怎么解,请看下面组合里的解释)。

代码如下:

class C9
…{
public:
C10 theC10;

};

class C10
…{

};

组 合(也有人称为包容):一般是实心菱形加实线箭头表示,如上图所示,表示的是C8被C7包容,而且C8不能离开C7而独立存在。但这是视问题域而定的,例 如在关心汽车的领域里,轮胎是一定要组合在汽车类中的,因为它离开了汽车就没有意义了。但是在卖轮胎的店铺业务里,就算轮胎离开了汽车,它也是有意义的, 这就可以用聚合了。在《敏捷开发》中还说到,A组合B,则A需要知道B的生存周期,即可能A负责生成或者释放B,或者A通过某种途径知道B的生成和释放。

他们的代码如下:

class C7
…{
public:
C8 theC8;

};

class C8
…{
};

可以看到,代码和聚合是一样的。具体如何区别,可能就只能用语义来区分了。

3、依赖

依赖:
指C5可能要用到C6的一些方法,也可以这样说,要完成C5里的所有功能,一定要有C6的方法协助才行。C5依赖于C6的定义,一般是在C5类的头文件中包含了C6的头文件。ROSE对依赖关系不产生属性。

注意,要避免双向依赖。一般来说,不应该存在双向依赖。

ROSE生成的代码如下:

// C5.h
#include ”C6.h”

class C5
…{

};

// C6.h
#include ”C5.h”

class C6
…{

};

虽然ROSE不生成属性,但在形式上一般是A中的某个方法把B的对象作为参数使用(假设A依赖于B)。如下:

#include ”B.h”
class A
…{
void Func(B &b);
}

那依赖和聚合\组合、关联等有什么不同呢?

关联是类之间的一种关系,例如老师教学生,老公和老婆,水壶装水等就是一种关系。这种关系是非常明显的,在问题领域中通过分析直接就能得出。

依 赖是一种弱关联,只要一个类用到另一个类,但是和另一个类的关系不是太明显的时候(可以说是“uses”了那个类),就可以把这种关系看成是依赖,依赖也 可说是一种偶然的关系,而不是必然的关系,就是“我在某个方法中偶然用到了它,但在现实中我和它并没多大关系”。例如我和锤子,我和锤子本来是没关系的, 但在有一次要钉钉子的时候,我用到了它,这就是一种依赖,依赖锤子完成钉钉子这件事情。

组合是一种整体-部分的关系,在问题域中这种关系很明显,直接分析就可以得出的。例如轮胎是车的一部分,树叶是树的一部分,手脚是身体的一部分这种的关系,非常明显的整体-部分关系。

上述的几种关系(关联、聚合/组合、依赖)在代码中可能以指针、引用、值等的方式在另一个类中出现,不拘于形式,但在逻辑上他们就有以上的区别。

这 里还要说明一下,所谓的这些关系只是在某个问题域才有效,离开了这个问题域,可能这些关系就不成立了,例如可能在某个问题域中,我是一个木匠,需要拿着锤 子去干活,可能整个问题的描述就是我拿着锤子怎么钉桌子,钉椅子,钉柜子;既然整个问题就是描述这个,我和锤子就不仅是偶然的依赖关系了,我和锤子的关系 变得非常的紧密,可能就上升为组合关系(让我突然想起武侠小说的剑不离身,剑亡人亡…)。这个例子可能有点荒谬,但也是为了说明一个道理,就是关系和 类一样,它们都是在一个问题领域中才成立的,离开了这个问题域,他们可能就不复存在了。

4、泛化(继承)

泛化关系:如果两个类存在泛化的关系时就使用,例如父和子,动物和老虎,植物和花等。
ROSE生成的代码很简单,如下:

#include ”C11.h”

class C12 : public C11
…{
};

5、这里顺便提一下模板

上面的图对应的代码如下:

template<int>
class C13
…{
};

这里再说一下重复度,其实看完了上面的描述之后,我们应该清楚了各个关系间的关系以及具体对应到代码是怎么样的,所谓的重复度,也只不过是上面的扩展,例如A和B有着“1对多”的重复度,那在A中就有一个列表,保存着B对象的N个引用,就是这样而已。

好了,到这里,已经把上面的类图关系说完了,希望你能有所收获了,我也费了不少工夫啊(画图、生成代码、截图、写到BLOG上,唉,一头大汗)。不过如果能让你彻底理解UML类图的这些关系,也值得了。:)

+++++++++++++++++++++++++++++++++++++++++++++++++++++

在UML建模中,对类图上出现元素的理解是至关重要的。开发者必须理解如何将类图上出现的元素转换到Java中。以java为代表结合网上的一些实例,下面是个人一些基本收集与总结:
基本元素符号:
1. 类(Classes)
类包含3个组成部分。第一个是Java中定义的类名。第二个是属性(attributes)。第三个是该类提供的方法。
属性和操作之前可附加一个可见性修饰符。加号(+)表示具有公共可见性。减号(-)表示私 有可见性。#号表示受保护的可见性。省略这些修饰符表示具有package(包)级别的可见性。如果属性或操作具有下划线,表明它是静态的。在操作中,可 同时列出它接受的参数,以及返回类型,如下图所示:

2. 包(Package)
包是一种常规用途的组合机制。UML中的一个包直接对应于Java中的一个包。在Java 中,一个包可能含有其他包、类或者同时含有这两者。进行建模时,你通常拥有逻辑性的包,它主要用于对你的模型进行组织。你还会拥有物理性的包,它直接转换 成系统中的Java包。每个包的名称对这个包进行了惟一性的标识。

3. 接口(Interface)
接口是一系列操作的集合,它指定了一个类所提供的服务。它直接对应于Java中的一个接口 类型。接口既可用下面的那个图标来表示(上面一个圆圈符号,圆圈符号下面是接口名,中间是直线,直线下面是方法名),也可由附加 了<<interface>>的一个标准类来表示。通常,根据接口在类图上的样子,就能知道与其他类的关系。

关 系:
1. 依赖(Dependency)
实体之间一个“使用”关系暗示一个实体的规范发生变化后,可能影响依赖于它的其他实例。更 具体地说,它可转换为对不在实例作用域内的一个类或对象的任何类型的引用。其中包括一个局部变量,对通过方法调用而获得的一个对象的引用(如下例所示), 或者对一个类的静态方法的引用(同时不存在那个类的一个实例)。也可利用“依赖”来表示包和包之间的关系。由于包中含有类,所以你可根据那些包中的各个类 之间的关系,表示出包和包的关系。

2. 关联(Association)
实体之间的一个结构化关系表明对象是相互连接的。箭头是可选的,它用于指定导航能力。如果 没有箭头,暗示是一种双向的导航能力。在Java中,关联转换为一个实例作用域的变量,就像图E的“Java”区域所展示的代码那样。可为一个关联附加其 他修饰符。多重性(Multiplicity)修饰符暗示着实例之间的关系。在示范代码中,Employee可以有0个或更多的TimeCard对象。但 是,每个TimeCard只从属于单独一个Employee。

3. 聚合(Aggregation)
聚合是关联的一种形式,代表两个类之间的整体/局部关系。聚合暗示着整体在概念上处于比局部更高的一个级别,而关联暗示两个类在概念上位于相同的级别。聚合也转换成Java中的一个实例作用域变量。
关联和聚合的区别纯粹是概念上的,而且严格反映在语义上。聚合还暗示着实例图中不存在回路。换言之,只能是一种单向关系。

4. 合成(Composition)
合成是聚合的一种特殊形式,暗示“局部”在“整体”内部的生存期职责。合成也是非共享的。所以,虽然局部不一定要随整体的销毁而被销毁,但整体要么负责保持局部的存活状态,要么负责将其销毁。
局部不可与其他整体共享。但是,整体可将所有权转交给另一个对象,后者随即将承担生存期职责。Employee和TimeCard的关系或许更适合表示成“合成”,而不是表示成“关联”。

5. 泛化(Generalization)
泛化表示一个更泛化的元素和一个更具体的元素之间的关系。泛化是用于对继承进行建模的UML元素。在Java中,用extends关键字来直接表示这种关系。

6. 实现(Realization)
实例关系指定两个实体之间的一个合同。换言之,一个实体定义一个合同,而另一个实体保证履行该合同。对Java应用程序进行建模时,实现关系可直接用implements关键字来表示。

像聚合还分为:非共享聚合、共享聚合、复合聚合等。以及其它内容,下次再补充。

Filled Under: it技术

看看别人的26岁!

这是一封同事的群发邮件,我很认真的看完了他,也很认真了看了看自己的现状!

 

1, 邓小平26岁时,领导百色起义,创建中国工农红军第七军、第八军。
2, 毛泽东26岁时,在湖南创建共产主义组织,并参加革命。
3, 周恩来26岁时,巴黎回国归来,在黄埔军校任政治部主任,国民革命军第一军政

部主任。
4, 蒋介石26岁时,在参加二次革命,并加入中华革命党。
5, 曹操26岁时,担任洛阳北部尉,相当于北京市副公安局局长。
6, 周瑜26岁时,已经当任建威中郎将。地方高级将领。
7, 诸葛亮26岁时,被刘备三顾茅庐,出山从政。
8, 美国总统林肯26岁时,已经通过自学成为律师,并被选为州议员。
9, 奥巴马26岁时,在哈佛大学的法学院读博士。
10,胡锦涛26岁时,在水电部刘家峡工程局房建队劳动。
11,温家宝26岁时,在甘肃省地质局地质力学队当技术员。
12,周杰伦26岁时,已经凭《七里香》红遍大江南北,并在同年拍了《头文字D》
13,王石26岁时,在兰州铁道学院读本科。
14,潘石屹26岁时,去海南开办了自己的房地产企业,在海南的房地产泡沫中大赚一
笔。
15,柳传志26岁时,在广东珠海白藤农场劳动锻炼,并于同年进入中科院计算技术研

所任助理研究员。
16,成龙26岁时,已经拍了N多电影,并于2年后成功进入好莱坞市场。
17,李鸿章26岁时,已经考中二甲第十三名进士,相当于全国第26名,并写下了“一万
年来谁著史,三千里外欲封侯”的名句。
18,曾国藩26岁时,在不断考公务员,不过运气很差,科举屡次不中,2年后中进士。

19,胡雪岩26岁时,在小钱庄当伙计,10年后,终于开了自己的银行-阜康钱庄。
20,盛宣怀26岁时,通过招聘进入李鸿章手下当幕府,3年后开办轮船招商局,成为洋
务运动的核心人物。
21,李白26岁时,通过倒插门,娶了前宰相许围师的孙女,并生下一男一女。
22,杜甫26岁时,不无正业,在全国巡回旅游,史书记载“客吴越、齐赵间”。后又
“举进士不中第,困长安”,考公务员也没考上,工作也不好找,后来被皇帝发现了,
才有好日子过了。
23,唐伯虎26岁时,在家里发奋读书,三年后省公务员考试得第一名,即中“解元”
24,巴菲特26岁时,成立了巴菲特聯合有限公司,他仅仅出资100美元,而其他7位合

人则出资105000美元。他狂妄的说:要在30岁之前成为百万富翁,否则就从奥马哈最

的建筑上跳下去。事实证明,他确实做到了。
25,格林斯潘26岁时,已经获得经济学硕士学位,这一年他结婚了,估计还没有找到

式工作,不过2年后他加入汤森公司,不到5年,就拥有该公司一半股权,并出任总
裁。

26,索罗斯26岁时,带着他的全部积蓄5000美元,在纽约当了一名套利交易员。17年
后,他创立的量子基金。
27,马克思26岁时,马克思会见了恩格斯,并完成了《哲学经济学手稿》,1年后被法
国政府驱逐出境。
28,恩格斯26岁时,和马克思在布鲁塞尔创建共产主义通迅委员会,1年后发表著名的
《共产主义宣言》。
29,列宁26岁时,在彼得堡创立了彼得堡工人解放协会,年底被捕入狱。并被流放到
西
伯利亚。
30,斯大林26岁时,参加革命活动而被捕7次,多次被流放和监禁,所以他不是在革命
就是在坐牢。斯大林的意思叫做“铁人”,果然名不虚传。
31,普京26岁时,在苏联的克格勃当特工,从事神秘的对外情报局工作。
32,叶挺26岁时,在孙中山陆海军大元帅府警卫团第二营营长。
33,彭德怀26岁时,在湘军当任连长或营长。
34,萨科奇26岁时,在当律师(已经取得硕士学位,那么年轻?),2年后当选讷伊市
市长。
35,克林顿26岁时,在耶鲁大学法学院攻读法学博士,4年后出任阿肯色州司法部长。

36,布什26岁时,在得克萨斯国民警卫队空军担任战斗机飞行员。
37,尼克松26岁时,在加利福尼亚州惠特尔当律师。
38,关羽26岁时,应该在卖红枣。
39,朱元璋26岁时,参加红巾军起义,并成为该军事力量的实际领导人。
40,成吉思汗26岁时,在为统一草原而战斗。
41,爱迪生26岁时,发明了2重电报机,10年后他发明了灯泡。
42,比尔盖茨26岁时,已经创立微软公司6年了,39岁就成为了世界首富。
43,贝多芬26岁时,听力失聪,但是他扼住了命运的咽喉,最终5年后创作了著名的
《命运交响曲》。
44,黄光裕26岁时,已经创办国美电器3年了,这一年国美电器连锁店已经开了10家。

45,张朝阳26岁时,在美国麻省理工学院攻读博士学位。
46,牛根生26岁时,任内蒙古伊利集团(原呼和浩特回民奶食品厂)厂长。
47,马化腾26岁时,在当软件工程师,1年后创办腾讯计算机系统有限公司。
48,张艺谋26岁时,在陕西咸阳国棉八厂当工人。
49,袁隆平26岁时,在湖南安江农校教书。
50,现在,大多数人26岁时,在找工作。。。

Filled Under: 个人生活

查看静态或动态库

Linux下动态库查看方法:nm -D libavformat.so

Linux下静态库查看方法:ar -t libavformat.a

 

-----------------------

此外查看程序所连接的动态库可以

ldd program

Filled Under: it技术

google要玩退出!it民工怎么办?

今天知道了谷歌要从中国退出的消息!我一开始以为是假的,怎么可能?!
这可是十几亿人口的市场啊!
可是我去客方博客读了它的原文后,我真的知道了,知道了谷歌总部是来真的了。
就像它上面写的在法律的框架下与中国政府达成一致,在几周内。
不可能!
那么谷歌就真的好退出吗?
有人已经到北京的谷歌总部去献花了!
--真汉子!
身为一名it民工,如果真的没有google我们应该怎么办呢,邮件都用gmail,办公用docs,等等。
杯具啊!

Filled Under: 未分类

linux 查看系统信息命令集合

系统

# uname -a               # 查看内核/操作系统/CPU信息
# head -n 1 /etc/issue   # 查看操作系统版本
# cat /proc/cpuinfo      # 查看CPU信息
# hostname               # 查看计算机名
# lspci -tv              # 列出所有PCI设备
# lsusb -tv              # 列出所有USB设备
# lsmod                  # 列出加载的内核模块
# env                    # 查看环境变量

资源

# free -m                # 查看内存使用量和交换区使用量
# df -h                  # 查看各分区使用情况
# du -sh <目录名>        # 查看指定目录的大小
# grep MemTotal /proc/meminfo   # 查看内存总量
# grep MemFree /proc/meminfo    # 查看空闲内存量
# uptime                 # 查看系统运行时间、用户数、负载
# cat /proc/loadavg      # 查看系统负载

磁盘和分区

# mount | column -t      # 查看挂接的分区状态
# fdisk -l               # 查看所有分区
# swapon -s              # 查看所有交换分区
# hdparm -i /dev/hda     # 查看磁盘参数(仅适用于IDE设备)
# dmesg | grep IDE       # 查看启动时IDE设备检测状况

网络

# ifconfig               # 查看所有网络接口的属性
# iptables -L            # 查看防火墙设置
# route -n               # 查看路由表
# netstat -lntp          # 查看所有监听端口
# netstat -antp          # 查看所有已经建立的连接
# netstat -s             # 查看网络统计信息

进程

# ps -ef                 # 查看所有进程
# top                    # 实时显示进程状态

用户

# w                      # 查看活动用户
# id <用户名>            # 查看指定用户信息
# last                   # 查看用户登录日志
# cut -d: -f1 /etc/passwd   # 查看系统所有用户
# cut -d: -f1 /etc/group    # 查看系统所有组
# crontab -l             # 查看当前用户的计划任务

服务

# chkconfig --list       # 列出所有系统服务
# chkconfig --list | grep on    # 列出所有启动的系统服务

程序

# rpm -qa                # 查看所有安装的软件包

Filled Under: it技术

shell脚本调试

在shell脚本中输出调试信息

通过在程序中加入调试语句把一些关键地方或出错的地方的相关信息显示出来是最常见的调试手段。Shell程序员通常使用echo(ksh程序员常使用 print)语句输出信息,但仅仅依赖echo语句的输出跟踪信息很麻烦,调试阶段在脚本中加入的大量的echo语句在产品交付时还得再费力一一删除。针 对这个问题,本节主要介绍一些如何方便有效的输出调试信息的方法。

1. 使用trap命令

trap命令用于捕获指定的信号并执行预定义的命令。
其基本的语法是:
trap ‘command’ signal
其中signal是要捕获的信号,command是捕获到指定的信号之后,所要执行的命令。可以用kill –l命令看到系统中全部可用的信号名,捕获信号后所执行的命令可以是任何一条或多条合法的shell语句,也可以是一个函数名。
shell脚本在执行时,会产生三个所谓的“伪信号”,(之所以称之为“伪信号”是因为这三个信号是由shell产生的,而其它的信号是由操作系统产生的),通过使用trap命令捕获这三个“伪信号”并输出相关信息对调试非常有帮助。
表 1. shell伪信号

信号名 何时产生
EXIT 从一个函数中退出或整个脚本执行完毕
ERR 当一条命令返回非零状态时(代表命令执行不成功)
DEBUG 脚本中每一条命令执行之前

通过捕获EXIT信号,我们可以在shell脚本中止执行或从函数中退出时,输出某些想要跟踪的变量的值,并由此来判断脚本的执行状态以及出错原因,其使用方法是:
trap ‘command’ EXIT 或 trap ‘command’ 0

通过捕获ERR信号,我们可以方便的追踪执行不成功的命令或函数,并输出相关的调试信息,以下是一个捕获ERR信号的示例程序,其中的$LINENO是一个shell的内置变量,代表shell脚本的当前行号。

$ cat -n exp1.sh
     1  ERRTRAP()
     2  {
     3    echo "[LINE:$1] Error: Command or function exited with status $?"
     4  }
     5  foo()
     6  {
     7    return 1;
     8  }
     9  trap 'ERRTRAP $LINENO' ERR
    10  abc
    11  foo

其输出结果如下:

$ sh exp1.sh
exp1.sh: line 10: abc: command not found
[LINE:10] Error: Command or function exited with status 127
[LINE:11] Error: Command or function exited with status 1

在调试过程中,为了跟踪某些变量的值,我们常常需要在shell脚本的许多地方插入相同的echo语句来打印相关变量的值,这种做法显得烦琐而笨拙。而通过捕获DEBUG信号,我们只需要一条trap语句就可以完成对相关变量的全程跟踪。

以下是一个通过捕获DEBUG信号来跟踪变量的示例程序:

$ cat –n exp2.sh
     1  #!/bin/bash
     2  trap 'echo “before execute line:$LINENO, a=$a,b=$b,c=$c”' DEBUG
     3  a=1
     4  if [ "$a" -eq 1 ]
     5  then
     6     b=2
     7  else
     8     b=1
     9  fi
    10  c=3
    11  echo "end"

其输出结果如下:

$ sh exp2.sh
before execute line:3, a=,b=,c=
before execute line:4, a=1,b=,c=
before execute line:6, a=1,b=,c=
before execute line:10, a=1,b=2,c=
before execute line:11, a=1,b=2,c=3
end

从运行结果中可以清晰的看到每执行一条命令之后,相关变量的值的变化。同时,从运行结果中打印出来的行号来分析,可以看到整个脚本的执行轨迹,能够判断出哪些条件分支执行了,哪些条件分支没有执行。

2. 使用tee命令

在shell脚本中管道以及输入输出重定向使用得非常多,在管道的作用下,一些命令的执行结果直接成为了下一条命令的输入。如果我们发现由管道连接起来的 一批命令的执行结果并非如预期的那样,就需要逐步检查各条命令的执行结果来判断问题出在哪儿,但因为使用了管道,这些中间结果并不会显示在屏幕上,给调试 带来了困难,此时我们就可以借助于tee命令了。

tee命令会从标准输入读取数据,将其内容输出到标准输出设备,同时又可将内容保存成文件。例如有如下的脚本片段,其作用是获取本机的ip地址:

ipaddr=`/sbin/ifconfig | grep 'inet addr:' | grep -v '127.0.0.1'
| cut -d : -f3 | awk '{print $1}'`
#注意=号后面的整句是用反引号(数字1键的左边那个键)括起来的。
echo $ipaddr

运行这个脚本,实际输出的却不是本机的ip地址,而是广播地址,这时我们可以借助tee命令,输出某些中间结果,将上述脚本片段修改为:

ipaddr=`/sbin/ifconfig | grep 'inet addr:' | grep -v '127.0.0.1'
| tee temp.txt | cut -d : -f3 | awk '{print $1}'`
echo $ipaddr

之后,将这段脚本再执行一遍,然后查看temp.txt文件的内容:

$ cat temp.txt
inet addr:192.168.0.1  Bcast:192.168.0.255  Mask:255.255.255.0

我们可以发现中间结果的第二列(列之间以:号分隔)才包含了IP地址,而在上面的脚本中使用cut命令截取了第三列,故我们只需将脚本中的cut -d : -f3改为cut -d : -f2即可得到正确的结果。

具体到上述的script例子,我们也许并不需要tee命令的帮助,比如我们可以分段执行由管道连接起来的各条 命令并查看各命令的输出结果来诊断错误,但在一些复杂的shell脚本中,这些由管道连接起来的命令可能又依赖于脚本中定义的一些其它变量,这时我们想要 在提示符下来分段运行各条命令就会非常麻烦了,简单地在管道之间插入一条tee命令来查看中间结果会更方便一些。

3. 使用”调试钩子”

在C语言程序中,我们经常使用DEBUG宏来控制是否要输出调试信息,在shell脚本中我们同样可以使用这样的机制,如下列代码所示:

if [ “$DEBUG” = “true” ]; then
echo “debugging”  #此处可以输出调试信息
fi

这样的代码块通常称之为“调试钩子”或“调 试块”。在调试钩子内部可以输出任何您想输出的调试信息,使用调试钩子的好处是它是可以通过DEBUG变量来控制的,在脚本的开发调试阶段,可以先执行 export DEBUG=true命令打开调试钩子,使其输出调试信息,而在把脚本交付使用时,也无需再费事把脚本中的调试语句一一删除。

如果在每一处需要输出调试信息的地方均使用if语句来判断DEBUG变量的值,还是显得比较繁琐,通过定义一个DEBUG函数可以使植入调试钩子的过程更简洁方便,如下面代码所示:

$ cat –n exp3.sh
     1  DEBUG()
     2  {
     3  if [ "$DEBUG" = "true" ]; then
     4      $@  
     5  fi
     6  }
     7  a=1
     8  DEBUG echo "a=$a"
     9  if [ "$a" -eq 1 ]
    10  then
    11       b=2
    12  else
    13       b=1
    14  fi
    15  DEBUG echo "b=$b"
    16  c=3
    17  DEBUG echo "c=$c"

在上面所示的DEBUG函数中,会执行任何传给它的命令,并且这个执行过程是可以通过DEBUG变量的值来控制的,我们可以把所有跟调试有关的命令都作为DEBUG函数的参数来调用,非常的方便。

使用shell的执行选项

上一节所述的调试手段是通过修改shell脚本的源代码,令其输出相关的调试信息来定位错误的,那有没有不修改源代码来调试shell脚本的方法呢?答案就是使用shell的执行选项,本节将介绍一些常用选项的用法:

-n 只读取shell脚本,但不实际执行
-x 进入跟踪方式,显示所执行的每一条命令
-c “string” 从strings中读取命令

“-n”可用于测试shell脚本是否存在语法错误,但不会实际执行命令。在shell脚本编写完成之后,实际执行之前,首先使用“-n”选项来测试脚本 是否存在语法错误是一个很好的习惯。因为某些shell脚本在执行时会对系统环境产生影响,比如生成或移动文件等,如果在实际执行才发现语法错误,您不得 不手工做一些系统环境的恢复工作才能继续测试这个脚本。

“-c”选项使shell解释器从一个字符串中而不是从一个文件中读取并执行shell命令。当需要临时测试一小段脚本的执行结果时,可以使用这个选项,如下所示:
sh -c ‘a=1;b=2;let c=$a+$b;echo “c=$c”‘

“-x”选项可用来跟踪脚本的执行,是调试shell脚本的强有力工具。“-x”选项使shell在执行脚本的过程中把它实际执行的每一个命令行显示出来,并且在行首显示一个”+”号。 “+”号后面显示的是经过了变量替换之后的命令行的内容,有助于分析实际执行的是什么命令。 “-x”选项使用起来简单方便,可以轻松对付大多数的shell调试任务,应把其当作首选的调试手段。

如果把本文前面所述的trap ‘command’ DEBUG机制与“-x”选项结合起来,我们 就可以既输出实际执行的每一条命令,又逐行跟踪相关变量的值,对调试相当有帮助。

仍以前面所述的exp2.sh为例,现在加上“-x”选项来执行它:

$ sh –x exp2.sh
+ trap 'echo "before execute line:$LINENO, a=$a,b=$b,c=$c"' DEBUG
++ echo 'before execute line:3, a=,b=,c='
before execute line:3, a=,b=,c=
+ a=1
++ echo 'before execute line:4, a=1,b=,c='
before execute line:4, a=1,b=,c=
+ '[' 1 -eq 1 ']'
++ echo 'before execute line:6, a=1,b=,c='
before execute line:6, a=1,b=,c=
+ b=2
++ echo 'before execute line:10, a=1,b=2,c='
before execute line:10, a=1,b=2,c=
+ c=3
++ echo 'before execute line:11, a=1,b=2,c=3'
before execute line:11, a=1,b=2,c=3
+ echo end
end

在上面的结果中,前面有“+”号的行是shell脚本实际执行的命令,前面有“++”号的行是执行trap机制中指定的命令,其它的行则是输出信息。

shell的执行选项除了可以在启动shell时指定外,亦可在脚本中用set命令来指定。 “set -参数”表示启用某选项,”set +参数”表示关闭某选项。有时候我们并不需要在启动时用”-x”选项来跟踪所有的命令行,这时我们可以在脚本中使用set命令,如以下脚本片段所示:

set -x    #启动"-x"选项
要跟踪的程序段
set +x     #关闭"-x"选项

set命令同样可以使用上一节中介绍的调试钩子—DEBUG函数来调用,这样可以避免脚本交付使用时删除这些调试语句的麻烦,如以下脚本片段所示:

DEBUG set -x    #启动"-x"选项
要跟踪的程序段
DEBUG set +x    #关闭"-x"选项

回页首

. 对”-x”选项的增强

“-x”执行选项是目前最常用的跟踪和调试shell脚本的手段,但其输出的调试信息仅 限于进行变量替换之后的每一条实际执行的命令以及行首的一个”+”号提示符,居然连行号这样的重要信息都没有,对于复杂的shell脚本的调试来说,还是 非常的不方便。幸运的是,我们可以巧妙地利用shell内置的一些环境变量来增强”-x”选项的输出信息,下面先介绍几个shell内置的环境变量:

$LINENO
代表shell脚本的当前行号,类似于C语言中的内置宏__LINE__

$FUNCNAME
函数的名字,类似于C语言中的内置宏__func__,但宏__func__只能 代表当前所在的函数名,而$FUNCNAME的功能更强大,它是一个数组变量,其中包含了整个调用链上所有的函数的名字,故变 量${FUNCNAME[0]}代表shell脚本当前正在执行的函数的名字,而变量${FUNCNAME[1]}则代表调用函 数${FUNCNAME[0]}的函数的名字,余者可以依此类推。

$PS4
主提示符变量$PS1和第二级提示符变量$PS2比较常见,但很少有人注意到第四级提示符 变量$PS4的作用。我们知道使用“-x”执行选项将会显示shell脚本中每一条实际执行过的命令,而$PS4的值将被显示在“-x”选项输出的每一条 命令的前面。在Bash Shell中,缺省的$PS4的值是”+”号。(现在知道为什么使用”-x”选项时,输出的命令前面有一个”+”号了吧?)。

利用$PS4这一特性,通过使用一些内置变量来重定义$PS4的值,我们就可以增强”-x”选项的输出信息。 例如先执行export PS4=’+{$LINENO:${FUNCNAME[0]}} ‘, 然后再使用“-x”选项来执行脚本,就能在每一条实际执行的命令前面显示其行号以及所属的函数名。

以下是一个存在bug的shell脚本的示例,本文将用此脚本来示范如何用“-n”以及增强的“-x”执行选项来调试shell脚本。这个脚本中定义了一个函数isRoot(),用于判断当前用户是不是root用户,如果不是,则中止脚本的执行

$ cat –n exp4.sh
     1  #!/bin/bash
     2  isRoot()
     3  {
     4          if [ "$UID" -ne 0 ]
     5                  return 1
     6          else
     7                  return 0
     8          fi
     9  }
    10  isRoot
    11  if ["$?" -ne 0 ]
    12  then
    13          echo "Must be root to run this script"
    14          exit 1
    15  else
    16          echo "welcome root user"
    17          #do something
    18  fi

首先执行sh –n exp4.sh来进行语法检查,输出如下:

$ sh –n exp4.sh
exp4.sh: line 6: syntax error near unexpected token `else'
exp4.sh: line 6: `      else'

发现了一个语法错误,通过仔细检查第6行前 后的命令,我们发现是第4行的if语句缺少then关键字引起的(写惯了C程序的人很容易犯这个错误)。我们可以把第4行修改为if [ "$UID" -ne 0 ]; then来修正这个错误。再次运行sh –n exp4.sh来进行语法检查,没有再报告错误。接下来就可以实际执行这个脚本了,执行结果如下:

$ sh exp4.sh
exp2.sh: line 11: [1: command not found
welcome root user

尽管脚本没有语法错误了,在执行时却又报告了错误。错误信息还非常奇怪“[1: command not found”。现在我们可以试试定制$PS4的值,并使用“-x”选项来跟踪:

$ export PS4='+{$LINENO:${FUNCNAME[0]}} '
$ sh –x exp4.sh
+{10:} isRoot
+{4:isRoot} '[' 503 -ne 0 ']'
+{5:isRoot} return 1
+{11:} '[1' -ne 0 ']'
exp4.sh: line 11: [1: command not found
+{16:} echo 'welcome root user'
welcome root user

从输出结果中,我们可以看到脚本实际被执行的语句,该语句的行号以及所属的函数名也被打印出来,从中可以清楚的分析出脚本的执行轨迹以及所调用的函数的内部执行情况。由于执行时是第11行报错,这是一个if语句,我们对比分析一下同为if语句的第4行的跟踪结果:

+{4:isRoot} '[' 503 -ne 0 ']'
+{11:} '[1' -ne 0 ']'

可知由于第11行的[号后面缺少了一个空格,导致[号与紧挨它的变量$?的值1被shell解释器看作了一个整体,并试着把这个整体视为一个命令来执行,故有“[1: command not found”这样的错误提示。只需在[号后面插入一个空格就一切正常了。

shell中还有其它一些对调试有帮助的内置变量,比如在Bash Shell中还有BASH_SOURCE, BASH_SUBSHELL等一批对调试有帮助的内置变量,您可以通过man sh或man bash来查看,然后根据您的调试目的,使用这些内置变量来定制$PS4,从而达到增强“-x”选项的输出信息的目的。


总结

现在让我们来总结一下调试shell脚本的过程:
首先使用“-n”选项检查语 法错误,然后使用“-x”选项跟踪脚本的执行,使用“-x”选项之前,别忘了先定制PS4变量的值来增强“-x”选项的输出信息,至少应该令其输出行号信 息(先执行export PS4='+[$LINENO]‘,更一劳永逸的办法是将这条语句加到您用户主目录的.bash_profile文件中去),这将使你的调试之旅更轻松。 也可以利用trap,调试钩子等手段输出关键调试信息,快速缩小排查错误的范围,并在脚本中使用“set -x”及“set +x”对某些代码块进行重点跟踪。这样多种手段齐下,相信您已经可以比较轻松地抓出您的shell脚本中的臭虫了。如果您的脚本足够复杂,还需要更强的调 试能力,可以使用shell调试器bashdb,这是一个类似于GDB的调试工具,可以完成对shell脚本的断点设置,单步执行,变量观察等许多功能, 使用bashdb对阅读和理解复杂的shell脚本也会大有裨益。关于bashdb的安装和使用,不属于本文范围,您可参阅 http://bashdb.sourceforge.net/上的文档并下载试用。

Filled Under: it技术