2007年3月22日星期四

CPIO 文件格式

http://www.mkssoftware.com/docs/man4/cpio.4.asp



DESCRIPTION

This document describes the format of archives read and written by the cpio utility. A cpio archive consists of the concatenation of one or more member files. Each member file contains a header (as described later in this reference page) optionally followed by file contents as indicated in the header. The end of the archive is indicated by another header describing an (empty) file named TRAILER!!.

There are two types of cpio archives, differing only in the style of the header. ASCII archives have totally printable header information; thus, if the files being archived are also ASCII files, the whole archive is ASCII. By default, cpio writes archives with binary headers.

The information in ASCII archive headers is stored in fixed-width, octal (base 8) numbers zero-padded on the left. Table 1 gives the order and field width for the information in the ASCII header.

Field Width Field Name Meaning

6 magic magic number "070707"
6 dev device where file resides
6 ino I-number of file
6 mode file mode
6 uid owner user ID
6 gid owner group ID
6 nlink number of links to file
6 rdev device major/minor for special file
11 mtime modify time of file
6 namesize length of file name
11 filesize length of file to follow

Table 1: ASCII Header Format for cpio File

Most of this information is compatible with that returned by the UNIX stat() function; see also stat. After this information, namesize bytes of path name is stored. namesize includes the null byte of the end of the path name. After this, filesize bytes of the file contents are recorded.

Binary headers contain the same information in 2 byte (short) and 4 byte (long) integers as shown in Table 2.

Bytes Field Name

2 magic
2 dev
2 ino
2 mode
2 uid
2 gid
2 nlink
2 rdev
4 mtime
2 namesize
4 filesize

Table 2: Binary Header Format for cpio File

After this information comes the file name (with namesize rounded up to the nearest 2 byte boundary). Then the file contents appear as in the ASCII archive. The byte ordering of the 2 and 4 byte integers in the binary format is machine dependent and thus portability of this format is not easily guaranteed.


PORTABILITY

cpio archives are fully compatible between UNIX and Windows systems. For maximum portability among different system architectures, only the ASCII archive format should be used.

Because the Windows file systems lack many features of the UNIX file system, much of the header information is meaningless.


AVAILABILITY

MKS Toolkit for System Administrators
MKS Toolkit for Developers
MKS Toolkit for Interoperability
MKS Toolkit for Professional Developers
MKS Toolkit for Enterprise Developers
MKS Toolkit for Enterprise Developers 64-Bit Edition


SEE ALSO

Commands:
cpio, pax, tar

File Formats:
tar

Miscellaneous:
stat

2007年3月17日星期六

redhat 安装程序 anaconda 分析

http://www.ibm.com/developerworks/cn/linux/l-anaconda/index.html


引言

Linux 的安装过程可以分为两个阶段,第一个阶段就是加载内核,创建供后续安装过程使用的系统环境,第二阶段就是加载系统安装程序,执行具体的安装过程。对于第一 阶段,有不少资料作了比较详细的介绍,而对于第二阶段,也就是具体的安装过程,却鲜有资料介绍,本文作者结合自己在实践中的心得体会,针对 redhat9.0的安装程序anaconda作了粗略的分析。

为了抓住主线,文中所列代码 片断基本都经过格式调整,并裁减掉了不影响理解的细节,读者可参照源代码。另外,anaconda是用python语言写的,所以读者最好具备一些 python语言的基础和面向对象的基本知识,如果有过图形界面程序的设计的经验,则无论是对图形安装模式还是字符安装模式安装过程(本文只讨论图形安装 模式)的理解都会有很大帮助。

1 概述
系统启动,加载启动映像,在内存中建立了linux系统环境后,解析安装程序映像文件,将安装程序载入内存,执行主执行程序anaconda,该程序是具体安装程序的一个口,负责启动具体的安转过程,完成linux系统的安装。

我 们首先看一下安装映像文件。进入redhat9.0第一张系统盘的redhat/base目录,其中stage2.img就是当安装介质为CD-ROM时 的安装映像文件。我们采用如下命令解析该映像文件(注意:命令中的挂载点/mnt/stage2要根据读者的实际情况修改):

mount -o loop /mnt/cdrom/Redhat/base/stage2.img /mnt/stage2

