帐前卒专栏

code, software architect, articles and novels.
代码,软件架构,博客和小说

转载自: http://nffish.com/archives/108

研究了好久3D桌面效果,终于找到这个好方法了。 CompizConfig设置管理器的功能实在是太强大了。在Ubuntu 8.04下测试通过。
首先安装 CompizConfig设置管理器(3D驱动必须装好)

sudo apt-get install compizconfig-settings-manager

运行 系统->首选项->Advanced Desktop Effects Settings,点击General Compiz options,(
这里只需要在System->Preference->compiz config setting manager点开即可 )把Desktop
Size选项卡里的“水平虚拟大小”设置为4,“垂直虚拟大小”设置为1,“桌面数”设置为4,(
这里我的设置是6,因为设置4时机器很卡,设置6机器就不卡了。诡异 )后退,勾选“桌面立方体”“旋转立方体”“立方体倒映” “Cube Caps
”几项,按住Ctrl+alt,再按住鼠标左键移动,3D桌面效果出现。(
我这里其实有一点点的3D桌面的意思,但是速度太快,而且立方体太大,不知道为啥。可能是显示器的原因吧 )

在Gube Gears前面打钩,然后找到前面打过钩的桌面立方体,点击,选择Transparent Cube,将Opacity During
Rotation降到50左右,再将3D桌面调出来,立方体成半透明的了,并且里面有三个齿轮在滚动!

点击旋转立方体->一般,把缩放调整到1.0,把速度调整到0.7,再按Ctrl+alt+向左/向右键,可以看到立方体运动时的慢动作,并且立方体小了很多。

将鼠标在桌面上的空白位置点击一下,然后转动鼠标的滚轮,出现旋转的3D桌面效果。按住Super键(Ctrl与Alt中间的那个键)+Tab键,桌面水平排列。

进入调整大小->动作,点击一般前面的小三角,双击“启动所有窗口的窗口拾取器”,选中Topleft,点击OK。现在将你的鼠标移动到桌面左上角,就能实现类似Ma
c os X的Expose特效了。

进入CompizConfig设置管理器的主界面,点击Expo->动作,在窗口中的Expo上面双击,勾选Top
Right,然后OK。将鼠标移动到桌面的右上角,4个桌面整齐排列在一个3D空间内(同super+E效果),可以将窗口从一个桌面拖动到另一个桌面,
甚至放在两个桌面中间!

在CompizConfig设置主界面选择Shift Switcher->动作->Initial(All
workspace)下,勾选Bottomleft。鼠标移动到桌面的左下角,出现iPhone的Cover
Flow特效。可以用鼠标滚轮和方向键切换桌面。回到Shift Switcher,点击外观,在Switcher
mode中选择Flip。鼠标移动到桌面的左下角,出现windows vista中的Flip3D特效。

在CompizConfig设置管理器主界面选择“在屏幕上绘制火焰”前面的复选框,现在就可以直接按住Super+shift键,然后用鼠
标左键在屏幕写了。火焰的默认颜色是红色,可以直接进入“在屏幕上绘制火焰”设置项里,调整火焰颜色,或者干脆“随机”,取消火焰的时候同时按
Super+Shift+C。

再次回到主界面中,勾选Windows Previews,这样将鼠标移动到任务栏会出现预览窗口。

在CCSM(CompizConfig设置管理器)主界面选择移动窗口,将其设置界面中的不透明度设置到60左右,这样你移动窗口的时候就会 有半透明的效果。
同样在主界面中选择General Options->Opacity Settings,点击ADD(添加)。在OpacityWindows
中输入Dock,OpacityWIndow
Values输入数值80。再以同样的方式添加DropDownMenu和PopupMenu,数值都为80。输入完毕之后,不管是什么菜单,现在都是以
半透明的方式显示了,包括右键菜单。

在CCSM主界面中选择Animations->CloseAnimation,在窗口中的第一个Glide2上双击,然后从CloseEffect的下拉菜单中选择
Burn,这样每次关闭窗口的时候会有火焰的效果。

点击Effect Settings,找到下面的Fire,点击小三角形出现火焰选项,然后勾选其中的“随机颜色火焰”后面的复选框,火焰颜色随机。也可以去Open
Animation里设置开启窗口时的动画特效,或者就选“随机”。

首先要有openoffice.在system->administration->synaptic package Manager中输入openoffice,
并选中列出的openoffice.org。然后再输入openoffice math, 选中texlive-math-extra。然后点击apply开始安装。

再启动openoffice之后,在Insert->object->formula中就可以创建公式了。点击一个,在下方可以编辑“<?>”来替代文中的方块。

添加字体转载自:http://www.fwolf.com/blog/post/170

windows的字体一般存放在c:/windows/fonts目录下,我拷贝到linux下的字体有:

simfang.ttf 仿宋体 simhei.ttf 黑体 simkai.ttf 楷体 simsun.ttf 宋体和新宋体,原文件名simsun.ttc
tahoma.ttf tahoma字体 tahomabd.ttf tahoma字体的粗体形式 verdana.ttf verdana字体
verdanab.ttf verdana字体的粗体形式 verdanai.ttf verdana字体的斜体形式 verdanaz.ttf
verdana字体的粗体+斜体形式

拷贝过来的字体文件放在了/home/fwolf/tools/fonts目录下。

** 二、将字体加入到linux的可使用字体中 **

首先把字体文件链接到存放字体的目录中

cd /usr/share/fonts ln -s /home/fwolf/tools/fonts xpfonts cd xpfonts
mkfontscale mkfontdir

这样作和把字体拷贝到/usr/share/fonts的一个目录下的效果是一样的。后面的两个mkfont命令是生成xpfonts目录下所包含的字体的索引信息
。然后运行fc-cache命令更新字体缓存:

fc-cache

在openoffice中其中仿宋体的字体名是FangSong_GB2312,黑体是SimHei,楷体是KaiTi_GB2312,宋体是SimSun

I will try google doc

转自北邮,北邮又可能转自清华。所以我写转自http://qun.qq.com/air/#88342452/bbs/view/cd/2/td/8

最近补了一下课。首先企业和学生申报户口的大概流程是这样的:

1,单位申报本年度的户口指标。各区申报截止时间不同,基本走人事局的在12月前就申报完成,基本是在11月15日前。

2,人事部门审批单位申报的指标。人事局的基本都在12月底到1月中会下来,也有很牛的企业会更早的;人事部的2月中下来。会严格控制数量。需要注意:指标的多少基本
只跟公司的规模,行业,性质,纳税等等方面有关,跟学生材料没有丝毫关系。

3,单位、学生正式申报。此时人事部门根据之前审批给单位的户口名额,再综合考虑学生的情况(专业是否紧俏,是否对口,是否双外)和单位的情况(牛不牛)进行审批。事
业单位部委都通过人事部申请指标,审批也是走人事部,相对好批;私企,民企和外企等通过人事局申请指标,到时也是走人事局审批材料,相对不好批。

关于三方的作用

三方主要两个作用,一是规范公司和学生的行为。二是户口、转档用。而实际上第二个作用才是主要的作用,第一个作用是可以通过双方协议(因为学生还不能签劳动合同,所以
学生和单位自己签订非劳动合同形式的协议)达到的,而第二个是无法代替的。

** 因此,如果单位不能解决户口,实际上就没有权利签三方,如果签了,只会让学生浪费申报户口的机会!! ** 这一点一定要注意!因为如果我们愿意去这个单位A,但又想解决户口,唯一的办法就是挂到其他可以解决户口的单位B,比如自己买户口。这时候,单位B必须拿到你的三方才能给你申报户口。而且不仅如此,就算不找其他在京单位解决户口了,档案户口回家乡,家乡单位也需要和你签署三方呀。更大的麻烦是:提出不给你办户口却要和你签三方的要求的单位,往往是对应届毕业生就业制度一无所知,所以千万不要给他们,一旦给了,等你发现需要新三方来纠正这件事情的时候学校往往会要求你拿回原三方,而你去向单位要的时候,他们因为不懂,往往以为你要违约,说什么都不给你,那时候就只剩一个字了:“晕!!”

单位A想给你申报户口,但不能保证申报成功,怎么办?

这时候一定要注意让单位填三方时在审批通过之前不要在三方中填写“单位信息”,这是因为:审批时虽然需要用到三方协议,但此时除个人及学校信息栏外,其他栏,包括单位
信息栏,空白即可,也即由单位上报的三方中其实不需要包括它自己单位的信息。一般正规的做法是,是公司先把你的其他审批材料上报给北京人事局或国家人事部,等“户口接
收函”快下来前才会把三方中的“单位信息栏”填完,然后盖上章交给人事局或人事部,这时才完成户口审批。而如果审批失败,则单位可以将三方立刻退给你,你可以立刻拿这
张三方找其他单位申报(比如买户口),而不必去学校重新换一张三方。学校换一张三方有什么坏处?有些学校不乐意学生换,所以会为难我们,清华的倒不存在这个问题,但没
必要多此一举,而且正规的公司应该按规定主动在审批结果没下来之前不在单位信息栏中填写信息。

另外有一点,如果我们在单位A申报不成功,那么之后想再申报户口就会很困难,因为在人事局已经有记录了,其他单位再申报你的信息时可以被认为是“多头申报”(即同一个
人在不同的单位进行申报,即便不是同时申报的,这是严厉禁止的)。目前了解到考国家公务员和到基层单位就业可能不存在这个问题。因此,如果一个单位本身很难解决户口,
我们不如一开始就跟单位说好只签双方的协议,而把宝贵的三方留作自己解决户口时才用(比如买户口)。

还有一种情况是单位承诺如果单位的在A城市的分公司A不能解决户口的话,可以在B城市的分公司B给予解决。这时候单位信息栏也很明显不能填,因为分公司 A一旦发现户
口审批没有成功,就可以立刻拿着这份空白三方再去分公司B申报。此时因为A、B两个公司不在同一个城市,不是由同一个人事局审批,也就不存在多头申报的可能了。

单位不能解决户口,也不想买户口,又想在单位工作,怎么办?

应该办理灵活就业手续,领取回生源地人事局的二分报到证,同时将户档落回生源地。这样的好处是:首先,你在国家规定的派遣时间内领取报到证,避免将来因种种原因错过报
到证办理期限,失去“干部”身份(如果晚了就会落到劳动局,就是工人身份了);其次,你将来有可能按“人才引进”的方式将户口调入实际工作单位所在地,但是需要注意的
是,实际上从06年开始,人才引进在实际操作中已经不审批了,所以如果单位给你人才引进的承诺,基本上是口头支票。
关于违约

虽然叫三方,你和企业签了之后实际上就生效了,即便学校没签,想违约也要交违约金(其实这时候不能交违约,因为学校没签三方也就没生效,但是是不是叫违约并不重要,关
键是能否领回单位手里的三方),因为不交的话单位不会交回三方你也就无法领到新的三方。

另外,如果在上报就业方案后(6月份)再违约,就属于改派,原则上不能再在北京或上海等不便落户的地方落户,学校一般也不喜欢改派。

前面说了其实三方的规范公司和学生行为的作用实际上是次要的作用,但又往往很多企业很在乎,而且很多企业要求学生赶紧交三方,不管是有指标的企业还是最终没有指标的企
业,这一是因为公司的HR根本不懂户籍相关的政策,二是因为HR把三方的用处认为是劳动合同签订之前的补充了,想用三方来约束我们违约的情况。

这或许是一个简单的问题,但是我早就忘记咋解了。

关于一个二进制数1111000 除以1101,模2除法的商为1011,余数为111.这个结果不同于十进制除法。所以特记下。具体步骤如下:

#第一步 1111000 1101 0010000 ----余数,商为1,只要第一位非0商就是1
#第二步,每步移一位,当起始位为0时,除以0;为1时除以除数。 010000 0000 010000 ----余数,商为0,只要第一位是0商就是0 #第三步
10000 1101 01010 -----余数,商为1,这里的余数你猜出,其实就是对应位异或 #第四步 1010 1101 0111
------余数,商为1,如果位数比除数还小,不再继续运算 #最终余数为111,商为1011

模2除法,参考了下网络资源。最后得到的结论。不知道是正确与否。

模2除法可以用在CRC冗余校验上。K,H均为2进制数,K向左移R,然后K除以H。模2除得到的余数在放到R位中。例如:K=1111,H=1101,R=3,移位
之后得1111000,最后CRC=1111111。

转载自:http://wells.osall.com/blog/index.php?uid=1&m=content&p=1434#article_conte
nt.php%3Fid%3D1434%26uid%3D1%26dt%3DY:pane:N;

BITimE // 咀嚼时光

前两天买了个Seagate的320G的2.5寸SATA硬盘,真便宜,300多元,差不多每G一元钱了。于是就把原来移动硬盘里的160G的硬盘装在那台ATOM小
PC上了。如果大家还没忘记的话,应该还记得 我的那个ATOM小PC里面是用的8G的CF转IDE作为硬盘

的。现在突然存储设备容量大了20倍,那就让它干更多的活吧。兔子老是跟我说他在他的服务器上装了 n多虚拟机
,充分利用硬件资源,那我也来试一下吧。

Linux上有很多虚拟机软件, XEN 、 [ KVM ](http://www.linux-
kvm.org/page/Main_Page) 、 VMWare VirtualBox

,从开源角度和性能角度来考量,当然选XEN啦。XEN性能非常优异,虚拟的主机的性能几乎和原生主机一致,如果主机硬件支持 VT (intel)
或者 [ SVM (AMD) ](http://www
.amd.com/us/products/technologies/virtualization/Pages/virtualization.aspx)
的技术的话,则可以虚拟任何能在x86架构上运行的操作系统。可惜我的ATOM 330
CPU不支持VT技术,因此只能玩半虚拟模式,虚拟出内核支持XEN的操作系统了。

换上新硬盘,首先就是安装新的操作系统。我一直用惯了Ubuntu,因此就安装了 Ubuntu 9.04 Server Edition 64位

版本。安装OS的过程就不多说了,直接说XEN的安装配置吧。

在XEN虚拟机环境上第一个被启动的操作系统被称为Domain-0,而之后启动的其他虚拟操作系统则被称为Domain-U,每一个Domain 就是一台虚拟机
。Domain-0除了可以运行应用程序外,还担负着XEN的控制功能,所以也被称为XEN Hypervisor。在Domain-
0上启动了XEN服务后,最重要的一个进程就是xend,这就是XEN的服务进程。

我们新安装的操作系统如果没有安装XEN软件,则就相当于普通的一个单机使用。如果安装了XEN软件,并且以XEN化的内核启动,则这个操作系统环
境就变成了Domain-0,而后在这个环境下安装的其他操作系统则就是Domain-U了,都可以通过Domain-0来进行控制。