解 析后,我们可以看到,安装程序的主执行体anaconda在/mnt/stag2/usr/bin目录下,其它安装脚本模块均在 /mnt/stage2/usr/lib/anaconda目录下,今后的行文中,除非是anaconda主执行体,其余均指 /mnt/stage2/usr/lib/anaconda目录下的模块,并将该目录简称为anaconda主目录。

我们先看一下anaconda主目录的结构:

  • installclasses 子目录中的各个模块定义了在安装过程中用户可选择的安装类型。redhat9.0下包含了四个文件workstation.py,server.py, custom.py和personal_desktop.py。其中,workstation.py描述了工作站安装类型,server.py描述了服务 器安装类型,custom.py描述了用户自定义安装类型,personal_desktop.py描述了个人桌面安装类型。每个安装类型描述文件根据相 应安装类型的特点,分别对安装步骤、分区策略以及安装包的取舍给出了不同的方案。
  • Iw子目录下包含所有安装图形界面类所在的模块,每个图形界面对应一个类,负责相应安装步骤图形界面的具体外观显示及与用户的交互,(可能)调用anaconda主目录下的相关安装行为模块完成具体的安装操作。
  • textw子目录和iw子目录含义是一致的,只是包含的是字符安装模式的前端字符用户界面类所在的模块,每个字符用户界面对应一个类,负责与用户的交互,字符界面的采用了python的snack库。
  • 如果说用户界面类是处理安装程序外观的话,则anaconda主目录下的各python模块则执行每个安装界面背后具体的安装行为,包括那些无用户界面安装步骤的安装操作。

Python 的许多内置模块在目录/mnt/stage2/usr/lib/pythonXX下,其中XX指版本号。另外可参考redhat的anaconda源码 包,所有的相关工具都在源码包里,读者在理解了安装过程的原理后,可修改源码,重新编译,然后搭建测试环境进行测试。

2 总体分析
图形安装界面采用python + Gtk开发,各个模块(类)之间的逻辑关系可用下图表示:

图1 图形安装模式下模块(类)逻辑关系图
图1 图形安装模式下模块(类)逻辑关系图

安装程序逻辑上可划分为三个层次,我们先看最主要的一层:调度中心。可以说,Dispatcher类和InstallControlWindow类控制了整个安装程序的运转,理解这两个核心类的功能及其与用户界面类和安装行为类的逻辑关系,就基本上理解了整个安装程序。

Dispatcher 类在anaconda主目录下的dispatch.py模块中,负责整个安装流程的控制,在安装过程中,某些安装步骤有的需要前置安装操作,有的又需要后 置安装操作,而某些安装操作则是没有用户界面的,我们统称这些安装操作为无用户界面的安装操作,那么,这些没有用户界面的安装操作由谁来调度运行呢?答案 就是Dispatcher。

InstallControlWindow 类在 anaconda 主目录下的 gui.py 模块中,控制安装过程中前端图形界面的显示,总体调度各个安装图形界面类,InstallControlWindow 建立图形界面的主窗体,每个具体的图形安装界面可视为其子窗体。InstallControlWindow 调用 Dispatcher 控制整个安装流程。

安装过程中,每个具体的图形安装界面均对应一个具体的类,由其对应的具体的类完成具体的安装任务,我们将这些图形界面类归为前端显示层,位于iw目录下的模块中。

anaconda将某些用户界面类中的具体安装操作分离出来,放到了另外一些模块中,这些模块放在了anaconda的主目录下。由Dispatcher直接调用的没有用户界面的安装步骤对应的函数也放在了anaconda的主目录下,我们将这些模块归为安装行为层。

图中虚线表达的含义是"可能调用"。如在安装过程中,负责前端显示的package_gui.py模块可能要调用负责安装行为的packages.py模块或其它负责安装行为模块中的类或函数。

2.1 主要控制类分析
2.1.1 Dispatcher

该 类在anaconda主目录下的dispatch.py模块中。无论是图形模式安装还是字符模式安装,都由该类来控制安装流程,所以,分析该类时,我们将 图形用户界面和字符用户界面统称为用户界面,涉及到字符安装模式的叙述,读者暂时可略过,读到字符安装模式分析时,再回头来看。dispatch.py模 块中有一个序列(sequence)数据结构:installSteps。installSteps中记录了有序排列的整个安装过程中所有可能的安装步 骤,在生成具体的Dispatcher实例时,会根据安装类型制定对此进行相应裁减。

installSteps中的条目(item)有如下两种格式,第一种格式:( name, tuple)

这种格式表示有用户界面的安装步骤。其中,name代表安装步骤的名称,tuple(元组,python的一种内置数据类型)存放创建相应安装步骤的用户界面的参数。

第二种格式:( name, Function, tuple)

这种格式表示没有用户界面的安装步骤,其中,name代表安装步骤的名称,Function指安装操作的具体执行函数,tuple存放的是传递给Function的参数。该安装步骤直接由Dispatcher调度进行。

下面我们结合具体的程序具体地解释一下上面的叙述,加深我们对安装流程的理解。

首先看下面从序列installSteps中截取的一个程序片断:




installSteps = [

……

("partitionmethod", ("id.partitions", "id.instClass")),

("partitionobjinit", partitionObjectsInitialize, ("id.diskset","id.partitions","dir", "intf")),

("partitionmethodsetup", partitionMethodSetup, ("id.partitions","dispatch")),

("autopartition", ("id.diskset", "id.partitions", "intf", "dispatch")),

("autopartitionexecute", doAutoPartition, ("dir", "id.diskset","id.partitions", "intf",

"id.instClass", "dispatch")),

("fdisk", ("id.diskset", "id.partitions", "intf")),

……

]

我们假设当前安装步骤为 partitionmethod,根据其条目的数据格式可以看出该安装步骤具有用户界面,根据安装步骤名称,如果熟悉linux安装过程,读者应该可以猜 出这个安装步骤是分区方式选择,对应该安装步骤的用户界面的类是PartitionMethodWindow,该类属于前端显示层,在iw目录下 partitionmethod_gui.py模块中。我们看一下该类的部分代码:




class PartitionMethodWindow(InstallWindow):

def getNext(self):

if self.useAuto.get_active():

self.partitions.useAutopartitioning = 1

else:

self.partitions.useAutopartitioning = 0

……

我们可以想象当用户在用户界面上点击"下 一步"按钮时getNext函数被调用,分析该函数,我们可以看出,当用户选择自动分区后,该函数设置 partitions.useAutopartitioning = 1,如果选择手动分区,则设置partitions.useAutopartitioning = 0。

本步骤完 成,安装过程进行下一步,Dispatcher首先在installSteps中取下一个条目"partitionobjinit",该条目中含有 function,说明此安装步骤没有用户界面,Dispatcher直接调用函数(Function) partitionObjectsInitialize,从字面上我们就可以看出,该函数是对分区操作进行相关初始化方面的操作,执行完该安装步骤后, Dispatcher继续调用下一个安装操作"partitionmethodsetup",该安装步骤也没有用户界面,Dispatcher直接调用函 数partitionMethodSetup,函数partitionMethodSetup和partitionObjectsInitialize都 属于安装行为层,在anaconda主目录下的partitioning.py模块中。我们看一下函数partitionMethodSetup的部分代 码:




def partitionMethodSetup(partitions, dispatch):

dispatch.skipStep("autopartition", skip = not partitions.useAutopartitioning)

dispatch.skipStep("autopartitionexecute", skip = not partitions.useAutopartitioning)

……

我们看出,如果用户选择手动分区,即 partitions.useAutopartitioning = 0,则not partitions.useAutopartitioning) = 1,即skip = 1,那么安装流程控制实例Dispatcher就会略过(skipsteip)安装步骤"autopartition"和 "autopartitionexecute"继续下一个安装步骤,下一个安装步骤有用户界面,对于图形安装模式,Dispatcher将控制权交给 InstallControlWindow,对于字符安装模式,Dispatcher将控制权交给InstallInterface。

我们再分析另一个从installSteps中截取的片断:




installSteps = [

……

("bootloaderadvanced", ("dispatch", "id.bootloader", "id.fsset", "id.diskset")),

("networkdevicecheck", networkDeviceCheck, ("id.network", "dispatch")),

("network", ("id.network", "dir", "intf")),

……

]