![虚拟机架构](http://p.blog.csdn.net/images/p_blog_csdn_net/cctt_1/EntryImages/2009
1109/hypervisor_01.png)

要安装并使用XEN,首先更新软件源的信息。

sudo apt-get update

然后安装xen服务器软件和工具。

sudo apt-get install ubuntu-xen-server # sudo apt-get install ubuntu-xen-

desktop
这时候我们去/boot目录看,可以看到一个xen3.3.gz文件,但是没有供操作系统使用的XEN化的内核,因此此时的操作系统还不是Doamin-0。我找了好
久都没找到现成的XEN化内核,于是就只能根据XEN手册自己编译一个内核了。

下载安装最新的kernel文件。

sudo apt-get install linux-image-server linux-server

安装编译所需要的一些工具包。

sudo apt-get install build-essential libncurses5-dev gawk mercurial

接下来下载XEN所提供的XEN化的内核的源代码。

mkdir -p ~/build/linux-2.6.27-xen # cd /usr/src/ # sudo hg clone

http://xenbits.xensource.com/ext/linux-2.6.27-xen.hg
配置内核选项。

cd /usr/src/linux-2.6.27-xen.hg # sudo make O=~/build/linux-2.6.27-xen/

menuconfig
出现内核参数的配置菜单后,根据如下的选项修改配置。

1。General setup —> Choose SLAB allocator (SLUB (Unqueued Allocator)) —>
(X) SLAB

2。Processor type and features —> Subarchitecture Type (PC-compatible) —>
(X) Enable Xen compatible kernel

3。

Bus options (PCI etc.) —> [] PCI support [] Xen PCI Frontend [ ] Xen PCI
Frontend Debugging (NEW)
4。将 802.1d Ethernet Bridging 修改为 <*> 802.1d Ethernet Bridging:

Networking support —> Networking options —> <*> 802.1d Ethernet Bridging

5。关闭 10000 Mbit Ethernet 支持(否则可能编译失败):

Device Drivers —> [*] Network device support —> [ ] Ethernet (10000 Mbit)
—>

6。在 XEN 选项区中,按照下面选择选项(确认选择 Xen version compatibility (3.0.4 and later) 来取代原来的
Xen version compatibility (3.0.2 and later)):
Device Drivers —> XEN —> [] Privileged Guest (domain 0) <> Backend
driver support (NEW) <> Block-device backend driver (NEW) <> Block-device
tap backend driver (NEW) <> Network-device backend driver (NEW) (8) Maximum
simultaneous transmit requests (as a power of 2) (NEW) [ ] Pipelined
transmitter (DANGEROUS) (NEW) < > Network-device loopback driver (NEW) <
>
PCI-device backend driver (NEW) PCI Backend Mode (Virtual PCI) —> [ ] PCI
Backend Debugging (NEW) < > TPM-device backend driver (NEW) SCSI backend
driver (NEW) Block-device frontend driver Network-device frontend
driver Network-device frontend driver acceleration for Solarflare NICs
(NEW) SCSI frontend driver (NEW) <> User-space granted page access driver
(NEW) <
> Framebuffer-device frontend driver (NEW) <> Keyboard-device
frontend driver (NEW) [
] Disable serial port drivers (NEW) <*> Export Xen
attributes in sysfs (NEW) (256) Number of guest devices (NEW) Xen version
compatibility (3.0.4 and later) —>
ESC退出并保存完配置后就可以开始编译内核了。

可能这里要先 make mrproper

sudo make O=~/build/linux-2.6.27-xen/

sudo make O=~/build/linux-2.6.27-xen/ modules_install install

通过漫长的等待(我的Atom 330
CPU花了大约3个小时),终于编译完内核了。编译安装完成后,去/boot目录检查,应该可以看到生成了支持xen的2.6.27.5的内核了。

ls -l /boot

total 72697
-rw-r–r-- 1 root root   525592 2009-04-17 12:05 abi-2.6.28-11-server
-rw-r–r-- 1 root root   524602 2009-07-25 11:14 abi-2.6.28-14-server
-rw-r–r-- 1 root root    87448 2009-08-13 19:50 config-2.6.27.5
-rw-r–r-- 1 root root    90587 2009-04-17 12:05 config-2.6.28-11-server
-rw-r–r-- 1 root root    90560 2009-07-25 11:14 config-2.6.28-14-server
drwxr-xr-x 2 root root     1024 2009-08-13 20:04 grub
-rw-r–r-- 1 root root  8259056 2009-08-14 03:31 initrd.img-2.6.28-11-server
-rw-r–r-- 1 root root  8272673 2009-08-13 15:02 initrd.img-2.6.28-14-server
drwxr-xr-x 2 root root    12288 2009-08-14 03:17 lost+found
-rw-r–r-- 1 root root   128796 2009-03-28 04:12 memtest86+.bin
-rw-r–r-- 1 root root  1651431 2009-08-13 19:50 System.map-2.6.27.5
-rw-r–r-- 1 root root  1871187 2009-04-17 12:05 System.map-2.6.28-11-server
-rw-r–r-- 1 root root  1863183 2009-07-25 11:14 System.map-2.6.28-14-server
-rw-r–r-- 1 root root     1169 2009-04-17 12:09 vmcoreinfo-2.6.28-11-server
-rw-r–r-- 1 root root     1169 2009-07-25 11:16 vmcoreinfo-2.6.28-14-server
-rw-r–r-- 1 root root  3145318 2009-08-13 19:50 vmlinuz-2.6.27.5
-rw-r–r-- 1 root root  3520832 2009-04-17 12:05 vmlinuz-2.6.28-11-server
-rw-r–r-- 1 root root  3510496 2009-07-25 11:14 vmlinuz-2.6.28-14-server
-rw-r–r-- 1 root root   470084 2009-06-18 16:16 xen-3.3.gz

内核有了,但是还没有initrd.img也就是启动时所需的ramdisk文件,手动来生成一个。

sudo depmod 2.6.27.5

sudo update-initramfs -c -k 2.6.27.5

更新grub引导程序。

update-grub

现在XEN的环境基本上都建好了,我们需要来修改一下配置文件了。

XEN服务的配置文件都在/etc/xen目录下。XEN服务的主配置文件是 /etc/xen/xend-
config.sxp。打开这个文件,我们要确认的是XEN虚拟服务器的网络连接方式,这是相当重要的。我选择了桥接模式, 也就是说,Domain-
U虚拟机和Domain-0在同一子网,用Domain-0的物理网卡来进行桥接到虚拟机的虚拟网卡。

sudo vi /etc/xen/xend-config.sxp

确认网络部分是这样配置的:
(network-script network-bridge)
下一步要根据你的习惯来修改。你喜欢将虚拟机安装在哪里?是用一个镜像文件来虚拟成磁盘,还是将一个LVM逻辑卷虚拟成磁盘,或者干脆就是将物理磁盘给虚拟机使用?

对于后两种选择,你可以跳过这一步。但如果你选择了在现有的文件系统中建立一个镜像文件来虚拟成一个虚拟机的物理磁盘,那就要按照下面修改默认配置了。

由于镜像文件在加载的时候是作为回圈设备(loop device),因此要将操作系统的默认最大回圈设备数量调大,以免出现不必要的问题。

sudo vi /etc/modules

将loop这一行修改为:
loop max_loop=64
好啦,重启主机,进入Domain-0的世界!!!

sudo shutdown -r now

重启完成后检查一下kernel版本,是不是XEN化的那个2.6.27.5?

uname -r

2.6.27.5
现在可以使用XEN的管理命令xm,来看看虚拟机运行状况啦!

sudo xm list

Name                                        ID   Mem VCPUs      State
Time(s)
Domain-0                                     0  1473     4     r-----    479.8
哈哈,可以看到Domain-0正常运行!!!你可以使用xm help来学习如何通过xm来管理虚拟机。

在State列,看到Domain-0是r的状态,也就是说,这个Domain正在运行中,并有任务在该Domain上运行。虚拟机还有如下几种状态:

  • r :该domain正在消耗CPU资源,任务运行中;
  • b :该domain正被暂时搁置(blockded),一般来说是因为这个domain在闲置中,等待输入或输出(I/O);
  • p :该domain处于暂停状态,通常是因为管理员使用 xm pause 暂停了这个domain。当domain至于暂停状态时,Xen的管理器将不会处理这个domain的任何动作;
  • s :该domain正在关机;
  • c :该domain已经crash了,但是没有关机。一般来说是因为domain的配置文件没有设置 on_crash 动作所致;
  • d :该domain正在死机中,一般来说是因为这个domain无法正确 shutdown/crashed 之故。

先到这里吧,总算是把XEN安装上了,而且也把Domain-0给顺利启动了。

找到很多网上的文档,也是一步一步按照上面所做,可是就是有太多的问题。写一篇自己的搭建过程和错误产生的原因。

开始我找了两台服务器,上面装的是Asianux的操作系统,因为含有rsh服务。所以会报出:

connect to address XXXX: Connection refused
Trying krb4 rlogin…
connect to address XXXX: Connection refused
trying normal rlogin (/usr/bin/rlogin)

这个问题解决方案有多种,不过最简单的一种是在配置时:./configure添加参数 -rsh=ssh如下:

./configure -rsh=ssh

后来我有了多台机器,并且因为要搭建云的缘故,所以只有重新来过。首先找来4台机器,一个switch,这4台机器都是内网中的。我可以通过远程ssh登录上去。然后
装系统ubuntu9.04,这里要注意的是,用户名必须一致,并且hostname设置为node1,node2,node3,node4.如果你不这样做,后面会
出来非常多的问题,我稍后会一一解决。这里先介绍一下一般流程。

1。然后找一个好源,再使用apt-get更新.具体步骤详见:
http://blog.csdn.net/cctt_1/archive/2009/10/14/4667460.aspx

然后sudo apt-get install ssh,这一步是下载并安装ssh.

2。然后下载 [ mpich2-1.2 ](http://www.mcs.anl.gov/research/projects/mpich2/download
s/tarballs/1.2/mpich2-1.2.tar.gz) ,当然你还可以下载一份 mpich2的安装文档

。都下载到server(主节点)上,然后执行下面的操作:

tar xzvf mpich2-1.2.tar.gz或者使用命令gunzip -c mpich2.tar.gz | tar xf -(后者我没有试过)

3。然后mkdir mpich2在进入刚才解压的目录,执行配置文件./configure -prefix=…/mpich2 -rsh=ssh这里的-
prefix是你要安装到哪里(就是刚才的mkdir的目录)。之后make一下,然后make install 一下,看看报的错误,我当时好像缺少g++和gcc
,所以最好先apt-get 这两个东东。如果还是报错,就使用sudo ./configure -prefix=…/mpich2 -rsh=ssh,
sudo make , sudo make install.

4。之后把mpich2/bin这个文件路径放入到/etc/profile的最后一行,这样写,这里/home/you/是你的用户主目录。

1
PATH=$PATH:/home/you/mpich2/bin

然后log out再log in,如果不想这样,也可以export PATH=$PATH:/home/you/mpich2/bin临时做一下。然后echo $PATH,看看有没有添加成功。再下一步which mpd,看看有没有找到相应的路径。

5。然后执行

1
cd $HOME touch .mpd.conf #创建一个文件 chmod 600 .mpd.conf #设置权限

6。下一步vi .mpd.conf,添加一行MPD_SECRETWORD=abcd,这里的abcd是自己写的,你可以变动。这一步是以后使用mpd命令必须加入
的,主要是为了机器间的通信。

7。然后vi mpd.hosts,写入你的那几台机器的hostname,记住一行一个,如下:

node1 node2 node3 node4

这其实是你要让那些主机运行mpi的程序。

8。然后你在server(主节点)运行一下以下命令。

mpd & #启动一个mpd mpdtrace #查看哪些主机加入到ring中 mpdallexit#退出

这几个命令执行一下,如果没发生什么错误,就ok.

9。之后修改/etc/hosts,添加几行ip和hostname的对应关系。在127.0.0.1
localhost之后添加几行(这里假设server节点为node1当然,你可以改动): 192.168.12.111 node1 server
#个人认为这里的server加不加都可以就只是一个名字 192.168.12.112 node2 192.168.12.113 node3
192.168.12.114 node4

10。然后配置ssh自动登录,如何配置请看blog:
http://blog.csdn.net/cctt_1/archive/2009/10/14/4667604.aspx

.配置完毕后,在server节点依次输入:ssh node1 date,ssh node2 date, ssh node3 date , ssh node4
date.看看有没有返回date,主要是为了检测自己能否使用ssh登录到自己或其他节点。其他节点按照此法依次检测一下。如果不需要输入密码,并返回了date,
则ssh配置成功。

11。之后把你的mpich2/bin这个目录(./configure时的目录)打包 tar cvf bin.tar
mpich2/bin,然后将包发送到其他节点:node2,node3…。可以使用scp命令如下:

scp bin.tar you@node2:bin.tar

12。使用ssh登录到其他节点上,并解压bin.tar,然后按照第4步添加此路径并检查一下。

13。在各个节点上重做一下5,6,8,9,10,第7步可以不做。

14。回到server,使用如下两个命令: mpd & ,  mpdtrace -l,这就会得到_
(ip),检查下这三项是不是对的
。hostname应该是自己的node1,port是一个随机分配的,ip是hostname对应的ip(和/etc/hosts中的一样)

15。ssh到其他节点,然后输入mpd -h -p
&,这里的是14步中server的。如果执行这步没有错误,输入mpdtrace,就能看到node1,node2…

16。配置完成。集群已经搭建成功。以后启动的时候,只需要在server上执行mpdboot -n -f
mpd.hosts,这里的是你想启动的mpi程序数(会分配到不同的节点上),mpd.hosts是第七步创建的文件。

种种问题~~~~~~~:

1.没有使用相同的用户名(ubuntu上root用户你是不可能知道密码的),所以只有开始安装Ubuntu时的用户。你要做的是必须在所有节点都添加一个相同的用
户。因为mpich2在后续使用mpd -h -p
&命令加入到已存在的ring中时使用的是相同的用户名。并且如果你的用户名不同,ssh node2, ssh
node3,是不可能通过的,除非你使用了ssh you@node3,但mpd不会这样作,除非你要修改它的源码。要建立用户可以使用我下面编写的小脚本,并且加入
的用户获得了和你安装用户一样的权限(除了自己组权限)。记得要使用sudo命令来执行并且chmod +x XXXscript。