我们假设当前安装步骤为 "bootloaderadvanced",其后的另一个具有用户界面的安装步骤为网络配置"network",而进行网络配置的前置条件就是安装的主机 要具有网络设备,因此,系统安装程序在"network"之前插入了步骤"networkdevicecheck"。假设安装过程从当前安装步骤 "bootloaderadvanced"进行到下一个安装步骤"networkdevicecheck",该安装步骤没有用户界面, Dispatcher直接调用函数networkDeviceCheck,我们看一下该函数的代码(network.py):




def networkDeviceCheck(network, dispatch):

devs = network.available()

if not devs:

dispatch.skipStep("network")

可以看到,该函数首先检测是否存在网络设备,如果没有网络设备,Dispatcher则忽略安装步骤"network"。这里,可以说函数networkDeviceCheck是有用户界面的安装步骤"network"的前置操作。

通过上面的分析,我想我们不难理解为什么Dispatcher被称为"主要安装流程控制类"了。其它的安装步骤控制基本相同,我们不一一分析了。

下面我们看一下Dispatcher的主要接口。

gotoNext & gotoPrev:这两个接口分别从当前安装步骤前进(后退)到下一个(上一个)具有用户界面的安装步骤,在图形界面安装模式下,由 installcontrolwindow调用,在字符模式下,由InstallInterface调用。这两个函数只是简单的设置安装方向,然后调用 movestep函数,其核心操作是movestep。我们来重点分析movestep函数:




def moveStep(self):

……

if self.step == None:

self.step = self.firstStep

else:

self.step = self.step + self.dir

while ((self.step >= self.firstStep and self.step < len(installSteps))

and (self.skipSteps.has_key(installSteps[self.step][0])

or (type(installSteps[self.step][1]) == FunctionType))):

info = installSteps[self.step]

if ((type(info[1]) == FunctionType) and (not self.skipSteps.has_key(info[0]))):

(func, args) = info[1:]

rc = apply(func, self.bindArgs(args))

if rc == DISPATCH_BACK:

self.dir = -1

elif rc == DISPATCH_FORWARD:

self.dir = 1

self.step = self.step + self.dir

if self.step == len(installSteps):

return None

我 们重点看一下程序while循环体,首先看一下循环条件:当下一个安装步骤是合法的,即在第一个安装步骤和最后一个安装步骤之间,并且(and)该步骤被 裁减了或者该步骤是一个无用户界面的安装步骤,即installSteps的条目的第二个元素是一个function,则进入循环体。进入循环后, Dispatcher直接调用该函数执行安装操作,其中bindArgs是Dispatcher类的一个函数,负责参数解析,这里的apply是 python的的一个内置方法,用来执行函数,apply接口的第一个参数是要运行的函数名称,第二个参数是传给该函数的参数。如果下一个安装步骤依然无 用户界面,则继续循环,直到下一个没有被裁减的具有用户界面的安装步骤,对于图形安装模式,Dispatcher将控制权交给 InstallControlWindow,对于字符安装模式,Dispatcher将控制权交给InstallInterface。如果安装过程完成则 退出循环。

currentStep:Dispatcher类的另一个主要接口,取得当前的安装步骤及其相关信息返回 给调用者。在图形安装模式下,该函数主要由InstallControlWindow调度图形用户界面类时调用,在字符模式下,主要由 InstallInterface调度字符用户界面时调用,这两个类通过该接口取得当前安装步骤的用户界面对应的类及创建该用户界面类的实例所需的信息。

另 外,Dispatcher类的主要接口还有skipStep(self, stepToSkip, skip = 1, permanent = 0)是裁减安装步骤函数。setStepList(self, *steps)是安装步骤设置函数,主要由安装类型实例调用,每个安装类型会根据自身的特点设置安装步骤。这些接口的实现逻辑都比较简单,这里不一一给出 分析了。

至此,Dispatcher的主体基本分析完了,看完下面InstallControlWindow以及后面字符安装模式的InstallInterface类的分析,我们对Dispatcher将会有更好的理解。

3 InstallControlWindow
该类在anaconda主目录下的gui.py模块中。首先我们先看一下启动图形安装界面的入口函数run:




def run (self, runres, configFileData):

self.configFileData = configFileData

self.setup_window(runres)

gtk.main()

该函数调用了setup_window接口,该接口调用gtk"绘制"图形安装界面的主窗体,然hou控制权交给了gtk。

nextClicked & prevClicked:这两个接口分别执行从当前图形安装界面向前(向后)到下一个图形安装界面的操作,我们可以想象安装过程中当用户点击"下一步"或 "上一步"按钮时,这两个函数被调用。这两个函数首先调用主流程控制Dispatcher实例向前(向后)前进到下一个图形安装界面,然后调用 setScreen函数,从函数名称的字面上看,setScreen是设置图形界面的,那么接下来我们看一看该函数的具体功能,在分析其之前,我们要首先 看一下gui.py模块中的一个字典数据结构:stepToClass。该字典中记录了安装过程中所有的具有图形用户界面的安装步骤。




stepToClass = {

"language" : ("language_gui", "LanguageWindow"),

"keyboard" : ("keyboard_gui", "KeyboardWindow"),

……

}

每一个条目从左到右依次是安装步骤名称、 图形界面对应的类所在的模块,图形界面类的名称。如language为安装步骤名称,language_gui为该步骤对应的图形界面类所在的模块 language_gui.py,LanguageWindow为图形界面对应的类名。理解了该数据结构后,我们开始分析setScreen函数:




def setScreen (self):

(step, args) = self.dispatch.currentStep()

if not stepToClass[step]:

if self.dir == 1:

return self.nextClicked()

else:

return self.prevClicked()

(file, className) = stepToClass[step]

newScreenClass = None

s = "from %s import %s; newScreenClass = %s" % (file, className, className)

while 1:

exec s

break

self.destroyCurrentWindow()

self.currentWindow = newScreenClass(ics)

new_screen = apply(self.currentWindow.getScreen, args)

self.installFrame.add(new_screen)

self.installFrame.show_all()

……

前面的nextClicked和 prevClicked函数已经通过Dispatcher将要进行的安装步骤标记为当前安装步骤,所以该函数首先通过Dispatcher的 currentStep从Dispatcher的数据结构installSteps中取得当前安装步骤名称及相关信息,接下来,做了一下判断,如果 Dispatcher的当前安装步骤不在字典stepToClass中,则忽略该步骤,调用nextClicked或prevClicked继续下一个图 形界面安装步骤,直到下一个步骤在字典stepToClass中。验证通过后,从字典stepToClass中取得当前图形安装界面对应的类及该类所在模 块,然后导入该模块并创建图形安装界面的实例,销毁前一个图形安装界面,并将新创建的图形界面实例置为当前安装界面,调用图形安装界面实例的 getScreen函数生成该安装步骤的图形用户界面,然后显示。

至此,InstallControlWindow的主要逻辑已经分析完了,接下来涉及每个具体安装界面及其安装操作读者可以到iw目录下逐个深入分析。

4 主执行程序anaconda分析
前 面我们比较详细的分析了图形模式和字符模式的安装过程,对于图形安装过程,图形环境运行是建立在X Server基础上的,因此在进行图形安装时,必须运行X。因此,简单的说,对于图形模式,anaconda的主执行体就作了两件事:1、运行X服务器 (如果X没有运行)。2、启动图形模式安装过程。而对于字符模式,anaconda的主执行体就作了一件事,启动字符模式安装过程。

4.1 启动X server
如 果是图形模式安装,则anaconda首先要启动X server。anaconda首先检测图形正常显示所需的硬件资源。Python提供了许多内置模块实现相关硬件的操作。这些模块在目录 pythonXX/rhpl下(XX表示版本号,在笔者的机器上路径是/mnt/stage2/usr/lib/python2.2):

Videocard.py、 Monitor.py、Mouse.py、keyboard.py分别是显示卡、监视器、鼠标和键盘的相关操作和配置数据。xserver.py - 图形模式下X server启动初始化方面的工作,包括相关硬件的探测和X server的启动,xserver为videcard、monitor和mouse的探测提供了统一个接口,实际调用的是相应硬件模块的接口。

下边的程序片段探测显示卡、监视器和鼠标、键盘,并且利用xhwstate记录该配置状态,供后续安装过程使用。




import rhpl.xserver as xserver

(videohw, monitorhw, mousehw) = xserver.probeHW(skipDDCProbe=skipddcprobe,

skipMouseProbe = skipmouseprobe)

kbd = keyboard.Keyboard()

if keymap:

kbd.set(keymap)