1
2
3
4
5
6
7
#! /bin/sh newone="mpich" #这里添加的用户为mpich,你也可以改动。 hostloc="/etc/hosts" # create
a new member and input the password useradd -m $newone passwd $newone # get
the admin member who | awk '{print $1}' >> tmpuser user=`head -n 1 tmpuser` #
get the admin member's groups and remove the self group groupinfo=`groups
$user | awk '{print}'| sed 's/'$user'//'` # copy the groups to the new member
for i in $groupinfo;do gpasswd -a $newone $i done echo `groups $newone`

2.hostname并不是node1,node2,node3,而是其他的名字。这里必须要改,否则mpd -h -p
&命令时会有rsh connect refused 或者timeout的情况。并且mpdtrace
-l时,会有ip和hostname不对应的情况。如下是更改方法:先在/etc/hosts中删除原来的hostname,然后 vi
/etc/hostname改成你想要的,注意必须顶行写。然后使用sudo
/etc/init.d/hostname来重新得到hostname,logout一下再login就会改掉。

3. rsh krb4 krb5问题,就使用./configure -rsh=ssh就可以解决

4. ./configure时有时会让你添加–disable-c++,你需要升级你的g了,sudo apt-get install g

5.make时文件冲突,只要使用sudo make即可

6.创建了新用户,记得使用新用户来执行ssh-keygen。ssh和用户是相关的。

7.清看清我的blog题目,如果你的不是mpich2-1.2可能搭建起来与此过程不同。mpich-
1中mpd是非安装的。我之前曾照网上的文档安装就是走不通。原因是版本不对。那个官方文档是mpich2-1.0.6的…好像也稍有不同。

转自:http://ss64.com/bash/

好不容易才找到…

  [alias](http://ss64.com/bash/alias.html)
    Create an alias  

  apropos  Search Help manual pages (man -k)  

  [apt-get](http://www.debian.org/doc/manuals/apt-howto/ch-apt-get.en.html)
  Search for and install software packages (Debian)  

  [aspell](http://aspell.net/)
   Spell Checker  

  [awk](http://ss64.com/bash/awk.html)
      Find and Replace text, database sort/validate/index  

b  

  bash     GNU Bourne-Again SHell   

  [bc](http://ss64.com/bash/bc.html)
       Arbitrary precision calculator language   

  [bg](http://ss64.com/bash/bg.html)
       Send to background  

  [break](http://ss64.com/bash/break.html)
    Exit from a loop  

  [builtin](http://ss64.com/bash/builtin.html)
  Run a shell builtin  

  [bzip2](http://www.bzip.org/)
    Compress or decompress named file(s)  

c  

  [cal](http://ss64.com/bash/cal.html)
      Display a calendar  

  [case](http://ss64.com/bash/case.html)
     Conditionally perform a command  

  [cat](http://ss64.com/bash/cat.html)
      Display the contents of a file  

  [cd](http://ss64.com/bash/cd.html)
       Change Directory  

  [cfdisk](http://ss64.com/bash/cfdisk.html)
   Partition table manipulator for Linux  

  [chgrp](http://ss64.com/bash/chgrp.html)
    Change group ownership  

  [chmod](http://ss64.com/bash/chmod.html)
    Change access permissions  

  [chown](http://ss64.com/bash/chown.html)
    Change file owner and group  

  [chroot](http://ss64.com/bash/chroot.html)
   Run a command with a different root directory  

  [chkconfig](http://ss64.com/bash/chkconfig.html)
 System services (runlevel)  

  [cksum](http://ss64.com/bash/cksum.html)
    Print CRC checksum and byte counts  

  clear    Clear terminal screen  

  [cmp](http://ss64.com/bash/cmp.html)
      Compare two files  

  [comm](http://ss64.com/bash/comm.html)
     Compare two sorted files line by line  

  [command](http://ss64.com/bash/command.html)
  Run a command - ignoring shell functions  

  [continue](http://ss64.com/bash/continue.html)
 Resume the next iteration of a loop  

  [cp](http://ss64.com/bash/cp.html)
       Copy one or more files to another location  

  [cron](http://ss64.com/bash/cron.html)
     Daemon to execute scheduled commands  

  [crontab](http://ss64.com/bash/crontab.html)
  Schedule a command to run at a later time  

  [csplit](http://ss64.com/bash/csplit.html)
   Split a file into context-determined pieces  

  [cut](http://ss64.com/bash/cut.html)
      Divide a file into several parts  

d  

  [date](http://ss64.com/bash/date.html)
     Display or change the date & time  

  [dc](http://ss64.com/bash/dc.html)
       Desk Calculator  

  [dd](http://ss64.com/bash/dd.html)
       Convert and copy a file, write disk headers, boot records  

  [ddrescue](http://ss64.com/bash/ddrescue.html)
 Data recovery tool  

  [declare](http://ss64.com/bash/declare.html)
  Declare variables and give them attributes  

  [df](http://ss64.com/bash/df.html)
       Display free disk space  

  [diff](http://ss64.com/bash/diff.html)
     Display the differences between two files  

  [diff3](http://ss64.com/bash/diff3.html)
    Show differences among three files  

  [dig](http://ss64.com/bash/dig.html)
      DNS lookup  

  [dir](http://ss64.com/bash/dir.html)
      Briefly list directory contents  

  [dircolors](http://ss64.com/bash/dircolours.html)
 Colour setup for `ls'  

  [dirname](http://ss64.com/bash/dirname.html)
  Convert a full pathname to just a path  

  [dirs](http://ss64.com/bash/dirs.html)
     Display list of remembered directories  

  [dmesg](http://ss64.com/bash/dmesg.html)
    Print kernel & driver messages   

  [du](http://ss64.com/bash/du.html)
       Estimate file space usage  

e  

  [echo](http://ss64.com/bash/echo.html)
     Display message on screen  

  [egrep](http://ss64.com/bash/egrep.html)
    Search file(s) for lines that match an extended expression  

  [eject](http://ss64.com/bash/eject.html)
    Eject removable media  

  [enable](http://ss64.com/bash/enable.html)
   Enable and disable builtin shell commands  

  [env](http://ss64.com/bash/env.html)
      Environment variables  

  ethtool  Ethernet card settings  

  [eval](http://ss64.com/bash/eval.html)
     Evaluate several commands/arguments  

  [exec](http://ss64.com/bash/exec.html)
     Execute a command  

  [exit](http://ss64.com/bash/exit.html)
     Exit the shell  

  [expect](http://en.wikipedia.org/wiki/Expect)
   Automate arbitrary applications accessed over a terminal  

  [expand](http://ss64.com/bash/expand.html)
   Convert tabs to spaces  

  [export](http://ss64.com/bash/export.html)
   Set an environment variable  

  [expr](http://ss64.com/bash/expr.html)
     Evaluate expressions  

f  

  [false](http://ss64.com/bash/false.html)
    Do nothing, unsuccessfully  

  [fdformat](http://ss64.com/bash/fdformat.html)
 Low-level format a floppy disk  

  [fdisk](http://ss64.com/bash/fdisk.html)
    Partition table manipulator for Linux  

  [fg](http://ss64.com/bash/fg.html)
       Send job to foreground   

  [fgrep](http://ss64.com/bash/fgrep.html)
    Search file(s) for lines that match a fixed string  

  file     Determine file type  

  [find](http://ss64.com/bash/find.html)
     Search for files that meet a desired criteria  

  [fmt](http://ss64.com/bash/fmt.html)
      Reformat paragraph text  

  [fold](http://ss64.com/bash/fold.html)
     Wrap text to fit a specified width.  

  [for](http://ss64.com/bash/for.html)
      Expand words
, and execute commands
  format   Format disks or tapes  

  free     Display memory usage  

  [fsck](http://ss64.com/bash/fsck.html)
     File system consistency check and repair  

  ftp      File Transfer Protocol  

  [function](http://ss64.com/bash/function.html)
 Define Function Macros  

  [fuser](http://ss64.com/bash/fuser.html)
    Identify/kill the process that is accessing a file  

g  

  [gawk](http://ss64.com/bash/awk.html)
     Find and Replace text within file(s)  

  [getopts](http://ss64.com/bash/getopts.html)
  Parse positional parameters  

  [grep](http://ss64.com/bash/grep.html)
     Search file(s) for lines that match a given pattern  

  [groups](http://ss64.com/bash/groups.html)
   Print group names a user is in  

  [gzip](http://ss64.com/bash/gzip.html)
     Compress or decompress named file(s)  

h  

  [hash](http://ss64.com/bash/hash.html)
     Remember the full pathname of a name argument  

  [head](http://ss64.com/bash/head.html)
     Output the first part of file(s)  

  [history](http://ss64.com/bash/history.html)
  Command History  

  [hostname](http://ss64.com/bash/hostname.html)
 Print or set system name  

i  

  [id](http://ss64.com/bash/id.html)
       Print user and group id's  

  [if](http://ss64.com/bash/if.html)
       Conditionally perform a command  

  [ifconfig](http://ss64.com/bash/ifconfig.html)
 Configure a network interface  

  [ifdown](http://ss64.com/bash/ifup.html)
   Stop a network interface   

  [ifup](http://ss64.com/bash/ifup.html)
     Start a network interface up  

  [import](http://ss64.com/bash/import.html)
   Capture an X server screen and save the image to file  

  [install](http://ss64.com/bash/install.html)
  Copy files and set attributes  

j  

  [join](http://ss64.com/bash/join.html)
     Join lines on a common field  

k  

  [kill](http://ss64.com/bash/kill.html)
     Stop a process from running  

  killall  Kill processes by name  

l  

  [less](http://ss64.com/bash/less.html)
     Display output one screen at a time  

  [let](http://ss64.com/bash/let.html)
      Perform arithmetic on shell variables  

  [ln](http://ss64.com/bash/ln.html)
       Make links between files  

  [local](http://ss64.com/bash/local.html)
    Create variables  

  [locate](http://ss64.com/bash/locate.html)
   Find files  

  [logname](http://ss64.com/bash/logname.html)
  Print current login name  

  [logout](http://ss64.com/bash/logout.html)
   Exit a login shell  

  [look](http://ss64.com/bash/look.html)
     Display lines beginning with a given string  

  [lpc](http://ss64.com/bash/lpc.html)
      Line printer control program  

  [lpr](http://ss64.com/bash/lpr.html)
      Off line print  

  lprint   Print a file  

  lprintd  Abort a print job  

  lprintq  List the print queue  

  [lprm](http://ss64.com/bash/lprm.html)
     Remove jobs from the print queue  

  [ls](http://ss64.com/bash/ls.html)
       List information about file(s)  

  [lsof](http://ss64.com/bash/lsof.html)
     List open files  

m  

  make     Recompile a group of programs  

  [man](http://ss64.com/bash/man.html)
      Help manual  

  [mkdir](http://ss64.com/bash/mkdir.html)
    Create new folder(s)  

  [mkfifo](http://ss64.com/bash/mkfifo.html)
   Make FIFOs (named pipes)  

  mkisofs  Create an hybrid ISO9660/JOLIET/HFS filesystem  

  [mknod](http://ss64.com/bash/mknod.html)
    Make block or character special files  

  [more](http://ss64.com/bash/more.html)
     Display output one screen at a time  

  [mount](http://ss64.com/bash/mount.html)
    Mount a file system  

  [mtools](http://ss64.com/bash/mtools.html)
   Manipulate MS-DOS files  

  [mv](http://ss64.com/bash/mv.html)
       Move or rename files or directories  

  [mmv](http://ss64.com/bash/mmv.html)
      Mass Move and rename (files)  

n  

  netstat  Networking information  

  [nice](http://ss64.com/bash/nice.html)
     Set the priority of a command or job  

  [nl](http://ss64.com/bash/nl.html)
       Number lines and write files  

  [nohup](http://ss64.com/bash/nohup.html)
    Run a command immune to hangups  

  [nslookup](http://ss64.com/bash/nslookup.html)
 Query Internet name servers interactively  

o  

  [open](http://ss64.com/bash/open.html)
     Open a file in its default application  

  [op](http://ss64.com/bash/op.html)
       Operator access   

p  

  [passwd](http://ss64.com/bash/passwd.html)
   Modify a user password  

  [paste](http://ss64.com/bash/paste.html)
    Merge lines of files  

  pathchk  Check file name portability  

  [ping](http://ss64.com/bash/ping.html)
     Test a network connection  

  [pkill](http://ss64.com/bash/pkill.html)
    Stop processes from running  

  [popd](http://ss64.com/bash/popd.html)
     Restore the previous value of the current directory  

  [pr](http://ss64.com/bash/pr.html)
       Prepare files for printing  

  printcap Printer capability database  

  printenv Print environment variables  

  [printf](http://ss64.com/bash/printf.html)
   Format and print data  

  [ps](http://ss64.com/bash/ps.html)
       Process status  

  [pushd](http://ss64.com/bash/pushd.html)
    Save and then change the current directory  

  [pwd](http://ss64.com/bash/pwd.html)
      Print Working Directory  

q  

  [quota](http://ss64.com/bash/quota.html)
    Display disk usage and limits  

  [quotacheck](http://ss64.com/bash/quotacheck.html)
 Scan a file system for disk usage  

  [quotactl](http://ss64.com/bash/quotactl.html)
 Set disk quotas  

r  

  [ram](http://ss64.com/bash/ram.html)
      ram disk device  

  [rcp](http://ss64.com/bash/rcp.html)
      Copy files between two machines  

  [read](http://ss64.com/bash/read.html)
     read a line from standard input  

  [readonly](http://ss64.com/bash/readonly.html)
 Mark variables/functions as readonly  

  reboot   Reboot the system  

  renice   Alter priority of running processes   

  remsync  Synchronize remote files via email  

  [return](http://ss64.com/bash/return.html)
   Exit a shell function  

  [rev](http://ss64.com/bash/rev.html)
      Reverse lines of a file  

  [rm](http://ss64.com/bash/rm.html)
       Remove files  

  [rmdir](http://ss64.com/bash/rmdir.html)
    Remove folder(s)  

  [rsync](http://ss64.com/bash/rsync.html)
    Remote file copy (Synchronize file trees)  

s  

  [screen](http://ss64.com/bash/screen.html)
   Multiplex terminal, run remote shells via ssh  

  [scp](http://ss64.com/bash/scp.html)
      Secure copy (remote file copy)  

  [sdiff](http://ss64.com/bash/sdiff.html)
    Merge two files interactively  

  [sed](http://ss64.com/bash/sed.html)
      Stream Editor  

  [select](http://ss64.com/bash/select.html)
   Accept keyboard input  

  [seq](http://ss64.com/bash/seq.html)
      Print numeric sequences  

  [set](http://ss64.com/bash/set.html)
      Manipulate shell variables and functions  

  sftp     Secure File Transfer Program  

  [shift](http://ss64.com/bash/shift.html)
    Shift positional parameters  

  [shopt](http://ss64.com/bash/shopt.html)
    Shell Options  

  [shutdown](http://ss64.com/bash/shutdown.html)
 Shutdown or restart linux  

  [sleep](http://ss64.com/bash/sleep.html)
    Delay for a specified time  

  [slocate](http://ss64.com/bash/slocate.html)
  Find files  

  [sort](http://ss64.com/bash/sort.html)
     Sort text files  

  [source](http://ss64.com/bash/source.html)
   Run commands from a file `.'  

  [split](http://ss64.com/bash/split.html)
    Split a file into fixed-size pieces  

  [ssh](http://en.wikipedia.org/wiki/Secure_Shell)
      Secure Shell client (remote login program)  

  strace   Trace system calls and signals  

  [su](http://ss64.com/bash/su.html)
       Substitute user identity  

  [sudo](http://ss64.com/bash/sudo.html)
     Execute a command as another user  

  [sum](http://ss64.com/bash/sum.html)
      Print a checksum for a file  

  [symlink](http://ss64.com/bash/symlink.html)
  Make a new name for a file  

  [sync](http://ss64.com/bash/sync.html)
     Synchronize data on disk with memory  

t  

  [tail](http://ss64.com/bash/tail.html)
     Output the last part of files  

  [tar](http://ss64.com/bash/tar.html)
      Tape ARchiver  

  [tee](http://ss64.com/bash/tee.html)
      Redirect output to multiple files  

  [test](http://ss64.com/bash/test.html)
     Evaluate a conditional expression  

  [time](http://ss64.com/bash/time.html)
     Measure Program running time  

  [times](http://ss64.com/bash/times.html)
    User and system times  

  [touch](http://ss64.com/bash/touch.html)
    Change file timestamps  

  [top](http://ss64.com/bash/top.html)
      List processes running on the system  

  [traceroute](http://ss64.com/bash/traceroute.html)
 Trace Route to Host  

  trap     Run a command when a signal is set(bourne)  

  [tr](http://ss64.com/bash/tr.html)
       Translate, squeeze, and/or delete characters  

  [true](http://ss64.com/bash/true.html)
     Do nothing, successfully  

  [tsort](http://ss64.com/bash/tsort.html)
    Topological sort  

  [tty](http://ss64.com/bash/tty.html)
      Print filename of terminal on stdin  

  [type](http://ss64.com/bash/type.html)
     Describe a command  

u  

  [ulimit](http://ss64.com/bash/ulimit.html)
   Limit user resources  

  [umask](http://ss64.com/bash/umask.html)
    Users file creation mask  

  umount   Unmount a device  

  [unalias](http://ss64.com/bash/alias.html)
  Remove an alias  

  [uname](http://ss64.com/bash/uname.html)
    Print system information  

  [unexpand](http://ss64.com/bash/unexpand.html)
 Convert spaces to tabs  

  [uniq](http://ss64.com/bash/uniq.html)
     Uniquify files  

  [units](http://ss64.com/bash/units.html)
    Convert units from one scale to another  

  [unset](http://ss64.com/bash/unset.html)
    Remove variable or function names  

  [unshar](http://ss64.com/bash/unshar.html)
   Unpack shell archive scripts  

  [until](http://ss64.com/bash/until.html)
    Execute commands (until error)  

  [useradd](http://ss64.com/bash/useradd.html)
  Create new user account  

  [usermod](http://ss64.com/bash/usermod.html)
  Modify user account  

  [users](http://ss64.com/bash/users.html)
    List users currently logged in  

  [uuencode](http://ss64.com/bash/uuencode.html)
 Encode a binary file   

  [uudecode](http://ss64.com/bash/uuencode.html)
 Decode a file created by uuencode  

v  

  v        Verbosely list directory contents (`ls -l -b')  

  vdir     Verbosely list directory contents (`ls -l -b')  

  [vi](http://ss64.com/bash/vi.html)
       Text Editor  

  [vmstat](http://ss64.com/bash/vmstat.html)
   Report virtual memory statistics  

w  

  [watch](http://ss64.com/bash/watch.html)
    Execute/display a program periodically  

  [wc](http://ss64.com/bash/wc.html)
       Print byte, word, and line counts  

  [whereis](http://ss64.com/bash/whereis.html)
  Report all known instances of a command      

  [which](http://ss64.com/bash/which.html)
    Locate a program file in the user's path.   

  [while](http://ss64.com/bash/while.html)
    Execute commands  

  [who](http://ss64.com/bash/who.html)
      Print all usernames currently logged in  

  [whoami](http://ss64.com/bash/whoami.html)
   Print the current user id and name (`id -un')  

  Wget     Retrieve web pages or files via HTTP, HTTPS or FTP  

  [write](http://ss64.com/bash/write.html)
    Send a message to another user   

x  

  [xargs](http://ss64.com/bash/xargs.html)
    Execute utility, passing constructed argument list(s)  

  [yes](http://ss64.com/bash/yes.html)
      Print a string until interrupted  

  [.](http://ss64.com/bash/source.html)
        Run a command script in the current shell  

  [###](http://ss64.com/bash/rem.html)
      Comment / Remark  

转载自:

http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-1/index.html

http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-2/index.html

http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-3/index.html

在 UNIX 世界中有很多文本编辑器可供我们选择。思考一下 – vi、emacs 和 jed
以及很多其它工具都会浮现在脑海中。我们都有自己已逐渐了解并且喜爱的编辑器(以及我们喜爱的组合键)。有了可信赖的编辑器,我们可以轻松处理任何数量与 UNIX
有关的管理或编程任务。

虽然交互式编辑器很棒,但却有其限制。尽管其交互式特性可以成为强项,但也有其不足之处。考虑一下需要对一组文件执行类似更改的情形。您可能会本能地运行自己所喜爱的
编辑器,然后手工执行一组烦琐、重复和耗时的编辑任务。然而,有一种更好的方法。

进入 sed
如果可以使编辑文件的过程自动化,以便用“批处理”方式编辑文件,甚至编写可以对现有文件进行复杂更改的脚本,那将太好了。幸运的是,对于这种情况,有一种更好的方法
– 这种更好的方法称为 “sed”。

sed 是一种几乎包括在所有 UNIX 平台(包括 Linux)的轻量级流编辑器。sed
有许多很好的特性。首先,它相当小巧,通常要比您所喜爱的脚本语言小很多倍。其次,因为 sed
是一种流编辑器,所以,它可以对从如管道这样的标准输入接收的数据进行编辑。因此,无需将要编辑的数据存储在磁盘上的文件中。因为可以轻易将数据管道输出 到
sed,所以,将 sed 用作强大的 shell 脚本中长而复杂的管道很容易。试一下用您所喜爱的编辑器去那样做。

GNU sed
对 Linux 用户来说幸运的是,最好的 sed 版本之一恰好是 GNU sed,其当前版本是 3.02。每一个 Linux
发行版都有(或至少应该有)GNU sed。GNU sed 之所以流行不仅因为可以自由分发其源代码,还因为它恰巧有许多对 POSIX sed
标准便利、省时的扩展。另外,GNU 没有 sed 早期专门版本的很多限制,如行长度限制 – GNU 可以轻松处理任意长度的行。

最新的 GNU sed
在 研究这篇文章之时我注意到:几个在线 sed 爱好者提到 GNU sed
3.02a。奇怪的是,在ftp.gnu.org(有关这些链接,请参阅参考资料)上找不到 sed
3.02a,所以,我只得在别处寻找。我在alpha.gnu.org 的 /pub/sed
中找到了它。于是我高兴地将其下载、编译然后安装,而几分钟后我发现最新的 sed 版本却是 3.02.80 – 可在alpha.gnu.org
3.02a 源代码旁边找到其源代码。安装完 GNU sed 3.02.80 之后,我就完全准备好了。

alpha.gnu.org
alpha.gnu.org(请 参阅参考资料)是新的和实验性 GNU 源代码的所在地。然而,您还会在那里发现许多优秀、稳定的源代码。出于某种原因,不是许多
GNU 开发人员忘记将稳定的源代码移至 ftp.gnu.org,就是它们的 “beta” 期间格外长(2 年!)。例如,sed 3.02a 已有两年,甚至
3.02.80 也有一年,但它们仍不能(在 2000 年 8 月写本文章时)在 ftp.gnu.org 上获得。

正确的 sed
在 本系列中,将使用 GNU sed 3.02.80。在即将出现的本系列后续文章中,某些(但非常少)最高级的示例将不能在 GNU sed 3.02 或
3.02a 中使用。如果您使用的不是 GNU sed,那么结果可能会不同。现在为什么不花些时间安装 GNU sed 3.02.80
呢?那样,不仅可以为本系列的余下部分作好准备,而且还可以使用可能是目前最好的 sed。

sed 示例
sed 通过对输入数据执行任意数量用户指定的编辑操作(“命令”)来工作。sed 是基于行的,因此按顺序对每一行执行命令。然后,sed 将其结果写入标准输出
(stdout),它不修改任何输入文件。

让我们看一些示例。头几个会有些奇怪,因为我要用它们演示 sed 如何工作,而不是执行任何有用的任务。然而,如果您是 sed
新手,那么理解它们是十分重要的。下面是第一个示例:

1
$ sed -e 'd' /etc/services  

如 果输入该命令,将得不到任何输出。那么,发生了什么?在该例中,用一个编辑命令 ‘d’ 调用 sed。sed 打开 /etc/services
文件,将一行读入其模式缓冲区,执行编辑命令(“删除行”),然后打印模式缓冲区(缓冲区已为空)。然后,它对后面的每一行重复这些步骤。这不会产生输 出,因为
“d” 命令除去了模式缓冲区中的每一行!

在该例中,还有几件事要注意。首先,根本没有修改 /etc/services。这还是因为 sed 只读取在命令行指定的文件,将其用作输入 –
它不试图修改该文件。第二件要注意的事是 sed 是面向行的。‘d’ 命令不是简单地告诉 sed 一下子删除所有输入数据。相反,sed 逐行将
/etc/services 的每一行读入其称为模式缓冲区的内部缓冲区。一旦将一行读入模式缓冲区,它就执行 ‘d’
命令,然后打印模式缓冲区的内容(在本例中没有内容)。我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行 –
但是,如果不使用地址,命令将应用到所有行。

第三件要注意的事是括起 ‘d’ 命令的单引号的用法。养成使用单引号来括起 sed 命令的习惯是个好注意,这样可以禁用 shell 扩展。

另一个 sed 示例
下面是使用 sed 从输出流除去 /etc/services 文件第一行的示例:

1
2
3
4
5
6
7
8
9
10
$ sed -e '1d' /etc/services | more  
```

如 您所见,除了前面有 '1' 之外,该命令与第一个 'd' 命令十分类似。如果您猜到 '1' 指的是第一行,那您就猜对了。与第一个示例中只使用 'd'
不同的是,这一次使用的 'd' 前面有一个可选的数字地址。通过使用地址,可以告诉 sed 只对某一或某些特定行进行编辑。

地址范围
现在,让我们看一下如何指定地址范围。在本例中,sed 将删除输出的第 1 到 10 行:
```bash
$ sed -e '1,10d' /etc/services | more

当用逗号将两个地址分开时,sed 将把后面的命令应用到从第一个地址开始、到第二个地址结束的范围。在本例中,将 ‘d’ 命令应用到第 1 到 10
行(包括这两行)。所有其它行都被忽略。

带规则表达式的地址
现 在演示一个更有用的示例。假设要查看 /etc/services 文件的内容,但是对查看其中包括的注释部分不感兴趣。如您所知,可以通过以 ‘#’
字符开头的行在 /etc/services 文件中放置注释。为了避免注释,我们希望 sed 删除以 ‘#’ 开始的行。以下是具体做法:

1
$ sed -e '/^#/d' /etc/services | more  

试一下该例,看看发生了什么。您将注意到,sed 成功完成了预期任务。现在,让我们分析发生的情况。

要 理解 ‘/^#/d’ 命令,首先需要对其剖析。首先,让我们除去 ‘d’ – 这是我们前面所使用的同一个删除行命令。新增加的是 ‘/^#/’
部分,它是一种新的规则表达式地址。规则表达式地址总是由斜杠括起。它们指定一种 模式,紧跟在规则表达式地址之后的命令将仅适用于正好与该特定模式匹配的行。

因此,‘/^#/’ 是一个规则表达式。但是,它做些什么呢?很明显,现在该复习规则表达式了。

规则表达式复习
可以使用规则表达式来表示可能会在文本中发现的模式。您在 shell 命令行中用过 ‘*’
字符吗?这种用法与规则表达式类似,但并不相同。下面是可以在规则表达式中使用的特殊字符:

字符 描述
与行首匹配
与行末尾匹配
与任一个字符匹配
将与前一个字符的零或多个出现匹配
[ ] 与 [ ] 之内的所有字符匹配

感受规则表达式的最好方法可能是看几个示例。所有这些示例都将被 sed 作为合法地址接受,这些地址出现在命令的左边。下面是几个示例:

规则

1
2
3
4
5
6
7
8
9
10
表达式 描述  
/./ 将与包含至少一个字符的任何行匹配
/../ 将与包含至少两个字符的任何行匹配
/^#/ 将与以 '#' 开始的任何行匹配
/^$/ 将与所有空行匹配
/}^/ 将与以 '}'(无空格)结束的任何行匹配
/} *^/ 将与以 '}' 后面跟有零或多个空格结束的任何行匹配
/[abc]/ 将与包含小写 'a'、'b' 或 'c' 的任何行匹配
/^[abc]/ 将与以 'a'、'b' 或 'c'开始的任何行匹配

在这些示例中,鼓励您尝试几个。花一些时间熟悉规则表达式,然后尝试几个自己创建的规则表达式。可以如下使用 regexp:

1
$ sed -e '/regexp/d' /path/to/my/test/file | more  

这将导致 sed 删除任何匹配的行。然而,通过告诉 sed打印 regexp
匹配并删除不匹配的内容,而不是与之相反的方法,会更有利于熟悉规则表达式。可以用以下命令这样做:

1
$ sed -n -e '/regexp/p' /path/to/my/test/file | more  

请注意新的 ‘-n’ 选项,该选项告诉 sed 除非明确要求打印模式空间,否则不这样做。您还会注意到,我们用 ‘p’ 命令替换了 ‘d’
命令,如您所猜想的那样,这明确要求 sed 打印模式空间。就这样,将只打印匹配部分。

有关地址的更多内容
目 前为止,我们已经看到了行地址、行范围地址和 regexp 地址。但是,还有更多的可能。我们可以指定两个用逗号分开的规则表达式,sed
将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配。例如,以下命令将打印从包含 “BEGIN”
的行开始,并且以包含 “END” 的行结束的文本块:

1
$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more  

如果没发现 “BEGIN”,那么将不打印数据。如果发现了 “BEGIN”,但是在这之后的所有行中都没发现
“END”,那么将打印所有后续行。发生这种情况是因为 sed 面向流的特性 – 它不知道是否会出现 “END”。

C 源代码示例
如果只要打印 C 源文件中的 main() 函数,可输入:

1
$ sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more  

该 命令有两个规则表达式 ‘/main[[:space:]]*(/’ 和 ‘/^}/’,以及一个命令
‘p’。第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串 “main” 匹配。这应该与一般 ANSI C main()
声明的开始匹配。

在这个特别的规则表达式中,出现了 ‘[[:space:]]’ 字符类。这只是一个特殊的关键字,它告诉 sed 与 TAB
或空格匹配。如果愿意的话,可以不输入 ‘[[:space:]]’,而输入 ‘[’,然后是空格字母,然后是 -V,然后再输入制表键字母和 ‘]’ –
Control-V 告诉 bash 要插入“真正”的制表键,而不是执行命令扩展。使用 ‘[[:space:]]’ 命令类(特别是在脚本中)会更清楚。

好,现在看一下第二个 regexp。‘/^}’ 将与任何出现在新行行首的 ‘}’ 字符匹配。如果代码的格式很好,那么这将与 main()
函数的结束花括号匹配。如果格式不好,则不会正确匹配 – 这是执行模式匹配任务的一件棘手之事。

因为是处于 ‘-n’ 安静方式,所以 ‘p’ 命令还是完成其惯有任务,即明确告诉 sed 打印该行。试着对 C 源文件运行该命令 – 它应该输出整个
main() { } 块,包括开始的 “main()” 和结束的 ‘}’。在 UNIX 世界中有很多文本编辑器可供我们选择。思考一下 – vi、emacs
和 jed 以及很多其它工具都会浮现在脑海中。我们都有自己已逐渐了解并且喜爱的编辑器(以及我们喜爱的组合键)。有了可信赖的编辑器,我们可以轻松处理任何数量与
UNIX 有关的管理或编程任务。

虽然交互式编辑器很棒,但却有其限制。尽管其交互式特性可以成为强项,但也有其不足之处。考虑一下需要对一组文件执行类似更改的情形。您可能会本能地运行自己所喜爱的
编辑器,然后手工执行一组烦琐、重复和耗时的编辑任务。然而,有一种更好的方法。

进入 sed
如果可以使编辑文件的过程自动化,以便用“批处理”方式编辑文件,甚至编写可以对现有文件进行复杂更改的脚本,那将太好了。幸运的是,对于这种情况,有一种更好的方法
– 这种更好的方法称为 “sed”。

sed 是一种几乎包括在所有 UNIX 平台(包括 Linux)的轻量级流编辑器。sed
有许多很好的特性。首先,它相当小巧,通常要比您所喜爱的脚本语言小很多倍。其次,因为 sed
是一种流编辑器,所以,它可以对从如管道这样的标准输入接收的数据进行编辑。因此,无需将要编辑的数据存储在磁盘上的文件中。因为可以轻易将数据管道输出 到
sed,所以,将 sed 用作强大的 shell 脚本中长而复杂的管道很容易。试一下用您所喜爱的编辑器去那样做。

GNU sed
对 Linux 用户来说幸运的是,最好的 sed 版本之一恰好是 GNU sed,其当前版本是 3.02。每一个 Linux
发行版都有(或至少应该有)GNU sed。GNU sed 之所以流行不仅因为可以自由分发其源代码,还因为它恰巧有许多对 POSIX sed
标准便利、省时的扩展。另外,GNU 没有 sed 早期专门版本的很多限制,如行长度限制 – GNU 可以轻松处理任意长度的行。

最新的 GNU sed
在 研究这篇文章之时我注意到:几个在线 sed 爱好者提到 GNU sed
3.02a。奇怪的是,在ftp.gnu.org(有关这些链接,请参阅参考资料)上找不到 sed
3.02a,所以,我只得在别处寻找。我在alpha.gnu.org 的 /pub/sed
中找到了它。于是我高兴地将其下载、编译然后安装,而几分钟后我发现最新的 sed 版本却是 3.02.80 – 可在alpha.gnu.org
3.02a 源代码旁边找到其源代码。安装完 GNU sed 3.02.80 之后,我就完全准备好了。

alpha.gnu.org
alpha.gnu.org(请 参阅参考资料)是新的和实验性 GNU 源代码的所在地。然而,您还会在那里发现许多优秀、稳定的源代码。出于某种原因,不是许多
GNU 开发人员忘记将稳定的源代码移至 ftp.gnu.org,就是它们的 “beta” 期间格外长(2 年!)。例如,sed 3.02a 已有两年,甚至
3.02.80 也有一年,但它们仍不能(在 2000 年 8 月写本文章时)在 ftp.gnu.org 上获得。

正确的 sed
在 本系列中,将使用 GNU sed 3.02.80。在即将出现的本系列后续文章中,某些(但非常少)最高级的示例将不能在 GNU sed 3.02 或
3.02a 中使用。如果您使用的不是 GNU sed,那么结果可能会不同。现在为什么不花些时间安装 GNU sed 3.02.80
呢?那样,不仅可以为本系列的余下部分作好准备,而且还可以使用可能是目前最好的 sed。

sed 示例
sed 通过对输入数据执行任意数量用户指定的编辑操作(“命令”)来工作。sed 是基于行的,因此按顺序对每一行执行命令。然后,sed 将其结果写入标准输出
(stdout),它不修改任何输入文件。

让我们看一些示例。头几个会有些奇怪,因为我要用它们演示 sed 如何工作,而不是执行任何有用的任务。然而,如果您是 sed
新手,那么理解它们是十分重要的。下面是第一个示例:

1
$ sed -e 'd' /etc/services  

如 果输入该命令,将得不到任何输出。那么,发生了什么?在该例中,用一个编辑命令 ‘d’ 调用 sed。sed 打开 /etc/services
文件,将一行读入其模式缓冲区,执行编辑命令(“删除行”),然后打印模式缓冲区(缓冲区已为空)。然后,它对后面的每一行重复这些步骤。这不会产生输 出,因为
“d” 命令除去了模式缓冲区中的每一行!

在该例中,还有几件事要注意。首先,根本没有修改 /etc/services。这还是因为 sed 只读取在命令行指定的文件,将其用作输入 –
它不试图修改该文件。第二件要注意的事是 sed 是面向行的。‘d’ 命令不是简单地告诉 sed 一下子删除所有输入数据。相反,sed 逐行将
/etc/services 的每一行读入其称为模式缓冲区的内部缓冲区。一旦将一行读入模式缓冲区,它就执行 ‘d’
命令,然后打印模式缓冲区的内容(在本例中没有内容)。我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行 –
但是,如果不使用地址,命令将应用到所有行。

第三件要注意的事是括起 ‘d’ 命令的单引号的用法。养成使用单引号来括起 sed 命令的习惯是个好注意,这样可以禁用 shell 扩展。

另一个 sed 示例
下面是使用 sed 从输出流除去 /etc/services 文件第一行的示例:

1
$ sed -e '1d' /etc/services | more  

如 您所见,除了前面有 ‘1’ 之外,该命令与第一个 ‘d’ 命令十分类似。如果您猜到 ‘1’ 指的是第一行,那您就猜对了。与第一个示例中只使用 ‘d’
不同的是,这一次使用的 ‘d’ 前面有一个可选的数字地址。通过使用地址,可以告诉 sed 只对某一或某些特定行进行编辑。

地址范围
现在,让我们看一下如何指定地址范围。在本例中,sed 将删除输出的第 1 到 10 行:

1
$ sed -e '1,10d' /etc/services | more  

当用逗号将两个地址分开时,sed 将把后面的命令应用到从第一个地址开始、到第二个地址结束的范围。在本例中,将 ‘d’ 命令应用到第 1 到 10
行(包括这两行)。所有其它行都被忽略。

带规则表达式的地址
现 在演示一个更有用的示例。假设要查看 /etc/services 文件的内容,但是对查看其中包括的注释部分不感兴趣。如您所知,可以通过以 ‘#’
字符开头的行在 /etc/services 文件中放置注释。为了避免注释,我们希望 sed 删除以 ‘#’ 开始的行。以下是具体做法:

1
$ sed -e '/^#/d' /etc/services | more  

试一下该例,看看发生了什么。您将注意到,sed 成功完成了预期任务。现在,让我们分析发生的情况。

要 理解 ‘/^#/d’ 命令,首先需要对其剖析。首先,让我们除去 ‘d’ – 这是我们前面所使用的同一个删除行命令。新增加的是 ‘/^#/’
部分,它是一种新的规则表达式地址。规则表达式地址总是由斜杠括起。它们指定一种 模式,紧跟在规则表达式地址之后的命令将仅适用于正好与该特定模式匹配的行。

因此,‘/^#/’ 是一个规则表达式。但是,它做些什么呢?很明显,现在该复习规则表达式了。

规则表达式复习
可以使用规则表达式来表示可能会在文本中发现的模式。您在 shell 命令行中用过 ‘*’
字符吗?这种用法与规则表达式类似,但并不相同。下面是可以在规则表达式中使用的特殊字符:

字符 描述
与行首匹配
与行末尾匹配
与任一个字符匹配
将与前一个字符的零或多个出现匹配
[ ] 与 [ ] 之内的所有字符匹配

感受规则表达式的最好方法可能是看几个示例。所有这些示例都将被 sed 作为合法地址接受,这些地址出现在命令的左边。下面是几个示例:

规则

1
2
3
4
5
6
7
8
9
10
表达式 描述  
/./ 将与包含至少一个字符的任何行匹配
/../ 将与包含至少两个字符的任何行匹配
/^#/ 将与以 '#' 开始的任何行匹配
/^$/ 将与所有空行匹配
/}^/ 将与以 '}'(无空格)结束的任何行匹配
/} *^/ 将与以 '}' 后面跟有零或多个空格结束的任何行匹配
/[abc]/ 将与包含小写 'a'、'b' 或 'c' 的任何行匹配
/^[abc]/ 将与以 'a'、'b' 或 'c'开始的任何行匹配

在这些示例中,鼓励您尝试几个。花一些时间熟悉规则表达式,然后尝试几个自己创建的规则表达式。可以如下使用 regexp:

1
$ sed -e '/regexp/d' /path/to/my/test/file | more  

这将导致 sed 删除任何匹配的行。然而,通过告诉 sed打印 regexp
匹配并删除不匹配的内容,而不是与之相反的方法,会更有利于熟悉规则表达式。可以用以下命令这样做:

1
$ sed -n -e '/regexp/p' /path/to/my/test/file | more  

请注意新的 ‘-n’ 选项,该选项告诉 sed 除非明确要求打印模式空间,否则不这样做。您还会注意到,我们用 ‘p’ 命令替换了 ‘d’
命令,如您所猜想的那样,这明确要求 sed 打印模式空间。就这样,将只打印匹配部分。

有关地址的更多内容
目 前为止,我们已经看到了行地址、行范围地址和 regexp 地址。但是,还有更多的可能。我们可以指定两个用逗号分开的规则表达式,sed
将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配。例如,以下命令将打印从包含 “BEGIN”
的行开始,并且以包含 “END” 的行结束的文本块:

1
$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more  

如果没发现 “BEGIN”,那么将不打印数据。如果发现了 “BEGIN”,但是在这之后的所有行中都没发现
“END”,那么将打印所有后续行。发生这种情况是因为 sed 面向流的特性 – 它不知道是否会出现 “END”。

C 源代码示例
如果只要打印 C 源文件中的 main() 函数,可输入:

1
$ sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more  

该 命令有两个规则表达式 ‘/main[[:space:]]*(/’ 和 ‘/^}/’,以及一个命令
‘p’。第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串 “main” 匹配。这应该与一般 ANSI C main()
声明的开始匹配。

在这个特别的规则表达式中,出现了 ‘[[:space:]]’ 字符类。这只是一个特殊的关键字,它告诉 sed 与 TAB
或空格匹配。如果愿意的话,可以不输入 ‘[[:space:]]’,而输入 ‘[’,然后是空格字母,然后是 -V,然后再输入制表键字母和 ‘]’ –
Control-V 告诉 bash 要插入“真正”的制表键,而不是执行命令扩展。使用 ‘[[:space:]]’ 命令类(特别是在脚本中)会更清楚。

好,现在看一下第二个 regexp。‘/^}’ 将与任何出现在新行行首的 ‘}’ 字符匹配。如果代码的格式很好,那么这将与 main()
函数的结束花括号匹配。如果格式不好,则不会正确匹配 – 这是执行模式匹配任务的一件棘手之事。

因为是处于 ‘-n’ 安静方式,所以 ‘p’ 命令还是完成其惯有任务,即明确告诉 sed 打印该行。试着对 C 源文件运行该命令 – 它应该输出整个
main() { } 块,包括开始的 “main()” 和结束的 ‘}’。

替换!
让我们看一下 sed 最有用的命令之一,替换命令。使用该命令,可以将特定字符串或匹配的规则表达式用另一个字符串替换。下面是该命令最基本用法的示例:

1
$ sed -e 's/foo/bar/' myfile.txt  

上 面的命令将 myfile.txt 中每行第一次出现的 ‘foo’(如果有的话)用字符串 ‘bar’
替换,然后将该文件内容输出到标准输出。请注意,我说的是每行第一次出现,尽管这通常不是您想要的。在进行字符串替换时,通常想执行全局替换。也就是说,
要替换每行中的所有出现,如下所示:

1
$ sed -e 's/foo/bar/g' myfile.txt  

在最后一个斜杠之后附加的 ‘g’ 选项告诉 sed 执行全局替换。

关于 ‘s///’ 替换命令,还有其它几件要了解的事。首先,它是一个命令,并且只是一个命令,在所有上例中都没有指定地址。这意味着,‘s///’
还可以与地址一起使用来控制要将命令应用到哪些行,如下所示:

1
$ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt  

上例将导致用短语 ‘entrapment’ 替换所有出现的短语 ‘enchantment’,但是只在第一到第十行(包括这两行)上这样做。

1
$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt  

该例将用 ‘mountains’ 替换 ‘hills’,但是,只从空行开始,到以三个字符 ‘END’ 开始的行结束(包括这两行)的文本块上这样做。

关 于 ‘s///’ 命令的另一个妙处是 ‘/’ 分隔符有许多替换选项。如果正在执行字符串替换,并且规则表达式或替换字符串中有许多斜杠,则可以通过在 ‘s’
之后指定一个不同的字符来更改分隔符。例如,下例将把所有出现的 /usr/local 替换成 /usr:

1
$ sed -e 's:/usr/local:/usr:g' mylist.txt  

在该例中,使用冒号作为分隔符。如果需要在规则表达式中指定分隔符字符,可以在它前面加入反斜杠。

规则表达式混乱
目前为止,我们只执行了简单的字符串替换。虽然这很方便,但是我们还可以匹配规则表达式。例如,以下 sed 命令将匹配从 ‘<’ 开始、到 ‘>’
结束、并且在其中包含任意数量字符的短语。下例将删除该短语(用空字符串替换):

1
$ sed -e 's/<.*>//g' myfile.html  

这 是要从文件除去 HTML 标记的第一个很好的 sed 脚本尝试,但是由于规则表达式的特有规则,它不会很好地工作。原因何在?当 sed
试图在行中匹配规则表达式时,它要在行中查找最长的匹配。在我的前一篇 sed 文章中,这不成问题,因为我们使用的是 ‘d’ 和 ‘p’
命令,这些命令总要删除或打印整行。但是,在使用 ‘s///’
命令时,确实有很大不同,因为规则表达式匹配的整个部分将被目标字符串替换,或者,在本例中,被删除。这意味着,上例将把下行:

This is what I meant.
变成:

meant.
我们要的不是这个,而是:

This is what I meant.
幸 运的是,有一种简便方法来纠正该问题。我们不输入“‘<’ 字符后面跟有一些字符并以 ‘>’ 字符结束”的规则表达式,而只需输入一个“‘<’
字符后面跟有任意数量非 ‘>’ 字符并以 ‘>’ 字符结束”的规则表达式。这将与最短、而不是最长的可能性匹配。新命令如下:

1
$ sed -e 's/<[^>]*>//g' myfile.html  

在上例中,‘[^>]’ 指定“非 ‘>’”字符,其后的 ‘*’ 完成该表达式以表示“零或多个非 ‘>’ 字符”。对几个 html
文件测试该命令,将它们管道输出到 “more”,然后仔细查看其结果。

更多字符匹配
‘[ ]’ 规则表达式语法还有一些附加选项。要指定字符范围,只要字符不在第一个或最后一个位置,就可以使用 ‘-’,如下所示:

‘[a-x]*’
这将匹配零或多个全部为 ‘a’、‘b’、‘c’…‘v’、‘w’、‘x’ 的字符。另外,可以使用 ‘[:space:]’
字符类来匹配空格。以下是可用字符类的相当完整的列表:

字符类 描述
[:alnum:] 字母数字 [a-z A-Z 0-9]
[:alpha:] 字母 [a-z A-Z]
[:blank:] 空格或制表键
[:cntrl:] 任何控制字符
[:digit:] 数字 [0-9]
[:graph:] 任何可视字符(无空格)
[:lower:] 小写 [a-z]
[:print:] 非控制字符
[:punct:] 标点字符
[:space:] 空格
[:upper:] 大写 [A-Z]
[:xdigit:] 十六进制数字 [0-9 a-f A-F]

尽可能使用字符类是很有利的,因为它们可以更好地适应非英语 locale(包括某些必需的重音字符等等).

高级替换功能
我们已经看到如何执行简单甚至有些复杂的直接替换,但是 sed
还可以做更多的事。实际上可以引用匹配规则表达式的部分或全部,并使用这些部分来构造替换字符串。作为示例,假设您正在回复一条消息。下例将在每一行前面加上短语
"ralph said: ":

1
2
$ sed -e 's/.*/ralph said: &/' origmsg.txt  

输出如下:

ralph said: Hiya Jim, ralph said: ralph said:
I sure like this sed stuff! ralph said:
该例的替换字符串中使用了 ‘&’ 字符,该字符告诉 sed 插入整个匹配的规则表达式。因此,可以将与 ‘.*’
匹配的任何内容(行中的零或多个字符的最大组或整行)插入到替换字符串中的任何位置,甚至多次插入。这非常好,但 sed 甚至更强大。

那些极好的带反斜杠的圆括号
‘s///’ 命令甚至比 ‘&’ 更好,它允许我们在规则表达式中定义区域,然后可以在替换字符串中引用这些特定区域。作为示例,假设有一个包含以下文本的文件:

foo bar oni eeny meeny miny larry curly moe jimmy the weasel
现在假设要编写一个 sed 脚本,该脚本将把 “eeny meeny miny” 替换成 “Victor eeny-meeny Von miny”
等等。要这样做,首先要编写一个由空格分隔并与三个字符串匹配的规则表达式。

‘.* .* .*’
现在,将在其中每个感兴趣的区域两边插入带反斜杠的圆括号来定义区域:

‘/(./) /(./) /(.*/)’
除了要定义三个可在替换字符串中引用的逻辑区域以外,该规则表达式的工作原理将与第一个规则表达式相同。下面是最终脚本:

1
2
$ sed -e 's//(.*/) /(.*/) /(.*/)/Victor /1-/2 Von /3/' myfile.txt  

如您所见,通过输入 ‘/x’(其中,x 是从 1 开始的区域号)来引用每个由圆括号定界的区域。输入如下:

Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe
Victor jimmy-the Von weasel
随着对 sed 越来越熟悉,您可以花最小力气来进行相当强大的文本处理。您可能想如何使用熟悉的脚本语言来处理这种问题 –
能用一行代码轻易实现这样的解决方案吗?

组合使用
在 开始创建更复杂的 sed 脚本时,需要有输入多个命令的能力。有几种方法这样做。首先,可以在命令之间使用分号。例如,以下命令系列使用 ‘=’ 命令和
‘p’ 命令,‘=’ 命令告诉 sed 打印行号,‘p’ 命令明确告诉 sed 打印该行(因为处于 ‘-n’ 模式)。

1
2
3
4
5
6
$ sed -n -e '=;p' myfile.txt  

```无 论什么时候指定了两个或更多命令,都按顺序将每个命令应用到文件的每一行。在上例中,首先将 '=' 命令应用到第 1 行,然后应用 'p'
命令。接着,sed 继续处理第 2 行,并重复该过程。虽然分号很方便,但是在某些场合下,它不能正常工作。另一种替换方法是使用两个 -e
选项来指定两个不同的命令:

$ sed -n -e ‘=’ -e ‘p’ myfile.txt

'-e' 选项也不能帮我们的忙。对于复杂的多行脚本,最好的方法是将命令放入一个单独的文件中。然后,用 -f
1
2
选项引用该脚本文件:  

$ sed -n -f mycommands.sed myfile.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  
一个地址的多个命令
有时,可能要指定应用到一个地址的多个命令。这在执行许多 's///' 以变换源文件中的字和语法时特别方便。要对一个地址执行多个命令,可在文件中输入 sed
命令,然后使用 '{ }' 字符将这些命令分组,如下所示:

1,20{ s/[Ll]inux/GNU//Linux/g s/samba/Samba/g s/posix/POSIX/g }
上例将把三个替换命令应用到第 1 行到第 20 行(包括这两行)。还可以使用规则表达式地址或者二者的组合:

1,/^END/{ s/[Ll]inux/GNU//Linux/g s/samba/Samba/g s/posix/POSIX/g p }
该例将把 '{ }' 之间的所有命令应用到从第 1 行开始,到以字母 "END" 开始的行结束(如果在源文件中没发现 "END",则到文件结束)的所有行。

附加、插入和更改行
既然在单独的文件中编写 sed 脚本,我们可以利用附加、插入和更改行命令。这些命令将在当前行之后插入一行,在当前行之前插入一行,或者替换模式空间中的当前行。
它们也可以用来将多行插入到输出。插入行命令用法如下:

i/ This line will be inserted before each line
如果不为该命令指定地址,那么它将应用到每一行,并产生如下的输出:

This line will be inserted before each line line 1 here
This line will be inserted before each line line 2 here
This line will be inserted before each line line 3 here
This line will be inserted before each line line 4 here
如果要在当前行之前插入多行,可以通过在前一行之后附加一个反斜杠来添加附加行,如下所示:

i/ insert this line/ and this one/ and this one/ and, uh, this one too.
附加命令的用法与之类似,但是它将把一行或多行插入到模式空间中的当前行之后。其用法如下:

a/ insert this line after each line. Thanks!
![smile.gif](http://www.loveunix.net/style_emoticons/default/smile.gif)
另一方面,“更改行”命令将实际替换模式空间中的当前行,其用法如下:

c/ You're history, original line! Muhahaha!
因为附加、插入和更改行命令需要在多行输入,所以将把它们输入到一个文本 sed 脚本中,然后通过使用 '-f' 选项告诉 sed
执行它们。使用其它方法将命令传递给 sed 会出现问题。

文本转换
第一个实际脚本将 UNIX 风格的文本转换成 DOS/Windows 格式。您可能知道,基于 DOS/Windows 的文本文件在每一行末尾有一个
CR(回车)和 LF(换行),而 UNIX 文本只有一个换行。有时可能需要将某些 UNIX 文本移至 Windows 系统,该脚本将为您执行必需的格式转换。

$ sed -e ‘s/$//r/’ myunix.txt > mydos.txt

规则表达式将与行的末尾匹配,而 '/r' 告诉 sed 在其之前插入一个回车。在换行之前插入回车,立即,每一行就以 CR/LF
1
2
3
4
5
6
7
结束。请注意,仅当使用 GNU sed 3.02.80 或以后的版本时,才会用 CR 替换 '/r'。如果还没有安装 GNU sed
3.02.80,请在我的第一篇 sed 文章中查看如何这样做的说明。

我已记不清有多少次在下载一些示例脚本或 C 代码之后,却发现它是 DOS/Windows 格式。虽然很多程序不在乎 DOS/Windows 格式的
CR/LF 文本文件,但是有几个程序却在乎 -- 最著名的是 bash,只要一遇到回车,它就会出问题。以下 sed 调用将把 DOS/Windows
格式的文本转换成可信赖的 UNIX 格式:

$ sed -e ‘s/.$//’ mydos.txt > myunix.txt

1
2
3
4
5
6
7
8
9
10
11
12
除了输出中每行的最末字符,那么,您就指定了已经是 UNIX 格式的文本文件。也就没必要那样做了!  

反转行
下 面是另一个方便的小脚本。与大多数 Linux 发行版中包括的 "tac" 命令一样,该脚本将反转文件中行的次序。"tac"
这个名称可能会给人以误导,因为 "tac" 不反转行中字符的位置(左和右),而是反转文件中行的位置(上和下)。用 "tac" 处理以下文件:

foo bar oni
....将产生以下输出:

oni bar foo
可以用以下 sed 脚本达到相同目的:

$ sed -e ‘1!G;h;$!d’ forward.txt > backward.txt

  
反转解释  
首 先,该脚本包含三个由分号隔开的单独 sed 命令:'1!G'、'h' 和 '\$!d'。现在,需要好好理解用于第一个和第三个命令的地址。如果第一个命令是
'1G',则 'G' 命令将只应用第一行。然而,还有一个 '!' 字符 -- 该 '!' 字符忽略该地址,即,'G'
命令将应用到除第一行之外的所有行。'\$!d' 命令与之类似。如果命令是 '\$d',则将只把 'd' 命令应用到文件中的最后一行('\$'
地址是指定最后一行的简单方式)。然而,有了 '!' 之后,'\$!d' 将把 'd'
命令应用到除最后一行之外的所有行。现在,我们所要理解的是这些命令本身做什么。  
  
当对上面的文本文件执行反转脚本时,首先执行的命令是 'h'。该命令告诉 sed
将模式空间(保存正在处理的当前行的缓冲区)的内容复制到保留空间(临时缓冲区)。然后,执行 'd' 命令,该命令从模式空间中删除
"foo",以便在对这一行执行完所有命令之后不打印它。  
  
现在,第二行。在将 "bar" 读入模式空间之后,执行 'G' 命令,该命令将保留空间的内容 ("foo/n") 附加到模式空间
("bar/n"),使模式空间的内容为 "bar/n/foo/n"。'h' 命令将该内容放回保留空间保护起来,然后,'d'
从模式空间删除该行,以便不打印它。  
  
对于最后的 "oni" 行,除了不删除模式空间的内容(由于 'd' 之前的 '\$!')以及将模式空间的内容(三行)打印到标准输出之外,重复同样的步骤。  
  
现在,要用 sed 执行一些强大的数据转换。  
  
sed QIF 魔法  
过去几个星期,我一直想买一份 Quicken 来结算我的银行帐户。Quicken
是一个非常好的金融程序,当然会成功地完成这项工作。但是,经过考虑之后,我觉得自己可以轻易编写某个软件来结算我的支票簿。我想,毕竟,我是个软件开发人员!  
  
我 开发了一个很好的小型支票簿结算程序(使用 awk),它通过分析包含我的所有交易的文本文件的语法来计算余额。略微调整之后,我将其改进,以便可以象
Quicken 那样跟踪不同的贷款和借款类别。但是,我还要添加一个特性。最近,我将帐户转移到一家有联机 Web 帐户界面的银行。有一天,我注意到,这家银行的
Web 站点允许以 Quicken 的 .QIF 格式下载我的帐户信息。我马上觉得,如果可以将该信息转换成文本格式,那就太棒了。  
  
两种格式的故事  
在查看 QIF 格式之前,先看一下我的 checkbook.txt 格式:  
  
28 Aug 2000 food - - Y Supermarket 30.94 25 Aug 2000 watr - 103 Y Check 103
52.86  
在我的文件中,所有字段都由一个或多个制表符分开,每个交易占据一行。日期之后的下一个字段列 出支出类型(如果是收入项,则为
"-")。第三个字段列出收入类型(如果是支出项,则为 "-")。然后,是一个支票号字段(如果为空,则还是 "-"),一个交易完成字段("Y" 或
"N"),一个注释和一个美元金额字段。现在,让我们看一下 QIF 格式。当用文本查看器查看下载的 QIF 文件时,它看起来如下:  
  
!Type:Bank D08/28/2000 T-8.15 N PCHECKCARD SUPERMARKET ^ D08/28/2000 T-8.25 N
PCHECKCARD PUNJAB RESTAURANT ^ D08/28/2000 T-17.17 N PCHECKCARD SUPERMARKET  
浏览过文件之后,不难猜出其格式 -- 忽略第一行,其余的格式如下:  
  
D<数据>  
T<交易量>  
N<支票号>  
P<描述>  
^ (这是字段分隔符)  
开始处理  
在处理象这样重要的 sed 项目时,不要气馁 -- sed 允许您将数据逐渐修改成最终形式。在进行当中,可以继续细化 sed
脚本,直到输出与预期的完全一样为止。无需在试第一次时就保证其完全正确。  
  
要开始,首先创建一个名为 "qiftrans.sed" 的文件,然后开始修改数据:  
  
1d /^^/d s/[[:cntrl:]]//g  
第一个 '1d' 命令删除第一行,第二个命令从输出除去那些讨厌的 '^' 字符。最后一行除去文件中可能存在的任何控制字符。既然在处理外来文件格式,我想消除在
中途遇到任何控制字符的风险。到目前为止,一切顺利。现在,要向该基本脚本中添加一些处理功能:  
  
1d /^^/d s/[[:cntrl:]]//g /^D/ {  
s/^D/(.*/)//1/tOUTY/tINNY/t/  
s/^01/Jan/ s/^02/Feb/  
s/^03/Mar/ s/^04/Apr/  
s/^05/May/ s/^06/Jun/  
s/^07/Jul/ s/^08/Aug/  
s/^09/Sep/ s/^10/Oct/  
s/^11/Nov/ s/^12/Dec/  
s:^/(.*/)//(.*/)//(.*/):/2 /1 /3: }  
首先,添加一个 '/^D/' 地址,以便 sed 只在遇到 QIF 数据字段的第一个字符 'D' 时才开始处理。当 sed
将这样一行读入其模式空间时,将按顺序执行花括号中的所有命令。  
  
花括号中的第一个命令将把如下行:  
  
D08/28/2000  
变换成:  
  
08/28/2000 OUTY INNY  
当然,现在的格式还不完美,但没关系。我们将在进行过程中逐渐细化模式空间的内容。后面 12
行的最后效果是将数据变换成三个字母的格式,最后一行从数据中除去三个斜杠。最后得到这一行:  
  
Aug 28 2000 OUTY INNY  
OUTY 和 INNY 字段是占位符,以后将被替换。现在还不能确定它们,因为如果美元金额为负,将把 OUTY 和 INNY 设置成 "misc" 和
"-",但是,如果美元金额为正,将分别把它们更改成 "-" 和 "inco"。既然还没有读入美元金额,所以,需要暂时使用占位符。  
  
细化  
现在进一步细化:  
  
1d /^^/d s/[[:cntrl:]]//g /^D/ {  
s/^D/(.*/)//1/tOUTY/tINNY/t/  
s/^01/Jan/ s/^02/Feb/  
s/^03/Mar/ s/^04/Apr/  
s/^05/May/ s/^06/Jun/  
s/^07/Jul/ s/^08/Aug/  
s/^09/Sep/ s/^10/Oct/  
s/^11/Nov/ s/^12/Dec/  
s:^/(.*/)//(.*/)//(.*/):/2 /1 /3:  
N N N  
s//nT/(.*/)/nN/(.*/)/nP/(.*/)/NUM/2NUM/t/tY/t/t/3/tAMT/1AMT/  
s/NUMNUM/-/ s/NUM/([0-9]*/)NUM//1/  
s//([0-9]/),//1/ }  
后七行有些复杂,所以将详细讨论它们。首先,连续使用三个 'N' 命令。'N' 命令告诉 sed 将下一行读入输入中,然后将其附加到当前模式空间。这三个
'N' 命令导致将下三行附加到当前模式空间缓冲区,现在这一行看起来如下:  
  
28 Aug 2000 OUTY INNY /nT-8.15/nN/nPCHECKCARD SUPERMARKET  
sed 的模式空间变得很难看 -- 需要除去额外的新行,并执行某些附加的格式化。要这样做,将使用替代命令。要匹配的模式为:  
  
'/nT.*/nN.*/nP.*'  
这 将与后面依次跟有
'T'、零或多个字符、新行、'N'、任何数量的字符、新行、'P'、以及任何数量字符的新行匹配。呀!这个规则表达式将与刚刚附加到模式空间的三行的全
部内容匹配。但我们要重新格式化该区域,而不是整个替换它。美元金额、支票号(如果有的话)和描述需要出现在替换字符串中。要这样做,我们用带有反斜杠的
圆括号括起那些“感兴趣部分”,以便可以在替换字符串中引用它们(使用 '/1'、'/2/ 和 '/3' 来告诉 sed 将它们插入到何处)。以下是最后的命令:  
  
s//nT/(.*/)/nN/(.*/)/nP/(.*/)/NUM/2NUM/t/tY/t/t/3/tAMT/1AMT/  
该命令将我们的行变换成:  
  
28 Aug 2000 OUTY INNY NUMNUM Y CHECKCARD SUPERMARKET AMT-8.15AMT  
虽 然该行正变得好一些,但是,有几件事一看就有点...啊...有趣。首先是那个愚蠢的 "NUMNUM" 字符串 -- 其目的何在?如果查看 sed
脚本的后两行,就会发现其目的,后两行将把 "NUMNUM" 替换成 "-",而把 "NUM"<number>"NUM" 替换成
<number>。如您所见,用愚蠢的标记括起支票号允许我们在该字段为空时方便地插入一个 "-"。  
  
结束尝试  
最后一行除去数字后的逗号。它把如 "3,231.00" 这样的美元金额转换成我使用的格式 "3231.00"。现在,让我们看一下最终脚本:  
  
最终的“QIF 到文本”脚本 1d /^^/d s/[[:cntrl:]]//g /^D/ { s/^D/(.*/)//1/tOUTY/tINNY/t/  
s/^01/Jan/ s/^02/Feb/ s/^03/Mar/ s/^04/Apr/ s/^05/May/  
s/^06/Jun/ s/^07/Jul/ s/^08/Aug/ s/^09/Sep/ s/^10/Oct/  
s/^11/Nov/ s/^12/Dec/ s:^/(.*/)//(.*/)//(.*/):/2 /1 /3:  
N N N s//nT/(.*/)/nN/(.*/)/nP/(.*/)/NUM/2NUM/t/tY/t/t/3/tAMT/1AMT/  
s/NUMNUM/-/ s/NUM/([0-9]*/)NUM//1/ s//([0-9]/),//1/  
/AMT-[0-9]*.[0-9]*AMT/b fixnegs  
s/AMT/(.*/)AMT//1/ s/OUTY/-/ s/INNY/inco/  
b done :fixnegs s/AMT-/(.*/)AMT//1/ s/OUTY/misc/  
s/INNY/-/ :done }  
附加的十一行使用替代和一些分支功能来美化输出。首先看一下这行:  
  
/AMT-[0-9]*.[0-9]*AMT/b fixnegs  
该行包含一个格式为 "/regexp/b label" 的分支命令。如果模式空间与规则表达式匹配,sed 将分支到 fixnegs
标号。您应该可以轻易找到该标号,它在代码中为 ":fixnegs"。如果规则表达式不匹配,则以常规方式继续处理下一个命令。  
  
既 然您理解该命令本身的工作原理,让我们看一下分支。如果看一下分支规则表达式,将看到它与后面依次跟有 '-'、任意数量的数字、一个 '.'、任意数量的数字和
'AMT' 的字符串 'AMT' 匹配。就象我确信您已猜到一样,该规则表达式专门处理负的美元金额。在这之前,用 'ATM'
括起美元金额,以便以后可以轻易找到它。因为规则表达式只与以 '-' 开始的美元金额匹配,所以,该分支只在恰巧处理借款时才发生。如果正处理贷款,应该将
OUTY 设置成 'misc',将 INNY 设置成
'-',并且应该除去贷款数量前面的负号。如果跟踪代码的流程,将看到实际情况正是这样。如果不执行分支,则用 '-' 替换 OUTY,用 'inco' 替换
INNY。完成了!现在输出行是完美的:  
  
28 Aug 2000 misc - - Y CHECKCARD SUPERMARKET -8.15  
别犯糊涂  
如 您所见,只要循序渐进地解决问题,使用 sed 转换数据就没有那么难。不要试图使用一个 sed
命令或一下子解决所有问题。相反,要朝着目标逐步进行,并不断改进 sed 脚本,直到其输出正如您希望那样为止。sed
有许多功能,希望您已非常熟悉其内部工作原理并继续努力以进一步掌握它!

这篇文章还是有些错误的,再脚本中如果写大段的echo文字时,需要使用如下的方式:

cat <<-HELP

这里是你的内容,HELP只不过是标识。但是一定要注意cat命令后两个小于后“-”,如果不加,语法会有错误。我辛苦的找了很久…

HELP

另外清晰的地址在

http://wiki.ubuntu.org.cn/Shell编程基础

不过它那里的文章中的cat也写错了。或者是bash版本不同的关系吧。

此文章参照:

http://www.linuxdiyf.com/viewarticle.php?id=20338 有严寒发表于 2006-9-29

http://www.openchess.org/noitatsko/programming/ 2001-05-25

1. Linux 脚本编写基础
1.1 语法基本介绍
1.1.1 开头
程序必须以下面的行开始(必须方在文件的第一行):
#!/bin/sh
符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。
当编辑好脚本时,如果要执行该脚本,还必须使其可执行。
要使脚本可执行:
编译 chmod +x filename 这样才能用./filename 来运行
1.1.2 注释
在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。
如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用
及工作原理。
1.1.3 变量
在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量
进行声明。要赋值给一个变量,您可以这样写:
#!/bin/sh
#对变量赋值:
a=“hello world”

现在打印变量a的内容:

1
2
echo "A is:"  
echo $a

有时候变量名很容易与其他文字混淆,比如:

1
2
num=2  
echo "this is the $numnd"

这并不会打印出"this is the 2nd",而仅仅打印"this is the ",因为shell会去搜索变量numnd的值,
但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:

1
2
num=2  
echo "this is the ${num}nd"

这将打印: this is the 2nd
1.1.4 环境变量
由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录
脚本中使用环境变量。
1.1.5 Shell命令和流程控制
在shell脚本中可以使用三类命令:
1)Unix 命令:
虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来
进行文件和文字操作的。
常用命令语法及功能
echo “some text”: 将文字内容打印在屏幕上
ls: 文件列表
wc –l filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数
cp sourcefile destfile: 文件拷贝
mv oldname newname : 重命名文件或移动文件
rm file: 删除文件
grep ‘pattern’ file: 在文件内搜索字符串比如:grep ‘searchstring’ file.txt
cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出
每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆,
这是两个完全不同的命令
cat file.txt: 输出文件内容到标准输出设备(屏幕)上
file somefile: 得到文件类型
read var: 提示用户输入,并将输入赋值给变量
sort file.txt: 对file.txt文件中的行进行排序
uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq
expr: 进行数学运算Example: add 2 and 3expr 2 “+” 3
find: 搜索文件比如:根据文件名搜索find . -name filename -print
tee: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile
basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux
dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin
head file: 打印文本文件开头几行
tail file : 打印文本文件末尾几行
sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将
结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。
不要和shell中的通配符相混淆。比如:将linuxfocus 替换为
LinuxFocus :cat text.file | sed ‘s/linuxfocus/LinuxFocus/’ > newtext.file
awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。

1
cat file.txt | awk -F, '{print $1 "," $3 }'

这里我们使用,作为字段分割符,同时打印
第一个和第三个字段。如果该文件内容如下: Adam Bor, 34, IndiaKerry Miller, 22, USA
命令输出结果为:Adam Bor, IndiaKerry Miller, USA
2) 概念: 管道, 重定向和 backtick
这些不是系统命令,但是他们真的很重要。
管道 (|) 将一个命令的输出作为另外一个命令的输入。
grep “hello” file.txt | wc -l
在file.txt中搜索包含有”hello”的行并计算其行数。
在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。
重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。

写入文件并覆盖旧文件

加到文件的尾部,保留旧文件内容。
反短斜线
使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。
命令:
find . -mtime -1 -type f -print
用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您
想将所有查找到的文件打一个包,则可以使用以下脚本:
#!/bin/sh

The ticks are backticks (`) not normal quotes ('):

tar -zcvf lastmod.tar.gz find . -mtime -1 -type f -print
3) 流程控制
1.if
“if” 表达式 如果条件为真则执行then后面的部分:
if …; then

elif …; then

else

fi
大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件
是否存在及是否可读等等…
通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。

1
2
3
4
[ -f "somefile" ] :判断是否是一个文件  
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限
[ -n "$var" ] :判断$var变量是否有值
[ "$a" = "$b" ] :判断$a$b是否相等

执行man test可以查看所有测试表达式可以比较和判断的类型。
直接执行以下脚本:

1
2
3
4
5
6
#!/bin/sh  
if [ "$SHELL" = "/bin/bash" ]; then
echo "your login shell is the bash (bourne again shell)"
else
echo "your login shell is not bash but $SHELL"
fi

变量$SHELL包含了登录shell的名称,我们和/bin/bash进行了比较。
快捷操作符
熟悉C语言的朋友可能会很喜欢下面的表达式:
[ -f “/etc/shadow” ] && echo “This computer uses shadow passwors”
这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。
您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在
则打印” This computer uses shadow passwors”。同样或操作(||)在shell编程中也是
可用的。这里有个例子:

1
2
3
4
5
#!/bin/sh  
mailfolder=/var/spool/mail/james
[ -r "$mailfolder" ]' '{ echo "Can not read $mailfolder" ; exit 1; }
echo "$mailfolder has mail from:"
grep "^From " $mailfolder

该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的"From" 一行。如果不可读
则或操作生效,打印错误信息后脚本退出。这里有个问题,那就是我们必须有两个命令:
-打印错误信息
-退出程序
我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。
不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。
2.case
case :表达式可以用来匹配一个给定的字符串,而不是数字。
case … in
…) do something here ;;
esac
让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型,比如:
file lf.gz
这将返回:
lf.gz: gzip compressed data, deflated, original filename,
last modified: Mon Aug 27 23:09:18 2001, os: Unix
我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh  
ftype=`file "$1"`
case "$ftype" in
"$1: Zip archive"*)
unzip "$1" ;;
"$1: gzip compressed"*)
gunzip "$1" ;;
"$1: bzip2 compressed"*)
bunzip2 "$1" ;;
*) echo "File $1 can not be uncompressed with smartzip";;
esac

您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。
也就是说,当我们运行:
smartzip articles.zip
$1 就是字符串 articles.zip

这里要注意的是最后一个*)是必须要写的,意思和default一样。而前面的*)中的*是正则式的一部分。另外"$ftype"没有必要写"".

case的具体语法如下:

case word in [ pattern [ | pattern ] … ) list ;; ] … esac
case/esac的标准用法大致如下:

1
2
3
4
5
6
7
8
case $arg in  
pattern | sample) # arg in pattern or sample
;;
pattern1) # arg in pattern1
;;
*) #default
;;
esac

arg是您所引入的参数,如果arg内容符合pattern项目的话,那麽便会执行pattern以下的程式码,而该段程式码则以两个分号";;"做结尾。

可以注意到"case"及"esac"是对称的,如果记不起来的话,把"case"颠倒过来即可。
-----------------------------------------------------------------------------

例一 : paranoia

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh  
case $1 in
start | begin)
echo "start something"
;;
stop | end)
echo "stop something"
;;
*)
echo "Ignorant"
;;
esac

执行
[foxman@foxman bash]# chmod 755 paranoia
[foxman@foxman bash]# ./paranoia
Ignorant
[foxman@foxman bash]# ./paranoia start
start something
[foxman@foxman bash]# ./paranoia begin
start something
[foxman@foxman bash]# ./paranoia stop
stop something
[foxman@foxman bash]# ./paranoia end
stop something

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

例二 : inetpanel
许多的daemon都会附上一个管理用的Shell Script,像BIND就附上ndc,Apache就附上apachectl。这些管理程式都是用shell
script来写的,以下示一个管理inetd的shell script。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/sh  

case $1 in
start | begin | commence)
/usr/sbin/inetd
;;
stop | end | destroy)
killall inetd
;;
restart | again)
killall -HUP inetd
;;
*)
echo "usage: inetpanel [start | begin | commence | stop | end | destory |
restart | again]"
;;
esac

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

例三 : 判断系统
有时候,您所写的Script可能会跨越好几种平台,如Linux、FreeBSD、Solaris等等,而各平台之间,多多少少都有不同之处,有时候需要判断目前正
在那一种平台上执行。此时,我们可以利用uname来找出系统资讯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh  

SYSTEM=`uname -s`

case $SYSTEM in
Linux)
echo "My system is Linux"
echo "Do Linux stuff here..."
;;
FreeBSD)
echo "My system is FreeBSD" case word in [ pattern [ | pattern ] ... ) list
;; ] ... esac
case/esac的标准用法大致如下:
case $arg in
pattern | sample) # arg in pattern or sample
;;
pattern1) # arg in pattern1
;;
*) #default
;;
esac

arg是您所引入的参数,如果arg内容符合pattern项目的话,那麽便会执行pattern以下的程式码,而该段程式码则以两个分号";;"做结尾。

可以注意到"case"及"esac"是对称的,如果记不起来的话,把"case"颠倒过来即可。
-----------------------------------------------------------------------------

例一 : paranoia

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh  
case $1 in
start | begin)
echo "start something"
;;
stop | end)
echo "stop something"
;;
*)
echo "Ignorant"
;;
esac

执行
[foxman@foxman bash]# chmod 755 paranoia
[foxman@foxman bash]# ./paranoia
Ignorant
[foxman@foxman bash]# ./paranoia start
start something
[foxman@foxman bash]# ./paranoia begin
start something
[foxman@foxman bash]# ./paranoia stop
stop something
[foxman@foxman bash]# ./paranoia end
stop something

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

例二 : inetpanel
许多的daemon都会附上一个管理用的Shell Script,像BIND就附上ndc,Apache就附上apachectl。这些管理程式都是用shell
script来写的,以下示一个管理inetd的shell script。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

#!/bin/sh

case $1 in
start | begin | commence)
/usr/sbin/inetd
;;
stop | end | destroy)
killall inetd
;;
restart | again)
killall -HUP inetd
;;
*)
echo "usage: inetpanel [start | begin | commence | stop | end | destory |
restart | again]"
;;
esac

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

例三 : 判断系统
有时候,您所写的Script可能会跨越好几种平台,如Linux、FreeBSD、Solaris等等,而各平台之间,多多少少都有不同之处,有时候需要判断目前正
在那一种平台上执行。此时,我们可以利用uname来找出系统资讯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

#!/bin/sh

SYSTEM=`uname -s`

case $SYSTEM in
Linux)
echo "My system is Linux"
echo "Do Linux stuff here..."
;;
FreeBSD)
echo "My system is FreeBSD"
echo "Do FreeBSD stuff here..."
;;
*)
echo "Unknown system : $SYSTEM"
echo "I don't what to do..."
;;
esac
echo "Do FreeBSD stuff here..."
;;
*)
echo "Unknown system : $SYSTEM"
echo "I don't what to do..."
;;
esac

3. selsect
select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。

1
2
3
4
select var in ... ; do  
break
done
.... now $var can be used ....

下面是一个例子:

1
2
3
4
5
6
7
#!/bin/sh  
echo "What is your favourite OS?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
break
done
echo "You have selected $var"

下面是该脚本运行的结果:
What is your favourite OS?

  1. Linux
  2. Gnu Hurd
  3. Free BSD
  4. Other
    #? 1
    You have selected Linux
    4.loop
    loop表达式:
    while …; do

    done
    while-loop 将运行直到表达式测试为真。will run while the expression that we test for is
    true.
    关键字"break" 用来跳出循环。而关键字”continue”用来不执行余下的部分而直接跳到下一个循环。

for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量:
for var in …; do

done
在下面的例子中,将分别打印ABC到屏幕上:

1
2
3
4
#!/bin/sh  
for var in A B C ; do
echo "var is $var"
done

下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息:

1
2
3
4
5
6
7
8
9
10
11
12
13

#!/bin/sh
# list a content summary of a number of RPM packages
# USAGE: showrpm rpmfile1 rpmfile2 ...
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm
for rpmpackage in $*; do
if [ -r "$rpmpackage" ];then
echo "=============== $rpmpackage =============="
rpm -qi -p $rpmpackage
else
echo "ERROR: cannot read file $rpmpackage"
fi
done

这里出现了第二个特殊的变量$,该变量包含了所有输入的命令行参数值。
如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm
此时 $
包含了 3 个字符串,即openssh.rpm, w3m.rpm and webgrep.rpm.

5. 引号
在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符
(比如*)替换成合适的文件名,它变量替换成变量值。为了防 止程序作这种替换,您可以使用
引号:让我们来看一个例子,假设在当前目录下有一些文件,两个jpg文件, mail.jpg 和tux.jpg。
1.2 编译SHELL脚本
#ch#!/bin/sh mod +x filename
cho *.jpg ∪缓螅梢酝ü淙耄?./filename 来执行您的脚本。
这将打印出"mail.jpg tux.jpg"的结果。
引号 (单引号和双引号) 将防止这种通配符扩展:

1
2
3
#!/bin/sh  
echo "*.jpg"
echo '*.jpg'

这将打印"*.jpg" 两次。
单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。

1
2
3
4
#!/bin/sh  
echo $SHELL
echo "$SHELL"
echo '$SHELL'

运行结果为:
/bin/bash
/bin/bash
$SHELL
最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆:

1
2
echo *.jpg  
echo $SHELL

这将输出:
*.jpg
$SHELL
6. Here documents
当要将几行文字传递给一个命令时,here documents(译者注:目前还没有见到过对该词适合的翻译)
一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个 here documents
就不必用echo函数一行行输出。 一个 “Here document” 以 << 开头,后面接上一个字符串,这个字符串
还必须出现在here document的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且
使用here documents打印帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/sh  
# we have less than 3 arguments. Print the help text:
if [ $# -lt 3 ] ; then
cat <
ren -- renames a number of files using sed regular expressions
USAGE: ren 'regexp' 'replacement' files...
EXAMPLE: rename all *.HTM files in *.html:
ren 'HTM$' 'html' *.HTM
HELP
exit 0
fi
OLD="$1"
NEW="$2"
# The shift command removes one argument from the list of
# command line arguments.
shift
shift
# $* contains now all the files:
for file in $*; do
if [ -f "$file" ] ; then
newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`
if [ -f "$newfile" ]; then
echo "ERROR: $newfile exists already"
else
echo "renaming $file to $newfile ..."
mv "$file" "$newfile"
fi
fi
done

这是一个复杂一些的例子。让我们详细讨论一下。第一个if表达式判断输入命令行参数是
否小于3个 (特殊变量$# 表示包含参数的个数) 。如果输入参数小于3个,则将帮助文字传递
给cat命令,然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。 如果输入参数等
于或大于3个,我们就将第一个参数赋值给变量OLD,第二个参数赋值给变量NEW。下一步,我
们使用shift命令将第一个和第二个参数从 参数列表中删除,这样原来的第三个参数就成为参
数列表$*的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量$file。
接着我 们判断该文件是否存在,如果存在则通过sed命令搜索和替换来产生新的文件名。然后
将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的目 的:得到了旧文件名和新
文件名。然后使用mv命令进行重命名。

4)函数
如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,
并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的:

1
2
3
4
5
6
7
functionname()  
{
# inside the body $1 is the first argument given to the function
# $2 the second ...
body
}

您需要在每个程序的开始对函数进行声明。
下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。
这里使用了一个叫做help的函数。正如您可以看到的那样,这个定义的函数被使用了两次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
cat <
xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole
USAGE: xtitlebar [-h] "string_for_titelbar"
OPTIONS: -h help text
EXAMPLE: xtitlebar "cvs"
HELP
exit 0
}
# in case of error or if -h is given we call the function help:
[ -z "$1" ] && help
[ "$1" = "-h" ] && help
# send the escape sequence to change the xterm titelbar:
echo -e "33]0;$107"
#

在脚本中提供帮助是一种很好的编程习惯,这样方便其他用户(和您)使用和理解脚本。
命令行参数
我们已经见过$* 和 $1, $2 … $9 等特殊变量,这些特殊变量包含了用户从命令
行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的
参数和查看帮助的-h选项)。 但是在编写更复杂的程序时,您可能会发现您需要更多的
自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值 (
比如文件名)。
有好多方法可以实现对输入参数的分析,但是下面的使用case表达式的例子无遗是一个不错的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh  
help()
{
cat <
This is a generic command line parser demo.
USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2
HELP
exit 0
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help is called
-f) opt_f=1;shift 1;; # variable opt_f is set
-l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2
\--) shift;break;; # end of options
-*) echo "error: no such option $1. -h for help";exit 1;;
*) break;;
esac
done
echo "opt_f is $opt_f"
echo "opt_l is $opt_l"
echo "first arg is $1"
echo "2nd arg is $2"

您可以这样运行该脚本:
cmdparser -l hello -f – -somefile1 somefile2
返回的结果是:
opt_f is 1
opt_l is hello
first arg is -somefile1
2nd arg is somefile2
这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数
与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例,
首先输入的应该是包含减号的参数.
第2部分 实例
现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本(framework.sh),该脚本包含了大多数脚本都需要的框
架结构,是一个非常不错的主意。这时候,在写一个新的脚本时我们只需要执行一下copy命令:
cp framework.sh myscript
然后再插入自己的函数。
让我们再看两个例子:
二进制到十进制的转换
脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/bin/sh  
# vim: set sw=4 ts=4 et:
help()
{
cat <
b2h -- convert binary to decimal
USAGE: b2h [-h] binarynum
OPTIONS: -h help text
EXAMPLE: b2h 111010
will return 58
HELP
exit 0
}
error()
{
# print an error and exit
echo "$1"
exit 1
}
lastchar()
{
# return the last character of a string in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
# now cut out the last char
rval=`echo -n "$1" | cut -b $numofchar`
}
chop()
{
# remove the last character in string and return it in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
if [ "$numofchar" = "1" ]; then
# only one char in string
rval=""
return
fi
numofcharminus1=`expr $numofchar "-" 1`
# now cut all but the last char:
rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help is called
\--) shift;break;; # end of options
-*) error "error: no such option $1. -h for help";;
*) break;;
esac
done
# The main program
sum=0
weight=1
# one arg must be given:
[ -z "$1" ] && help
binnum="$1"
binnumorig="$1"
while [ -n "$binnum" ]; do
lastchar "$binnum"
if [ "$rval" = "1" ]; then
sum=`expr "$weight" "+" "$sum"`
fi
# remove the last position in $binnum
chop "$binnum"
binnum="$rval"
weight=`expr "$weight" "*" 2`
done
echo "binary $binnumorig is decimal $sum"

该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,…),比如二进制"10"可
以这样转换成十进制:
0 * 1 + 1 * 2 = 2
为了得到单个的二进制数我们是用了lastchar 函数。该函数使用wc –c计算字符个数,
然后使用cut命令取出末尾一个字符。Chop函数的功能则是移除最后一个字符。
文件循环程序
或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月
以后,这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的 脚本rotatefile
可以解决这个问题。这个脚本可以重命名邮件保存文件(假设为outmail)为outmail.1,
而对于outmail.1就变成了outmail.2 等等等等…

#!/bin/sh  
# vim: set sw=4 ts=4 et:  
ver="0.1"  
help()  
{  
cat <  
rotatefile -- rotate the file name  
USAGE: rotatefile [-h] filename  
OPTIONS: -h help text  
EXAMPLE: rotatefile out  
This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1  
and create an empty out-file  
The max number is 10  
version $ver  
HELP  
exit 0  
}  
error()  
{  
echo "$1"  
exit 1  
}  
while [ -n "$1" ]; do  
case $1 in  
-h) help;shift 1;;   
\--) break;;  
-*) echo "error: no such option $1. -h for help";exit 1;;   
*) break;;   
esac  
done  
# input check:  
if [ -z "$1" ] ; then  
error "ERROR: you must specify a file, use -h for help"  
fi  
filen="$1"  
# rename any .1 , .2 etc file:  
for n in 9 8 7 6 5 4 3 2 1; do  
if [ -f "$filen.$n" ]; then  
p=`expr $n + 1`  
echo "mv $filen.$n $filen.$p"  
mv $filen.$n $filen.$p  
fi  
done  
# rename the original file:  
if [ -f "$filen" ]; then  
echo "mv $filen $filen.1"  
mv $filen $filen.1  
fi  
echo touch $filen  
touch $filen  

这个脚本是如何工作的呢?在检测用户提供了一个文件名以后,我们进行一个9到1的循环。文件9被命名为10,文件8重命名为9等等。循环完成之后,我们将原始文件命名
为文件1同时建立一个与原始文件同名的空文件。
调试
最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印任何变量值。这也是绝大多数的shell程序员要花费80%的时间来调试程序的
原因。Shell程序的好处在于不需要重新编译,插入一个echo命令也不需要多少时间。
shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误,您可以这样来进行调试:
sh -x strangescript
这将执行该脚本并显示所有变量的值。
shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用:
sh -n your_script
这将返回所有语法错误

0%