xcfg = xhwstate.XF86HardwareState(defcard=videohw, defmon=monitorhw)

硬件检测完成后,启动X server的条件已就绪,如果X server没有启动,并且安装模式为图形模式,则启动X server:




xsetup_failed = xserver.startXServer(videohw, monitorhw, mousehw, kbd,

runres,

xStartedCB=doStartupX11Actions,

xQuitCB=doShutdownX11Actions,

logfile=xlogfile)

该调用除了传递相关硬件的配置信息外,还传递了两个函数。

doStartupX11Actions函数在X server启动时调用,该函数主要是启动窗口管理器,设置相关资源。

doShutdownX11Actions函数在X server退出时调用,关闭窗口管理器。

4.2 启动安装过程
我 们前面分析安装过程原理时指出:安装的流程由Dispatcher控制,对于图形模式,图形模式的前端显示及与用户的交互由 InstallControlWindow调度,而字符模式的前端显示层由InstallInterface调度。因此,启动安装过程,实际就是创建主要 控制类的实例,调用实例的接口,启动安装过程,然后再由这几个主要的控制类的实例创建具体安装界面,创建安装行为类的实例,调用具体的函数完成具体的安装 过程。

  • 创建实例

    1. 创建dispatch实例
    dispatch = dispatch.Dispatcher(intf, id, methodobj, rootPath)

    2. 创建InstallInterface实例

    对 于字符模式,前端字符用户界面由InstallInterface调度。而对于图形模式,我们在前面分析时,没有提到InstallInterface 类,其实在图形模式下,也有这么一个同名类,在模块gui.py中,但在图形安装模式下,这个类的作用很小,除了封装了创建常用对话框(包括 messageWindow,progressWindow,exceptionWindow等)的接口外,一个主要的作用就是为创建 installcontrolwindow实例提供了一个接口,为什么不在anaconda主执行程序中直接创建 installcontrolwindow实例呢?个人认为可能是为了保证代码的一致性。



    if (display_mode == 'g'):

    from gui import InstallInterface

    if (display_mode == 't'):

    from text import InstallInterface

    if display_mode == "t":

    intf = InstallInterface ()

    else:

    intf = InstallInterface ()

    3. 创建installcontrolwindow实例

    installcontrolwindow实例在gui.py模块中的InstallInterface类的接口run中创建,该接口创建了InstallControlWindow实例后运行InstallControlWindow实例的run接口启动图形安装界面:



    def run(self, id, dispatch, configFileData):

    ……

    self.icw = InstallControlWindow (self, self.dispatch, lang)

    self.icw.run (self.runres, configFileData)

  • 启动安装过程

    安 装控制类的实例创建完成后,调用其相应接口启动安装过程。对于字符安装模式,则直接调用InstallInterface实例的run接口。而对于图形安 装模式,则是由InstallInterface实例的run接口间接的调用installcontrolwindow实例的run接口。



    intf.run(id, dispatch, configFileData)

    另外,在启动安装过程前,anacoanda的主执行程序作了大量的繁琐的准备工作,主要包括引用模块路径设置、参数解析、内存检测、安装类型设置、创建安装介质实例等等。

anaconda安装程序较大,限于篇幅,文中分析不可能面面俱到,读者可以在理解程序运行的主要逻辑的基础上,参照原代码进一步分析。限于作者水平,分析中难免存在偏颇,恳请读者多多指正,欢迎来信讨论。

2007年3月15日星期四

Linux 脚本编写基础

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的内容:
echo "A is:"
echo $a
有时候变量名很容易与其他文字混淆,比如:
num=2
echo "this is the $numnd"
这并不会打印出"this is the 2nd",而仅仅打印"this is the ",因为shell会去搜索变量numnd的值,
但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:
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指定其他分割符。
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
大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件
是否存在及是否可读等等…
  通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。
[ -f "somefile" ] :判断是否是一个文件
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限
[ -n "$var" ] :判断$var变量是否有值
[ "$a" = "$b" ] :判断$a和$b是否相等
  执行man test可以查看所有测试表达式可以比较和判断的类型。
  直接执行以下脚本:
#!/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编程中也是
可用的。这里有个例子:
#!/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 类型的压缩文件:
#!/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
3. selsect
select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。
select var in ... ; do
 break
done
.... now $var can be used ....
下面是一个例子:
#!/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到屏幕上:
#!/bin/sh
for var in A B C ; do
  echo "var is $var"
done
下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息:
#!/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"的结果。
    引号 (单引号和双引号) 将防止这种通配符扩展:
#!/bin/sh
echo "*.jpg"
echo '*.jpg'
  这将打印"*.jpg" 两次。
  单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。
#!/bin/sh
echo $SHELL
echo "$SHELL"
echo '$SHELL'
  运行结果为:
/bin/bash
/bin/bash
$SHELL
  最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆:
echo *.jpg
echo $SHELL
  这将输出:
*.jpg
$SHELL
6. Here documents
当要将几行文字传递给一个命令时,here documents(译者注:目前还没有见到过对该词适合的翻译)
一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个 here documents
就不必用echo函数一行行输出。 一个 "Here document" 以 << 开头,后面接上一个字符串,这个字符串
还必须出现在here document的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且
使用here documents打印帮助:
#!/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)函数
如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,
并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的:
functionname()
{
# inside the body $1 is the first argument given to the function
# $2 the second ...
body
}
您需要在每个程序的开始对函数进行声明。
  下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。
这里使用了一个叫做help的函数。正如您可以看到的那样,这个定义的函数被使用了两次。
#!/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表达式的例子无遗是一个不错的方法。
#!/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命令进行数学运算的例子:
#!/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
  这将返回所有语法错误


http://raymondchen625.blogbus.com/logs/2005/12/1752423.html


shell简介入门

1 shell简介入门bash编程之执行

在Bourne Shell中有五种方式执行一个命令,而这五利方式所产生的结果有所不同:
  ::直接下命令:这个方式和在命令行中用命令的效果一样。
  ::使用sh命令sh command:这时的文件必须是Bourne Shell的脚本,但这个文件并不一定要设成
  可执行。除此之外和直接用命令的方式一样。
  ::使用"."命令command:这时和使用sh命令相似,只不过它不像sh一般会产生新的process,相反
  地,它会在原有的process下完成工作。
  ::使用exec命令exec command:此时这个脚本将会被所执行的命令所取代。当这个命令执行完毕
  之后,这个脚本也会随之结束。
  ::使用命令替换:这是一个相当有用的方法。如果想要使某个命令的输出成为另一个命令的参
  数时,就一定要使用这个方法。我们将命令行放在两个"`"符号。
  例如:
  str=`Current directory is ``pwd`
  echo $str
  结果如下:
  Current directory is /home/dfbb
  这个意思是pwd这个命令输出"/users/cc/mgtsai",然后整个字符串代替原来的pwd设定str变量,
  所以str变量的内容则会有pwd命令的输出


shell简介入门之bash编程之流程控制(1)


在介绍流程控制之前我们先来看看twst命令。test命令的参数是条件判式,当为真时则传回非零
  值,而条件为假时则传回零。在所有的流程控制都必须用到test命令来判断真假。另外一种方法
  是使用中括号[],一般都是用中括号居多。测试的种类有:
  A::字符串测试
  string1 = string 2 两字符串是否相等
  string1 != string2 两字符串是否不等
  string  字符串是否是空的
  -z string 字符串长度是否为0
  -n string 字符串长度是否非0
  B::整数测试
  -eq 等于
  -ne  不等
  -lt 小于
  -gt  大于
  -le 小于或等于
  -ge 大于或等于
  C::文件测试
  -b 区块文件
  -c 字符文件 
  -d 目录
  -f 一般文件
  -r 可读
  -w 可写
  -x 可执行
  -k 设定了限定位
  -g 设定了组位
  -u 设定了use id
  -p 管线
  -s 文件大小非0
  以下介绍各种流程控制
  A::
  if then
  语法如下:
  if (condition)
   then
   then-commands
  fi
  condition是一个test命令。往的一所介绍的各种流程中的conditon都是test命令。
  例如:
  test4.sh
  --------------------------------------------------
  #!/bin/bash
  if(test $# !=0)
   then
   echo Arg1:$1
  fi
  --------------------------------------------------
  $/test4.sh hello
  Arg1:hello
  $./test4.sh
  $
  B::
  if then else
  语法如下:
  if(confition)
   then
   then-commands
   else
   else-commands
  fi
  C::
  if then elif
  语法如下:
  if (conditon1)
   then
   commands1
  elif(condition2)
   then
   commands2
  else
   commands3
  fi
  例如:
  test5.sh
  -------------------------------------------------------------
  #!/bin/bash
  echo `word 1:`
  read word1
  echo `word 2:`
  read word2
  echo `word 3:`
  read word3
  if(test "$word1" = "$word2" -a "$word2" = "$word3")
   then
   echo `match:words 1,2 & 3`
  elif(test "$word1" = "$word2")
   then
   echo `match:word 1 & 2`
  elif(test "$word1" = "$word3")
   then
   echo `match:words 1 & 3`
  elif(test "$word2"="$word3")
   then
   echo `match:words 2 & 3`
  else
   echo `no match `
  ------------------------------------------------------
  $./test5.sh
  word 1:
  do
  word 2:
  do
  word 3:
  do
  match:words 1,2&3
  D::
  for in
  语法如下:
  for var in arg-list
   do
   commands
  done
  例如:
  test6.sh
  --------------------------------------------------
  #!/bin/bash
  for a in xx yy zz
   do
   echo $a
  done
  ---------------------------------------------------
  结果如下:
  xx
  yy
  zz
  E::
  语法如下
  for var
   do
   commands
  done
  例如
  test7.sh
  -------------------------------
  #!/bin/bash
  for a
   do
   echo $a
  done
  -----------------------
  $./test7.sh xx yy zz
  xx
  yy
  zz
  F::
  while
  语法如下:
  while(condition)
  do
   commands
  done
  例如:
  #!/bin/bash
  number=0
  while(test $number -lt 10)
   do
   echo "$number\c"
   number=`expr $number+1`
  done
  echo
  -----------------------------------------
  结果如下:
  0123456789
  G::
  until
  语法如下:
  until(condition)
  do
   commands
  done
  它和while的不同只在于while是在条件为真时执行循环,而until是在条件为假时执行循环。
  H::
  break及continue
  这两者是用于for,while,until等循环控制下的。break会跳到done后才执行,而continue会跳至
  done后才执行,而continue会跳至done执行,继续执行循环。
  I::
  case
  语法如下:
  case str in
   pat1) commands1;;
   pat2) commands2;;
   pat3) commands3;;
  esac
  而pat除了可以指定一些确定的字符串,也可以指定字符串的集合,如下:
  * 任意字符串
  ?   任意字符
  [abc]  a,b,c三字符其中之一
  [a-n]  从a到n的任一字符
  │    多重选择
  例如:
  test8.sh
  -----------------------------------------------------------
  #!/bin/bash
  echo `enter A,B,C:"
  read letter
  case $letter in
  A│a) echo `you entered A.`;;
  B│b) echo `you entered B.;;
  C│c) echo `you entered C,;;
  *) echo `not a,b,c`;;
  esac
  ---------------------------------------------------------------------- 
  J::
  函数
  格式如下:
  function-name()
  {
   commands
  }

2007年3月13日星期二

Linux下设置串口终端

在某些情况下,出于调试的目的需要给linux配置串口控制台,或者linux根本没有显卡,也需要配置串口终端。一个串口控制台将发送所有的终端显示到串口,而串口终端则可以实现通过串口登录到系统。你可以同时设置两个或者其中一个。
为了控制内核输出所有控制台消息到串口,你需要在时向内核传递参数console=ttyS0,这可以通过GRUB来实现,下面的例子会将控制台消息发送到tty0(显示器控制台)和ttyS0(串口1),发送到串口控制台的速度为115200,但是需要注意的是一种类型设备只能定义一个控制台,例如不能把消息同时发送到串口1和串口2,编辑/boot/grub/grub.conf,添加如下内容: console=ttyS0,115200 console=tty0

串口终端 为了设置一个串口终端,需要为串口衍生(spawn)一个agetty,在/etc/inittab添加如下内容: co:2345:respawn:/sbin/agetty ttyS0 115200 vt100 init q 例如: # Run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
co:2345:respawn:/sbin/agetty ttyS0 115200 vt100 init q
也可以在/etc/securetty文件中添加串口设置,这样才可以以root身份从串口登录,在该文件最后添加一行,内容为
ttyS0